From 3433eee570eb44c7041478449b3dccda2fb540e9 Mon Sep 17 00:00:00 2001 From: Nijil Nirmal <62882794+nijil-deriv@users.noreply.github.com> Date: Fri, 6 Oct 2023 16:05:27 +0400 Subject: [PATCH] Feature/wallets with traders hub (#8810) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add 3 static steps for wallet upgrade * fix: responsive * fix: add dynamic steps * fix: address comments * fix: address comments and combine 3 steps to 1 component * test: add test for wallet-steps component * chore: add deriv ui in package * fix: rename togglewalletsupgrade * fix: use Wizard component * fix: move steps to config * fix: use Modal * fix: remove css * chore: add comments * fix: use walletimage * fix: rename config gile * redeploy: Empty-Commit * fix: cfd sub_account_type * fix: hamid commentz * fix: subtasks * fix: eu content * fix: responsive * fix: test import * fix: css responsive * fix: align icon * fix: retrigger * fix: remove trading * feat: wallet styles (#8358) * feat: wallet styles * feat: change name * feat: remove duplicate icons * feat: remove duplicate icons * docs: add sample class to wallet mixin * hamid/wallet-icon-and-small (#8391) * feat: wallet-icon * fix: wallet-icon and wallet-small * feat: update styles * feat: update styles * feat: update styles * feat: update styles * feat: export wallet * feat: update type * chore: trigger-test * Sergei / wall 147 / in progress and migrated banner (#8201) * feat: create a scratch of desktop upgrade banner * feat: create upgrade wallet banner * feat: add review suggestions and rename WalletsBannerImage component * feat: add images and scratch of components * feat: add some suggestions * feat: add some images * feat: almost complete upgrading banner * feat: finished with ready banner * feat: change title for ready banner * refactor: change icon name * feat: add size for tick * feat: add tests * feat: add some changes from PR review * feat: add changes from review * feat: add some constants * refactor: add React.Fragment * refactor: change images name * feat: add suggestions after review * feat: add switch to wallets-banner component * feat: change short dash to long dash * feat: add logout when click ready banner logout button * feat: fix tests * feat: add 1 more test for logout * george / WALL-520 / create badge component (#8384) * feat: :sparkles: add badge component * perf: :zap: rename label prop to weight prop * perf: :zap: add Discriminated unions types, fix colors, replace cheildren with label prop * refactor: :zap: refactor code * perf: :zap: add export from components package * refactor: :zap: refactor badge component, add tests * refactor: :zap: refactor css * fix: :bug: fix tests * Farzin/wall 548/Add `GradientBackground` component (#8436) * feat(components): :sparkles: add `GradientBackground` * style(components): :lipstick: add support for mobile * fix(components): :memo: resolve PR comments * feat(components): :sparkles: add `Watermark` --------- Co-authored-by: Farzin Mirzaie * chore: update feature branch with master (#8525) * chore: remove unused icons from wallet folder inside components package (#8507) * chore: remove unused icons from wallet folder inside components package * chore: build icons * Aum/WALL-562/create-wallet-icon-component (#8501) * feat: created wallet-icon component * feat: integrated wallet-icon with gradient-background-wallet-icon * fix: changed the color positioning for icon gradient * chore: replaced currency prop with icon * chore: removed wallet-small and wallet-icon made by @hamid-deriv * chore: removed dark prop for wallet-icon * refactor: made gradient-background-wallet-icon dynamic and refactored wallet-icon * fix: made changes from comments * chore: renamed gradient-background component to two-point * chore: removed unused code * chore: create component for consent wallet popup (#8178) * chore: create component for consent wallet popup * fix: added info text based on clients region and risk status * fix: added test case * fix: make changes based on comments and reviews * fix: added mobile stylings * fix: small tweaks * fix: change svg added small changes to the stylings * fix: commit suggestions * fix: commit resolved messages * fix: commit changes based comments and did code improvements * fix: made changes to information list file * fix: added title border and checkbox function fix * fix: added bold text for low risk cr accounts in modal * fix: replace text for p2p * fix: modal not displaying properly on small screens * fix: issue with small screen final * Sergei / wall 272 / wallet desktop header (#8334) * feat: create scratch of the header * feat: add WalletsImage component * feat: add limiting types * feat: trying to add size changing for SVG (not completed) * feat: add some icons * feat: optimise svgs * feat: add buttons * feat: collapsed wallet header * feat: commit to check in test link * feat: add wallet bg with css * feat: add check for dark mode * feat: change color for svg for light/dark themes * feat: add hover on buttons * feat: add some check for demo wallet * feat: delete uploaded icons because these icons already exists * feat: delete currency icons because its already exist * feat: add some svgs * feat: add demo color and mixin * feat: create wallet-currency-card component * feat: divide component to some small components * feat: add status badge * feat: repair background image for appstore * feat: add bg svg * feat: move background image * feat: add disabled buttons * feat: delete unnecessary prop * feat: delete comments * feat: delete some comments * feat: add some test cases * fix: fix one test * feat: change some types and add util function * refactor: delete comment * refactor: delete comment * feat: delete unnecessary icons * feat: add some suggestions * refactor: delete comments * feat: add check for fiat * feat: delete default values for component props * feat: complete tests * refactor: change var name * feat: add 3 more tests * feat: add actions and button types * fix: fix scss var color * feat: move wallet buttons creation function to utils * refactor: delete curly braces * refactor: change import order * refactor: delete line which breaks eslint test * feat: use Badge component * feat: add suggestions * feat: change types and shortcode for some wallets * feat: make account_type prop as optional * feat: empty just to start deployment * feat: change bg color to gray * feat: change getWalletCurrencyIcon and add some icons * feat: empty to restart vercel * feat: update getWalletCurrencyIcon function * feat: format balance money * fix: repair test * fix: move wallet-balance to the right side of the screen * refactor: change opacity * feat: Rostislav / WALL-361 / Cashier Fiat Transfer amount input field (#8442) --------- Co-authored-by: George Usynin <103181646+heorhi-deriv@users.noreply.github.com> Co-authored-by: Farzin Mirzaie <72082844+farzin-deriv@users.noreply.github.com> * chore: stitch banner with popup (#8476) * chore: create component for consent wallet popup * fix: added info text based on clients region and risk status * fix: added test case * fix: make changes based on comments and reviews * fix: added mobile stylings * fix: small tweaks * fix: change svg added small changes to the stylings * fix: commit suggestions * fix: commit resolved messages * fix: commit changes based comments and did code improvements * fix: made changes to information list file * fix: added title border and checkbox function fix * fix: added bold text for low risk cr accounts in modal * fix: replace text for p2p * fix: modal not displaying properly on small screens * fix: issue with small screen final * chore: stitch banner with popup * fix: refactor some code * fix: refactor code * fix: test cases * chore: stitching 80% done * fix: fixing all the test cases * fix: commit suggestions * fix: fix test cases and other stuff * fix: remove unused stuff * fix: edit stylings making mobile view look better * fix: added form line height to wallet intro component * fix: refactor somemore * fix: commit suggestions * empty commit * empty commit * retrigger commit * chore: retrigger commit * fix: commit suggestions * fix: circleci error * fix: wallet banner test case * fix: tests * fix: issue with the disabled buttons and checkbox * fix: updated real-wallets-upgrade * fix: circleci error * fix: remove button outline from banner * fix: merge conflicts * fix: resolve circle ci issues * fix: added changes to show the banner * fix: circle ci issues * fix: remove additional button props --------- Co-authored-by: Thisyahlen Nair * Aum/wall 278/create wallet card component (#8580) * feat: created wallet-card and integrated wallet-icon for small size * refactor: changed height of gradient-background * feat: added states for wallet-card small * feat: added card shine effect to wallet-card for medium and large * feat: wallet-card states completed * fix: rendering values in correct places * refactor: added single prop for handling wallet-card states * fix: applied correct padding and font-size for mobile * feat: included 'added' state for wallet-card * chore: added constants config for wallet-card * chore: applied changes from comments * fix: wallet-card state becomes default for add/added states * refactor: hamid-aum-forked-wallet-card * refactor: enhance-wallet-card * feat: enhance-wallet-card * docs: add comment * revert: revert trader-hub home page * refactor: refactor wallet-icon * fix: resolve comments * fix: fixed some styling * refactor: removed parent hack from scss and fixed all the states * chore: removed mock response file --------- Co-authored-by: Hamid Yaftian Co-authored-by: Hamid * fix: issues during conflict resolution * fix: fix serve error (#8749) * fix: fix serve error * fix: fix serve error * feat: add app-icon and update wallet-icon (#8730) * feat: add app-icon and update wallet-icon * chore: clean code * fix: fix eslint error * chore: update pr * feat: update component name * feat: update component name * fix: update component name * chore: retrigger the test * Update packages/appstore/src/assets/svgs/currency/index.tsx * Update packages/reports/src/Containers/statement.tsx --------- Co-authored-by: Nijil Nirmal <62882794+nijil-deriv@users.noreply.github.com> * hamid/wall-432/transfer-account-selector-refactor (#8758) * refactor: refactor transfer-account-selector with new components * feat: add is_value prop * fix: enhance app-icon style * fix: enhance app-icon style * fix: enhance modal prop * fix: apply pr commits * feat: use badge component * fix: apply pr commits * feat: add portal_id prop * george / WALL-306 / add wallet cashier modal new (#8750) * feat: :sparkles: add wallet cashier modal * fix: align files with feature branch * Aum/wall 5/the upgrade modal which wallets will be created and linked (#8751) * feat: created wallets linking step for wallet upgrade * feat: create responsive wallet-link component * feat: renamed wallet-link to wallet-link-wrapper and completed static UI * feat: created wrapper for the 2nd step in wallet-upgrade * feat: added dependency for hooks to appstore and added useBalance and useWalletMigration hooks * chore: minor UI fixes * fix: fixed scrolling of accounts in mobile view * feat: appended the 2nd step to 1st step * feat: rendering all data for each step during linking * chore: added some mock data * refactor: replaced placeholder card with wallet-card component * fix: removed z-index and fixed shine (trapezoid) element * chore: add types for wallet-card * feat: added types to all the tsx files * fix: fixed some type errors * fix: fixed the active border issue in wallet-card * chore: changed the state of wallet-card in wallet-linking to default * fix: fixed wallet title in mobile mode * fix: changed color for text and fixed clipping of linking step content * fix: fixed scrolling when only one wallet with few accounts is shown * chore: made changes from review suggestions * chore: removing unused classnames * chore: changed order of imports for wallet-link-wrapper * chore: changed height for wallet-linking-step * chore: resolved comments * chore: add USDC crypto mock data * chore: added rebranded icons to components package * fix: fixed the link line units using px * chore: added margin top and bottom to link-wrapper * fix: changed linking step height * fix: fixed the bracket for safari browser * Sergei/wall 273/ (NEW) Complete wallets (#8786) * feat: add some changes * feat: add useContentFlag hook * feat: add all files * feat: implement updated WalletIcon component * feat: add deleted test * feat: create success toast message for wallets (#8763) * Sergei / wall-578 / (Updated) Pop up for wallet migration failed (#8765) * feat: create PR from latest feature branch * feat: delete hooks from appstore (not related to this PR) * feat: add hooks to appstore * hamid/wall-934/fix-type-error (#8903) * fix: type errors of balance in wallet * chore: change currency-switcher-account * chore: change _ to - in testid * chore: retrigger the test --------- Co-authored-by: Sergei Baranovski * thisyahlen/fix: sorting of wallets list (#8926) * fix: sorting and refactor * fix: logout issue * fix: open only 1 wallet at a time * chore: add test for useWalletlist * fix: to fix type for shortcode * fix: test type * fix: comments and suggestions * fix: comments * fix: test * fix: show wallets to only high risk clients * redeploy: codecov * george / WALL-431 / Demo: Input & selector stitching (#8794) * feat: :sparkles: account transfer component (demo) * fix: transfer hint message * fix: :art: apply comments * refactor: :art: remove reducer, simplify transfer logic, improve readability (#34) * fix: update padding of app-icon component (#33) * style: improve styles for account list * style: apply comments * fix: apply comments, refactor account modal in desktop * fix: :art: apply comments * feat: add scrolling behaviour to account list in mobile view * fix: apply comments, fix test * refactor: :fire: remove mobile dialog, refactor scrolling, remove redundant logic * fix: apply comments, refactor tests * test: :rocket: add tests for transfer related components * style: fix style for merged icon * fix: mock loginid * fix: typo, demo icon, transition on closing, blinking icons * perf: disable automatic opening of wallet modal --------- Co-authored-by: Hamid * Rostislav / WALL-454 / Fiat transaction list (post feature branch update) (#8753) * refactor: add DeepRequired for use * refactor: prototypes * refactor: better prototypes * refactor: almost done w/o responsive and themes * refactor: tmp number formatting (better use hooks) + minor changes * refactor: added a degree of responsiveness * refactor: line heights and font sizes * refactor: code improvement * refactor: remove duplication in testing component * refactor: adding FiatTransactionList with hardcoded list for now * refactor: deriv/components deps * refactor: added hook for grouped transactions * refactor: add lodash.groupby and moment to hooks deps * refactor: useGroupedFiatTransactions * refactor: visuals pretty much done w the grouping logic * refactor: add spec for hook * refactor: move FiatTransactionList to modal * refactor: filling the gaps for transactions * refactor: API call for transactions added * refactor: add logic for transfer transactions * refactor: minor change * refactor: switched to using WalletIcon * refactor: added is-crypto check for transfer transaction (other) account currency * refactor: transaction list style change fix for later * refactor: transactions_ -> mock_transactions * refactor: types and logic * refactor: better transactions.map(...) * refactor: improved code * refactor: post-feat-branch-merge fixes * refactor: remove unnecessary change * refactor: hardcode app transactions * refactor: remove seemingly redundant useCallbacks * refactor: TODO * refactor: fiat-transaction-list-item.tsx suggestions * refactor: some of the styles suggestions applied * refactor: scss variable * Update packages/appstore/src/components/fiat-transaction-list/fiat-transaction-list.tsx Co-authored-by: Hamid * Update packages/appstore/src/components/fiat-transaction-list/fiat-transaction-list.tsx Co-authored-by: Hamid * refactor: not styling direct html tags now * refactor: move style import from index.ts to fiat-transaction-list.tsx * refactor: app acc mock name change * refactor: isMobile() -> is_mobile * refactor: separated fetching and grouping * Merge remote-tracking branch 'upstream/feature/wallets_with_traders_hub' into rostislav/WALL-641/transaction-list-item-new * refactor: account balance type fix * fix: tests fix * refactor: switched back to using mock transaction list * refactor: update mock data * refactor: fixes related to appearance * refactor: minor fix * refactor: minor changes * refactor: icons * refactor: changed wallet-icon.tsx + other small changes * refactor: a fix for malta wallets * refactor: a few more appearance changes * refactor: qa appearance fixes * refactor: deriv apps fix * refactor: real page fix * refactor: one more transaction * refactor: + initial deposit for real * refactor: a few more changes to styles * refactor: a few more changes to styles * refactor: z-index shenanigans * refactor: app icon dark theme * refactor: package.json * refactor: (tmp) remove failing test * refactor: return failing test * fix: tests * refactor: svg optimized * refactor: svg fixes --------- Co-authored-by: Hamid * synchronize amount inputs, add transfer button (WALL-554) with test link (#8962) * feat: :sparkles: account transfer component (demo) * fix: transfer hint message * fix: :art: apply comments * refactor: :art: remove reducer, simplify transfer logic, improve readability (#34) * fix: update padding of app-icon component (#33) * style: improve styles for account list * style: apply comments * fix: apply comments, refactor account modal in desktop * fix: :art: apply comments * feat: :sparkles: synchronize amount inputs, add transfer button * perf: refactor code * refactor: remove listener, add onScroll to mobile_dialog, remove ref from mobile_dialog * fix: apply comments * fix: reset amount_to if selected transfer_from is a wallet * feat: :sparkles: account transfer component (demo) * fix: transfer hint message * fix: :art: apply comments * refactor: :art: remove reducer, simplify transfer logic, improve readability (#34) * fix: update padding of app-icon component (#33) * style: improve styles for account list * style: apply comments * fix: apply comments, refactor account modal in desktop * fix: :art: apply comments * feat: add scrolling behaviour to account list in mobile view * fix: apply comments, fix test * refactor: :fire: remove mobile dialog, refactor scrolling, remove redundant logic * fix: name visibility after closing the modal * perf: improve transition behaviour * test: fix tests * refactor: :fire: remove mobile dialog, refactor scrolling, remove redundant logic * fix: add missing code * refactor: apply comments * fix: apply comments, refactor tests * test: :rocket: add tests for transfer related components * refactor: roll back Tab component * style: fix style for merged icon * fix: mock loginid * fix: mock account loginid * fix: typo, demo icon * fix: typo, demo icon, transition on closing, blinking icons --------- Co-authored-by: Hamid * feat: added wallets notifications to wallets (#8837) * feat: added wallets notifications to wallets * fix: remove commentted code * fix: show notifications for platforms only * fix: change label on CTA * fix: added hooks package * fix: update branch * chore: remove unused code * Hamid/wallet card colors mixin (#9008) * feat: update wallet cards color mixin * feat: update wallet colors mixin and wallet modal header * feat: change wallet color in wallet card and icon * chore: retrigger ci tests * feat: update components based on the new gradient color prop * fix: revert index.tsx * fix: update test cases * fix: update test cases * fix: fix pr comments * fix: fix transfer icon bg * feat: replace gradient_for with gradient_*_class props * fix: failed build * fix: fix pr comments * hamid/wall-582/demo-reset-balance-tab (#8899) * feat: add demo reset tab component * feat: add redirect to transfer tab setter * fix: add redirect to transfer tab setter * test: add test to demo-reset-balance component * test: add test to demo-reset-balance component * fix: apply pr commits * fix: apply pr commits * feat: apply pr commits * fix: add missing package * fix: use balance store for getting demo wallet balance * chore: retrigger ci tests * chore: retrigger ci tests * fix: change text after transfer success * fix: apply pr commits * fix: apply pr commits * fix: change icon of the success step * feat: update design based on the new changes * Thisyahlen/WALL-1020/ List of wallets according to authorize and balance (#9010) * chore: add balance from response, switching and refactor * fix: test and verification only for eu users * fix: tests * fix: modal first, then switch account * fix: refactor logic to hooks * fix: refactor to use wallet_account instead of data * redeploy: vercel * fix: refactor test * fix: tests and address comments * fix: use debounce instead of settimeout * fix: refactor tests and hooks * fix: use landing_company_name instead of shortcode * fix: test title * fix: scroll to active wallet upon click * fix: resolve test and comments * fix: test * fix: tests again * feat: implement withdraw verification reques (#9125) * feat: implement withdraw verification reques * feat: update component name * feat: update typo * feat: change icons with new icons design provided * chore: retrigger ci tests * chore: retrigger ci tests --------- Co-authored-by: mahdiyeh-deriv <82078941+mahdiyeh-deriv@users.noreply.github.com> * yashim/feat: add mock server integration phase1 (#9003) * feat: add mock server integration * refactor: use session instead of client * fix: persistent mock server enable state * chore: draft * feat: add mock server control panel UI * feat: add clear all functionality * feat: completed login mock * fix: tests * fix: code * feat: add feature toggle * feat: end of day commit * fix: review comments + tests * chore: used deriv-api * fix: typescript error * chore: update package lock --------- Co-authored-by: Dev Sans * Rostislav / Wall-455 / Transaction list filter (Demo&Real) (#9007) * refactor: init pr * feat: adding filter dropdown component * feat: restructuring FiatTransactionList -> TransactionList + added the filter * refactor: localize * refactor: appearance fixes * refactor: further appearance fixes * refactor: grouping transactions now in utils * refactor: minor change * refactor: new hook * refactor: useWalletsList.ts changes by @sergei-deriv * refactor: continuation of the prev commit * refactor: typescript shenanigans * refactor: use wallet list * refactor: moving stuff to hooks * refactor: removed unused stuff for transaction-list.tsx * refactor: improving logic * refactor: test file * refactor: no more circular deps * refactor: improved hook * refactor: test * test: add tests (#10) * test: fix tests * fix: apply comments * fix: apply comments, refactor code * test: fix test * test: reactor code * trigger build * trigger build * refactor: fixing appearance * update feature_branch with master (#9220) * fix: close account message doesn't show up (#9210) Co-authored-by: niloofar sadeghi * Jim/WEBREL-667/fix-bug-revealed-during-ts-migration (#9216) * revert: revert code changes * fix: fix bug revealed during ts migration * chore: add test cases for line changed --------- Co-authored-by: Niloofar Sadeghi <93518187+niloofar-deriv@users.noreply.github.com> Co-authored-by: niloofar sadeghi Co-authored-by: Jim Daniels Wasswa <104334373+jim-deriv@users.noreply.github.com> * refactor: mock data change * refactor: finish resolving * refactor: remove package-lock.json changes * refactor: fix tests * refactor: resolve some ts problems * refactor: resolve some ts problems * refactor: one more small fix with the help of @heorhi-deriv * refactor: remove unused @ts-expect-error * refactor: remove fiat-transaction-list.tsx --------- Co-authored-by: George Usynin <103181646+heorhi-deriv@users.noreply.github.com> Co-authored-by: george-usynin-binary Co-authored-by: mahdiyeh-deriv <82078941+mahdiyeh-deriv@users.noreply.github.com> Co-authored-by: Niloofar Sadeghi <93518187+niloofar-deriv@users.noreply.github.com> Co-authored-by: niloofar sadeghi Co-authored-by: Jim Daniels Wasswa <104334373+jim-deriv@users.noreply.github.com> * Sergei / wall 680 / Desktop wallet dark theme (#9006) * style: change text color and add bg color for demo wallet in dark mode * feat: add logo for dark theme * feat: add @deriv/hooks to package.json in appstore * chore: empty commit to restart vercel * refactor: wrap TradersHub component by observer * chore: empty commit to restart vercel * feat: change color of button border * update feature_branch with master (#9220) * fix: close account message doesn't show up (#9210) Co-authored-by: niloofar sadeghi * Jim/WEBREL-667/fix-bug-revealed-during-ts-migration (#9216) * revert: revert code changes * fix: fix bug revealed during ts migration * chore: add test cases for line changed --------- Co-authored-by: Niloofar Sadeghi <93518187+niloofar-deriv@users.noreply.github.com> Co-authored-by: niloofar sadeghi Co-authored-by: Jim Daniels Wasswa <104334373+jim-deriv@users.noreply.github.com> * feat: fix test for useWalletsList * fix: try to fix useWalletsList test * feat: add type to default_mock_useFetch_response * feat: just add @ts-expect-error * chore: change number to boolean --------- Co-authored-by: mahdiyeh-deriv <82078941+mahdiyeh-deriv@users.noreply.github.com> Co-authored-by: Niloofar Sadeghi <93518187+niloofar-deriv@users.noreply.github.com> Co-authored-by: niloofar sadeghi Co-authored-by: Jim Daniels Wasswa <104334373+jim-deriv@users.noreply.github.com> * george / wall-14 / fiat deposit (#9060) * feat: add fiat deposit iframe * test: fix test * Quick fix for `useWalletList` hook (#9256) * fix: useWalletsList.ts * refactor: changes requested by @heorhi-deriv * refactor: changes requested by @sergei-deriv * refactor: remove unnecessary falsy check * feat: added add-wallets section (#9018) * feat: added add-wallets section * chore: added carousel and hooks * chore: 90% finish implementation * fix: commit suggestions * feat: done but without test cases * fix: added test cases but wil improve on it * chore: small changes but big impact 😎 * fix: added loading screen to the component * fix: commit suggestions * fix: resolve somemore comments * fix: small changes * fix: circleci * fix: this is the last one i swear * fix: resolved comments * fix: commit suggestions * fix: commit suggestions * fix: oh man oh man oh man * fix: rename UST to USDT * fix: update test cases to reflect on the changes * fix: usd description * fix: commit suggestions * fix: added cursor pointer to wallet cards * fix: small changes * fix: i know its failing thats why im pushing this commit to fix it * fix: i know its failing thats why im pushing this commit to fix it * fix: test cases and vercel * fix: retrigger vercel * fix: make changes for the icon and the position of the arrow btn * fix: test cases * fix: commit suggestions * fix: remove unnecessary files from commit * fix: firefox issues and wallet card btn * fix: commit suggestions * fix: commit suggestions pt.2 * chore: fix test * fix: circle ci issue * fix: circle ci errors * fix: circle ci pt.2 * fix: circle ci pt.3 * fix: removed duplicate entries * chore: update package-lock using npm run bootstrap:dev * Farzin/Putting wallet behind feature flag [WIP] (#9236) * refactor(appstore): :fire: clean-up * refactor(appstore): :fire: clean-up * refactor(appstore): :fire: clean-up * refactor(appstore): :fire: clean-up * refactor(appstore): :fire: clean-up * refactor(appstore): :fire: clean-up * refactor(appstore): :fire: clean-up * refactor(appstore): :fire: clean-up * refactor(appstore): :fire: clean-up * refactor(appstore): :fire: clean-up * refactor(appstore): :fire: clean-up * refactor(appstore): :fire: clean-up * refactor(appstore): :fire: clean-up --------- Co-authored-by: Farzin Mirzaie * Sergei / wall-348 / Responsive Wallets: The complete flow (#9015) * feat: add wallet cards to carousel * feat: add check for is_mobile * feat: add transition for pagination * feat: add recalculate feature for carousel when the user changes screen size * feat: finish with UI part * feat: create common type for desktop and responsive wallets * feat: add sorting function * refactor: delete wallet word from wallet name * feat: add react-slick library * feat: intermediate result of carousel * feat: intermediate result * feat: delete TWalletType * feat: add useWalletAccount test stub * feat: move styles to AccountWithWallets * refactor: delete comments * feat: delete comments again * feat: add generic type to useRef * refactor: move TWalletAccount to common.types.ts * feat: delete React.memo, doesn't need now * chore: empty commit to restart vercel * feat: change balance type from string to number and repair tests * chore: restore tests * feat: add swipe to slide and make container wider * feat: create separate button component and refactor WalletHeader * chore: empty commit to restart vercel * refactor: delete styles for dots * feat: add tests for wallet-cards-carousel * feat: change wallet-transfer-block tests * feat: add tests for wallet-button component * feat: delete comments from style * feat: delete slick-theme.scss file * feat: move slick.scss file to traders-hub folder * refactor: delete commented lines * feat: make wallet_account optional * refactor: rename hook * feat: add wallets accounts to list of valid logins * feat: allow only one open desktop wallet * feat: repair test * feat: implement some suggestions * feat: rename the hook * refactor: rename useWalletAccounts test file * feat: intermediate result. Have to think about useCallback * feat: move convertWallets to utils * refactor: implemet some suggestions * feat: add TODO * feat: fix wallet-card component to show proper demo badge * chore: empty commit to restart vercel * feat: change library * feat: repair test * refactor: delete comment * feat: change color to prominent for Text components * feat: change jurisdiction to UpperCase * feat: delete test select and add hooks to package.json * feat: add fake accounts because qa29 is unavailable * chore: empty commit to restart vercel * fix: fix console error * feat: imtermediate result * feat: change tabs order * feat: add plus icon for CFDs * feat: add select as for desktop * feat: add compare accounts for fiat and crypto * feat: add comment * feat: add right colors for responsive mode * feat: repair test * style: add some colors * feat: add active_wallet_loginid to client-store * style: add styles for CFD tab * feat: delete filter available_platforms for WalletOptionsAndMultipliersListing * feat: repair test * chore: empty commit to restart vercel * feat: add placeholder * feat: add one todo * chore: empty commit to restart vercel * feat: add is_switching and is_landing_company_loaded flags for placeholder * feat: delete check for is_landing_company_loaded * feat: implement suggestions * feat: add some properties to iseWalletList result * feat: refactor useWalletsList * feat: check for demo icon in useWalletList * feat: refactor progress-bar-onboarding * feat: add carousel and refactor all wallets * feat: comment currency_display_code * feat: change useWalletList hook again * feat: repair wallet-cards-carousel tests * feat: repair tests for wallet-header * feat: add tests for badge in wallet-header * feat: delete unuses type * style: create var for wallets box-shadow * feat: add useActiveWallet hook * feat: refactor code * refactor: some refactor of code * feat: delete using of useActiveWallet from 2 components * feat: add useActiveWallet for CFDsListing and OptionsAndMultipliersListing * feat: repair wallet-content test * feat: repair wallet-header test * feat: rewrite one test case for wallets component * refactor: delete unused import * style: add prominent color for Options & multipliers * chore: empty commit to restart vercel * feat: fix buttons issue * feat: fix logout issue for responsive * chore: delete comments * feat: return original onArrowClickHandler * feat: add needed properties in useWalletTransactions * feat: change currency to wallet_currency * feat: add wallet_currency to useWalletsList * fix: comment AddMoreWallets because it breaks PR when switch to Demo wallet * fix: add check for currencies in useAvailableWallets * feat: add ability to wallet buttons to open modal in responsive mode * refactor: change names for WalletContent props * feat: add open wallet modal for transfer button in CFD section of crypto wallet * feat: add select needed wallet card after redirect from cashier * Kill changes in package-lock * feat: add ts-expect-error to fix CircleCI error * fix: fix CircleCI test * feat: add open wallet modal for OptionsAndMultipliers components * fix: fix wallet-transfer-block test after modification * chore: comment scroll to choosed card * feat: delete autoselect wallet in responsive mode because cashier will be deleted * feat: fix some issues * feat: fix carousel issue * fix: fix tests for CircleCI * feat: delete is_added from useWalletsList * Farzin/Putting wallet behind feature flag [WIP] (#9362) * refactor(appstore): :fire: clean-up * refactor(appstore): :fire: clean-up * refactor(appstore): :fire: clean-up * refactor(appstore): :fire: clean-up * refactor(appstore): :fire: clean-up * refactor(appstore): :fire: clean-up * refactor(appstore): :fire: clean-up * refactor(appstore): :fire: clean-up * refactor(appstore): :fire: clean-up * refactor(appstore): :fire: clean-up * refactor(appstore): :fire: clean-up * refactor(appstore): :fire: clean-up * refactor(appstore): :fire: clean-up * refactor(appstore): :fire: clean-up * refactor(appstore): :fire: clean-up * refactor(appstore): :fire: clean-up --------- Co-authored-by: Farzin Mirzaie * fix: import issue in style (#9436) * thisyahlen/fix: wallet migration using mock server (#9161) * fix: mock config push * chore: mock server works boii * fix: comments and refactor * fix: remove unused import * fix: change refetch interval to 500ms * fix: refactor * fix: refactor v2 * fix: change back to migrated * fix: change to state * fix: test * fix: resolve comments * fix: removed async keyword (#9504) * fix: updated deriv/api-types in p2p (#9535) * fix: updated deriv/api-types in p2p * fix: added types for notification * fix: test cases * fix: circle ci issues pt.2 * bugfix: logout issue (#9466) * bugfix: logout issue * chore: fix code smell * refactor: move token check into the if scope * fix: ternary option for token * chore: reload build --------- Co-authored-by: Mahdiyeh Amirkhani * george / wall-1177 / Demo transfer insufficient funds error (#9211) * feat: add demo transfer error handlers, add useTransferBetweenAccounts hook * fix: types errors * fix: commit * feat: add transfer hooks * test: add tests * fix: commit * feat: :art: refactor code, add useActiveCFDAccounts hook * chore: remove comment * refactor: :fire: cleanup * fix: useAvaliableWallets hook * fix: typescript errors * test: add tests, apply comments * test: add test, add test accounts * test: fix tests * fix: fix ts errors * fix: apply comments * perf: small improvement * perf: small improvement * fix: resolve conflicts * refactor: improve logic, fix test * chore: remove unused import * feat: add reset balance button handler * fix: currency register * style: fix styles in dark mode * test: fix test * perf: minor improvements * refactor: :zap: transfer account data transfarmation layer * fix: minor fix * test: fix tests * fix: input error color * refactor: split accounts: trading_accounts and wallets * test: fix test * refactor: improve naming * fix: minor fix * feat: add useExistingCFDAccounts hook * test: fix tests * perf: replace useRequest with usefetch in useExistingCFDAccounts hook * test: fix test * style: fix selected tile style * refactor: apply comments * Aum/FEQ-503/proper-wallet-listing-logic-with-feature-flag (#9550) * feat: binding feature flag with API response to show or hide the wallets * fix: fixed wallet-card shine issue * fix: removed extra check for wallets list length in useHasWallet * refactor: moved logic for useHasWallet to useWalletsList * Aum/wall 1332/implementing the colors updating the components (#9492) * feat: added wallet-card colors for wallet-migration modal * fix: fixing the colors for the bitcoin gradient * implement migration banner visibility logic (#9552) * refactor: implement migration banner visibility logic * test: fix tests * refactor: move has_wallet check to useWalletsList hook * test: remove unused test * test: fix test type * refactor: remove button for test purposes * fix: missing icon * fix: type, sonarcloud issue (#9642) * fix: blinking wallet banner, double scroll (#9653) * fix: blinking wallet banner, double scroll * fix: blinking wallet banner with no wallets account * refactor: remove has_wallet, add is_migrated check * refactor: improve conditions * feat: add containScroll and clearTimeout (#9662) * fix: remove duplicated types * Merge branch 'master' into feature/wallets_with_traders_hub * Aizad-sergei/Fix: :feature/wallets with traders hub (#9755) * chore: initial commit * fix: fixed tests * chore: init commit * refactor: move WalletsImage component to separate file * refactor: move wallet_migration type, add TSDoc for it, change ?? to && for authorize checking * refactor: implement suggestions for packages/appstore/src/components/containers * refactor: add button styles to title * refactor: changed EUDisclaimer component * refactor: implement suggestions for packages/appstore/src/components/modals * refactor: reverted changes back * refactor: delete useCallback * refactor: wallet-add-card.spec.tsx * refactor: addmorewallets.tsx,addmorewallets.scss,addmorewallets.spec.tsx,walletaddcard.tsx * refactor: implement suggestions for packages/appstore/src/components * refactor: some more implemented suggestions * feat: optimize svgs for wallets * refactor: minified rest of svgs * refactor: add some more suggestions * refactor: complete with suggestions * feat: add check for stringified object * refactor: real-wallets-upgrade folder * chore: resolve comments from the big man himself: yashim * refactor: implement Maryia suggestions and use svgo for all icons * refactor: use svgo just for icons related to wallets * refactor: rename all variables to snake_case * refactor: optimize listing-container component --------- Co-authored-by: Mahdiyeh Amirkhani Co-authored-by: aizad-deriv Co-authored-by: Sergei Baranovski Co-authored-by: Aizad Ridzo <103104395+aizad-deriv@users.noreply.github.com> * fix: fix test * chore: update embla-carousel-react version to 8.0.0-rc12 in appstore package * fix: unused '@ts-expect-error' directive * chore: resolve comments * fix: conflicts * chore: initial commit (#9853) * chore: initial commit * refactor: refactor sonar cloud code smells * refactor: :recycle: remove unused and replace deprecated properties * refactor: extracted out function * refactor: change isMobile to is_mobile from store * refactor: revert useCurrencyConfig usage from api * fix: add imports for routes --------- Co-authored-by: Mahdiyeh Amirkhani Co-authored-by: arshad-rao-deriv * fix: remove wrong import * feat: modify progressBarTracker and renamed this component in CardsSliderSwiper (#9954) * Fix Review Comments Wallets Feature branch (#9998) * refactor: :recycle: extracted logic to a function in useWalletTransactions * refactor: remove code smells * refactor: :recycle: updated wrapper function name used in test * Fix feature/wallets with traders hub (#10041) * refactor: :recycle: extracted logic to a function in useWalletTransactions * refactor: remove code smells * refactor: :recycle: updated wrapper function name used in test * refactor: refactoed code, replaced localize with Localize component * refactor: formatted code * refactor: refactored code, used early return * george / resolve comments for FB1 (feature/wallets_with_traders_hub) (#10040) * fix: :recycle: resolve comments * refactor: :recycle: improve modal to show close cross button only * refactor: :recycle: refactor wallet upgrade props * fix: :bug: fix app loading issue related to can_get_more_cfd_mt5_accounts usage (replace with hook) * fix: modal component (#10148) * Rostislav / Wallets feature branch refactors (#10178) * refactor * refactor * refactor: code refactoring PR comments (#10180) * Arshad/feature/wallets with traders hub fixes (#10226) * refactor: code refactoring PR comments * refactor: refactored code review comments * fix: revert icon and content that got overwritten while merge in withdrawal-verification-email.tsx * feat: add conditions to show wallet migration notifications (#10280) * fix: top up modal height issue (#10279) * lubega/feq 750/fix: withdraw page spacing issue (#10302) * fix: :bug: Account selector alignment fix * fix: :bug: Withdraw page spacing issue * fix: 🐛 Withdraw page spacing issue * fix: 🐛 Withdraw page spacing issue * nijil/aum/FEQ-739/fix alignment issues for feature branch 1 (#10252) * fix: fixed height of pa deposit payment methods dropdown * fix: fixing tabs component for alignment issues * fix: password meter and hint alignment (#10285) * fix: fix test for min-max-stake (#10316) * fix: bump version of @deriv/deriv-api to 1.0.13 in reports package * Sergei / FEQ 741 / add prevent logout logic as for fb2 (#10282) * feat: add logic as for fb2 * fix: fix test for useWalletMigration * fix: reset MT5AccountTypeModal file to the one in master * fix: reset MT5AccountTypeModal file to the one in master * fix: add import for useState hook * Remove unnecessary modal min height param (#10355) * fix: Remove unnecessary modal min height param * fix: removed unnecessary prop * aum/WALL-1983/fix-description-font-size-in-empty-state (#10334) * fix: fixed font-size for empty-states description * refactor: changed the font-sizes * george / WALL-1985 / fix style issues (PA withdarwal, fiat withdrawals) (#10350) * style: :lipstick: fix style issues * test: :test_tube: add test for balance-text * chore: :recycle: fix naming convention for data testid * Merge branch 'feature/wallets_with_traders_hub' of https://github.com/binary-com/deriv-app into pa-aligment * refactor: :recycle: align with master * test: :test_tube: refactor test * style: :lipstick: add spacing * Aizad/WALL-1987/Seeing loading issue during DIEL account creation (#10391) * fix: remove console errors on onboarding page * fix: allow onboarding for diel * fix: resolve comments * Rostislav / WALL-1986 / Alignment issues in Transfer limit message (#10376) * fix: align * fix: appearance, but different * fix: appearance, but yet again different * refactor: a bunch more blank lines in css for readibility * Sergei / wall 1998 / inputs alignment (#10431) * style: change margin * style: add style for mobile * style: change bottom property to top * feat: add is_authorize check (#10466) * Rostislav / WALL-1986 (2) / Alignment issues in Transfer limit message (#10462) * fix: appearance align with production * refactor: better css * refactor: better css * Nijil/Comment out usage of useWalletList and useWalletMigration (#10480) * chore: comment code using useWalletList and useWalletMigration to reduce number of authorize calls * chore: add is_wallet_enabled check from useFeatureFlags hook for WalletModal in ModalManager * chore: consistent TODO comments across all commented out code * chore: Update package-lock * fix: failing test cases Co-authored-by: Sergei Baranovski --------- Co-authored-by: Sergei Baranovski * Rostislav / WALL-2011 / Fiat<->Fiat transfer percentage selector removed (#10511) * fix: mt5<->fiat no more percentage selector * fix: logic * Revert "Rostislav / WALL-2011 / Fiat<->Fiat transfer percentage selector removed (#10511)" (#10527) This reverts commit d61b94abce6d57cad6508344d24fb80c754c2648. * george / WALL-2020 / transfer modal height (#10532) * style: :lipstick: fix buttons group margin * style: :lipstick: align style with staging --------- Co-authored-by: Thisyahlen Nair Co-authored-by: Hamid Co-authored-by: Sergei Baranovski <120570511+sergei-deriv@users.noreply.github.com> Co-authored-by: George Usynin <103181646+heorhi-deriv@users.noreply.github.com> Co-authored-by: Farzin Mirzaie <72082844+farzin-deriv@users.noreply.github.com> Co-authored-by: Farzin Mirzaie Co-authored-by: mahdiyeh-deriv <82078941+mahdiyeh-deriv@users.noreply.github.com> Co-authored-by: aum-deriv <125039206+aum-deriv@users.noreply.github.com> Co-authored-by: Aizad Ridzo <103104395+aizad-deriv@users.noreply.github.com> Co-authored-by: Rostik Kayko <119863957+rostislav-deriv@users.noreply.github.com> Co-authored-by: Hamid Yaftian Co-authored-by: Sergei Baranovski Co-authored-by: thisyahlen <104053934+thisyahlen-deriv@users.noreply.github.com> Co-authored-by: yashim-deriv Co-authored-by: Dev Sans Co-authored-by: Mahdiyeh Amirkhani Co-authored-by: george-usynin-binary Co-authored-by: Niloofar Sadeghi <93518187+niloofar-deriv@users.noreply.github.com> Co-authored-by: niloofar sadeghi Co-authored-by: Jim Daniels Wasswa <104334373+jim-deriv@users.noreply.github.com> Co-authored-by: aizad-deriv Co-authored-by: arshad-rao-deriv Co-authored-by: Arshad Rao <135801848+arshad-rao-deriv@users.noreply.github.com> Co-authored-by: lubega-deriv <142860499+lubega-deriv@users.noreply.github.com> Co-authored-by: Sergei Baranovski --- package-lock.json | 58 +- .../Routes/__tests__/binary-routes.spec.tsx | 6 +- .../src/Components/Routes/binary-routes.tsx | 5 +- .../address-details/address-details.tsx | 4 +- .../__tests__/form-body-section.spec.tsx | 6 +- .../form-body-section/form-body-section.tsx | 5 +- .../__tests__/personal-details-form.spec.tsx | 2 + .../__tests__/confirmation-checkbox.spec.tsx | 4 +- .../confirmation-checkbox.tsx | 3 +- .../idv-doc-submit-on-signup.tsx | 6 +- .../__tests__/language-settings.spec.tsx | 2 +- .../proof-of-identity-submission.jsx | 3 +- .../__test__/proof-of-ownership-form.spec.js | 3 + packages/api/package.json | 2 +- packages/api/src/hooks/useResidenceList.ts | 1 + packages/api/src/hooks/useTransactions.ts | 2 + packages/api/src/index.ts | 4 +- packages/api/src/useAPI.ts | 4 +- packages/api/src/useInfiniteQuery.ts | 4 +- packages/api/src/usePaginatedFetch.ts | 6 +- packages/api/src/useQuery.ts | 4 +- packages/api/types.ts | 44 +- packages/appstore/package.json | 14 +- .../src/assets/svgs/wallets/how-it-works.svg | 1 + .../src/assets/svgs/wallets/image-types.tsx | 10 + .../src/assets/svgs/wallets/index.tsx | 3 + .../svgs/wallets/introducing-wallets-eu.svg | 1 + .../svgs/wallets/introducing-wallets.svg | 1 + .../wallets/ready_to_update_wallets_image.svg | 1 + .../ready_to_upgrade_wallets_image.svg | 1 + .../svgs/wallets/trading-accounts-eu.svg | 1 + .../assets/svgs/wallets/trading-accounts.svg | 1 + .../src/assets/svgs/wallets/wallets-image.tsx | 42 + .../svgs/wallets/wallets-ready-desktop-eu.svg | 1 + .../svgs/wallets/wallets-ready-desktop.svg | 1 + .../svgs/wallets/wallets-ready-mobile-eu.svg | 1 + .../svgs/wallets/wallets-ready-mobile.svg | 1 + .../svgs/wallets/wallets-upgrade-desktop.svg | 1 + .../svgs/wallets/wallets-upgrade-mobile.svg | 1 + .../__test__/add-more-wallets.spec.tsx | 136 +++ .../__test__/wallet-add-card.spec.tsx | 118 +++ .../add-more-wallets/add-more-wallets.scss | 91 ++ .../add-more-wallets/add-more-wallets.tsx | 33 + .../add-more-wallets/carousel-buttons.tsx | 20 + .../add-more-wallets/carousel-container.tsx | 56 ++ .../src/components/add-more-wallets/index.ts | 3 + .../add-more-wallets/wallet-add-card.tsx | 52 ++ .../appstore/src/components/app-content.tsx | 27 + packages/appstore/src/components/app.tsx | 51 +- .../cfds-listing/__tests__/index.spec.tsx | 3 +- .../src/components/cfds-listing/index.tsx | 6 +- .../containers/__tests__/wallets.spec.tsx | 74 ++ .../currency-switcher-container.tsx | 81 +- .../containers/listing-container.tsx | 75 +- .../containers/trading-app-card.tsx | 19 +- .../src/components/containers/wallet.scss | 44 + .../src/components/containers/wallet.tsx | 39 + .../__tests__/demo-reset-balance.spec.tsx | 135 +++ .../demo-reset-balance.scss | 20 + .../demo-reset-balance/demo-reset-balance.tsx | 82 ++ .../components/demo-reset-balance/index.ts | 3 + .../text/__tests__/balance-text.spec.tsx | 52 +- .../components/elements/text/balance-text.tsx | 4 +- .../__tests__/eu-disclaimer.spec.tsx | 63 ++ .../eu-disclaimer/eu-disclaimer.scss | 24 + .../eu-disclaimer/eu-disclaimer.tsx | 52 ++ .../src/components/eu-disclaimer/index.ts | 3 + .../main-title-bar/__tests__/index.spec.tsx | 91 +- .../src/components/main-title-bar/index.tsx | 26 +- .../src/components/modals/modal-manager.tsx | 11 +- .../__tests__/real-wallets-upgrade.spec.tsx | 41 + .../desktop-real-wallets-upgrade.tsx | 31 + .../components/modal-elements/index.ts | 4 + .../mobile-real-wallets-upgrade.tsx | 26 + .../modal-elements/wallet_steps.tsx | 49 ++ .../wallets-upgrade-content.tsx | 12 + .../modal-elements/wallets-upgrade-footer.tsx | 72 ++ .../ready-to-upgrade-wallets/index.ts | 3 + .../ready-to-upgrade-wallets.scss | 48 ++ .../ready-to-upgrade-wallets.tsx | 62 ++ .../test/ready-to-upgrade-wallets.spec.tsx | 93 ++ .../wallet-account/wallet-account.scss | 25 + .../wallet-account/wallet-account.tsx | 26 + .../wallet-link/wallet-link-wrapper.scss | 148 ++++ .../wallet-link/wallet-link-wrapper.tsx | 73 ++ .../wallet-linking-step.scss | 60 ++ .../wallet-linking-step.tsx | 67 ++ .../__tests__/wallets-intro.spec.tsx | 46 + .../components/wallets-intro/index.ts | 3 + .../wallets-intro/wallets-intro.scss | 54 ++ .../wallets-intro/wallets-intro.tsx | 92 ++ .../modals/real-wallets-upgrade/index.ts | 3 + .../real-wallets-upgrade.scss | 34 + .../real-wallets-upgrade.tsx | 60 ++ .../__tests__/wallet-modal-body.spec.tsx | 119 +++ .../__tests__/wallet-modal-header.spec.tsx | 79 ++ .../__tests__/wallet-modal.spec.tsx | 66 ++ .../components/modals/wallet-modal/index.ts | 4 + .../modals/wallet-modal/provider.tsx | 101 +++ .../modals/wallet-modal/wallet-modal-body.tsx | 103 +++ .../wallet-modal/wallet-modal-header.tsx | 102 +++ .../modals/wallet-modal/wallet-modal.scss | 218 +++++ .../modals/wallet-modal/wallet-modal.tsx | 80 ++ .../wallets-migration-failed.spec.tsx | 38 + .../modals/wallets-migration-failed/index.ts | 3 + .../wallets-migration-failed.scss | 29 + .../wallets-migration-failed.tsx | 65 ++ .../options-multipliers-listing/index.tsx | 27 +- .../src/components/routes/routes-wrapper.tsx | 16 - .../appstore/src/components/routes/routes.tsx | 52 +- .../src/components/transaction-list/index.ts | 3 + .../non-pending-transaction.tsx | 116 +++ .../transaction-list/transaction-for-day.tsx | 45 + .../transaction-list/transaction-list.scss | 99 +++ .../transaction-list/transaction-list.tsx | 77 ++ .../__tests__/wallet-button.spec.tsx | 62 ++ .../src/components/wallet-button/index.ts | 3 + .../wallet-button/wallet-button.scss | 79 ++ .../wallet-button/wallet-button.tsx | 56 ++ .../__tests__/wallet-cards-carousel.spec.tsx | 165 ++++ .../cards-slider-swiper.tsx | 86 ++ .../components/wallet-cards-carousel/index.ts | 3 + .../wallet-cards-carousel.scss | 36 + .../wallet-cards-carousel.tsx | 35 + .../__tests__/wallet-content-divider.spec.tsx | 20 + .../__tests__/wallet-content.spec.tsx | 82 ++ .../__tests__/wallet-transfer-block.spec.tsx | 56 ++ .../src/components/wallet-content/index.ts | 3 + .../wallet-content/wallet-cfds-listing.tsx | 253 ++++++ .../wallet-content/wallet-content-divider.tsx | 12 + .../wallet-content/wallet-content.scss | 75 ++ .../wallet-content/wallet-content.tsx | 35 + .../wallet-option-multipliers-listing.tsx | 131 +++ .../wallet-content/wallet-transfer-block.tsx | 50 ++ .../src/components/wallet-deposit/index.ts | 3 + .../wallet-deposit/wallet-deposit.scss | 7 + .../wallet-deposit/wallet-deposit.tsx | 29 + .../__tests__/wallet-header.spec.tsx | 326 +++++++ .../src/components/wallet-header/index.ts | 3 + .../wallet-header/wallet-currency-card.tsx | 25 + .../wallet-header/wallet-header-balance.tsx | 60 ++ .../wallet-header/wallet-header-buttons.tsx | 40 + .../wallet-header/wallet-header-title.tsx | 29 + .../wallet-header/wallet-header.scss | 91 ++ .../wallet-header/wallet-header.tsx | 81 ++ .../wallet-jurisdiction-badge.spec.tsx | 23 + .../wallet-jurisdiction-badge/index.ts | 3 + .../wallet-jurisdiction-badge.tsx | 18 + .../__tests__/wallet-transfer.spec.tsx | 47 + .../src/components/wallet-transfer/index.ts | 3 + .../__tests__/transfer-account-list.spec.tsx | 98 +++ .../transfer-account-selector.spec.tsx | 88 ++ .../transfer-account-selector/index.ts | 3 + .../transfer-account-list.tsx | 102 +++ .../transfer-account-selector.scss | 173 ++++ .../transfer-account-selector.tsx | 160 ++++ .../__tests__/wallet-transfer-tile.spec.tsx | 88 ++ .../wallet-transfer-tile/index.ts | 3 + .../wallet-transfer-tile.scss | 48 ++ .../wallet-transfer-tile.tsx | 123 +++ .../wallet-transfer/wallet-transfer.scss | 52 ++ .../wallet-transfer/wallet-transfer.tsx | 280 ++++++ .../__tests__/wallet-withdrawal.test.tsx | 36 + .../src/components/wallet-withdrawal/index.ts | 3 + .../wallet-withdrawal/wallet-withdrawal.tsx | 8 + .../__tests__/wallets-banner.spec.tsx | 222 +++++ .../src/components/wallets-banner/index.ts | 4 + .../wallets-banner/wallets-banner-ready.tsx | 59 ++ .../wallets-banner/wallets-banner-upgrade.tsx | 38 + .../wallets-banner-upgrading.tsx | 46 + .../wallets-banner/wallets-banner.scss | 188 ++++ .../wallets-banner/wallets-banner.tsx | 22 + .../mock_wallet_migration_response.ts | 213 +++++ .../appstore/src/constants/routes-config.ts | 45 - .../constants/upgrade-info-lists-config.tsx | 46 + packages/appstore/src/constants/utils.ts | 124 +++ .../src/constants/wallet-mocked-response.ts | 43 + .../constants/wallet_description_mapper.ts | 27 + .../wallets-intro-content-config.tsx | 46 + .../traders-hub/__tests__/index.spec.tsx | 7 +- .../src/modules/traders-hub/index.tsx | 6 +- .../modules/wallets/desktop-wallets-list.tsx | 18 + .../appstore/src/modules/wallets/index.ts | 1 + .../wallets/mobile-wallets-carousel.tsx | 56 ++ .../appstore/src/modules/wallets/wallets.scss | 25 + .../appstore/src/modules/wallets/wallets.tsx | 37 + .../images/wallet-header-demo-bg-dark.svg | 1 + .../public/images/wallet-header-demo-bg.svg | 1 + packages/appstore/src/services/websocket.ts | 7 - packages/appstore/src/stores/base-store.ts | 9 - packages/appstore/src/stores/config-store.ts | 48 -- packages/appstore/src/stores/index.ts | 18 +- packages/appstore/src/stores/root-store.ts | 23 - packages/appstore/src/types/api.types.ts | 6 - packages/appstore/src/types/common.types.ts | 74 ++ packages/appstore/src/types/index.ts | 5 - packages/appstore/src/types/params.types.ts | 30 - packages/appstore/src/types/props.types.ts | 6 - packages/appstore/src/types/stores.types.ts | 11 - packages/appstore/src/types/ws.types.ts | 2 - packages/appstore/webpack.config.js | 12 +- packages/bot-skeleton/package.json | 2 +- packages/bot-skeleton/src/scratch/dbot.js | 16 +- packages/bot-web-ui/package-lock.json | 2 +- .../dashboard/bot-builder/bot-builder.tsx | 3 + .../toolbar/__tests__/toolbar.spec.tsx | 22 +- .../dashboard/bot-builder/toolbar/toolbar.tsx | 10 +- .../__tests__/dashboard-component.spec.tsx | 2 + .../dashboard-component.tsx | 4 + .../dashboard-component/info-panel.tsx | 10 +- .../load-bot-preview/recent-workspace.tsx | 7 +- .../src/components/dashboard/dashboard.tsx | 4 + .../bot-builder-tour-desktop.tsx | 5 +- .../bot-builder-tour-mobile.tsx | 5 +- .../dbot-tours/bot-builder-tour/index.tsx | 2 + .../common/__tests__/accordion.spec.tsx | 2 + .../__tests__/react-joyride-wrapper.spec.tsx | 5 +- .../common/__tests__/tour-button.spec.tsx | 2 + .../dashboard/dbot-tours/common/accordion.tsx | 2 + .../common/react-joyride-wrapper.tsx | 1 + .../dbot-tours/common/tour-button.tsx | 1 + .../dbot-tours/common/tour-end-dialog.tsx | 2 + .../dbot-tours/common/tour-start-dialog.tsx | 3 + .../dbot-tours/common/tour-steps.tsx | 1 + .../config/__tests__/config.spec.tsx | 5 +- .../dashboard/dbot-tours/config/index.tsx | 4 +- .../dbot-tours/onboarding-tour/index.tsx | 2 + .../onboarding-tour-desktop.tsx | 5 +- .../onboarding-tour-mobile.tsx | 5 +- .../src/components/load-modal/local.tsx | 3 + .../src/components/run-panel/run-panel.tsx | 10 +- .../src/components/summary/summary-card.tsx | 3 + .../__tests__/transaction-details.spec.tsx | 33 +- .../transaction-details-mobile.tsx | 4 + .../transaction-details.tsx | 12 +- packages/bot-web-ui/src/constants/contract.js | 1 + .../bot-web-ui/src/stores/dashboard-store.ts | 4 + .../bot-web-ui/src/stores/download-store.js | 1 + .../bot-web-ui/src/stores/journal-store.js | 2 + .../src/stores/summary-card-store.js | 2 + packages/cashier/package.json | 2 +- .../crypto-fiat-converter.scss | 11 +- .../email-verification-empty-state.scss | 7 +- .../email-verification-empty-state.tsx | 2 +- .../src/containers/cashier/cashier.tsx | 10 +- .../cashier-onboarding-fiat-card.tsx | 2 +- .../account-transfer-form.scss | 35 +- .../payment-agent-list.scss | 3 + .../crypto-withdraw-form.tsx | 13 +- .../withdrawal-verification-email.test.tsx | 61 ++ .../withdrawal-verification-email.tsx | 4 +- packages/cfd/package.json | 2 +- .../cfd/src/Components/cfd-account-card.tsx | 31 +- packages/cfd/src/Components/specbox.tsx | 2 + .../Containers/compare-accounts-content.tsx | 7 +- .../cfd/src/Containers/dmt5-trade-modal.tsx | 17 +- .../cfd/src/Stores/Modules/CFD/cfd-store.js | 2 +- packages/cfd/src/Stores/base-store.js | 5 +- .../__tests__/amount-input.spec.tsx | 84 ++ .../components/amount-input/amount-input.scss | 51 ++ .../components/amount-input/amount-input.tsx | 151 ++++ .../src/components/amount-input/index.ts | 4 + .../app-linked-with-wallet-icon.scss | 56 ++ .../app-linked-with-wallet-icon.tsx | 65 ++ .../app-linked-with-wallet-icon/index.ts | 3 + .../components/badge/__tests__/badge.spec.tsx | 107 +++ .../src/components/badge/badge.scss | 40 + .../components/src/components/badge/badge.tsx | 96 +++ .../components/src/components/badge/index.ts | 4 + .../toggle-card-dialog.tsx | 19 +- .../src/components/date-picker/index.ts | 1 + .../div100vh-container/div100vh-container.tsx | 4 +- .../components/empty-state/empty-state.scss | 15 +- .../components/empty-state/empty-state.tsx | 26 +- .../gradient-background-two-point.scss | 25 + .../gradient-background-two-point.tsx | 31 + .../gradient-background-two-point/index.ts | 3 + .../gradient-background.scss | 80 ++ .../gradient-background.tsx | 28 + .../components/gradient-background/index.ts | 1 + .../icon/appstore/ic-appstore-tick-white.svg | 1 + .../icon/appstore/ic-appstore-tick.svg | 1 + .../appstore/ic-appstore-wallets-link.svg | 1 + .../cashier/ic-cashier-credit-debit-dark.svg | 1 + .../cashier/ic-cashier-credit-debit-light.svg | 1 + .../common/ic-demo-reset-balance-done.svg | 1 + .../icon/common/ic-demo-reset-balance.svg | 1 + .../ic-withdraw-request-verification-sent.svg | 1 + .../ic-withdraw-request-verification.svg | 1 + .../components/src/components/icon/icons.js | 70 +- .../rebranding/ic-rebranding-mt5-cfds.svg | 1 + .../ic-rebranding-mt5-derived-dashboard.svg | 1 + .../ic-rebranding-mt5-financial-dashboard.svg | 1 + .../ic-rebranding-mt5-swap-free.svg | 1 + .../icon/wallet/ic-wallet-bitcoin-dark.svg | 1 + .../icon/wallet/ic-wallet-bitcoin-light.svg | 1 + .../icon/wallet/ic-wallet-clear-funds.svg | 1 - .../wallet/ic-wallet-credit-debit-dark.svg | 1 - .../wallet/ic-wallet-credit-debit-light.svg | 1 - .../icon/wallet/ic-wallet-credit.svg | 1 - .../icon/wallet/ic-wallet-crypto-light.svg | 1 - .../icon/wallet/ic-wallet-currency-aud.svg | 1 + .../icon/wallet/ic-wallet-currency-eur.svg | 1 + .../icon/wallet/ic-wallet-currency-gbp.svg | 1 + .../icon/wallet/ic-wallet-currency-usd.svg | 1 + .../icon/wallet/ic-wallet-deriv-apps.svg | 2 +- .../icon/wallet/ic-wallet-deriv-demo-dark.svg | 1 + .../wallet/ic-wallet-deriv-demo-light.svg | 1 + .../icon/wallet/ic-wallet-deriv-p2p-dark.svg | 1 + .../icon/wallet/ic-wallet-deriv-p2p-light.svg | 1 + .../icon/wallet/ic-wallet-deriv.svg | 1 - .../icon/wallet/ic-wallet-dp2p-light.svg | 1 - .../ic-wallet-error-message-with-cross.svg | 1 + .../icon/wallet/ic-wallet-ethereum-dark.svg | 1 + .../icon/wallet/ic-wallet-ethereum-light.svg | 1 + .../icon/wallet/ic-wallet-explore.svg | 1 - .../icon/wallet/ic-wallet-fasapay-dark.svg | 1 - .../icon/wallet/ic-wallet-fasapay-light.svg | 1 - ...ic-wallet-info-message-with-three-dots.svg | 1 + .../icon/wallet/ic-wallet-jeton-dark.svg | 1 - .../icon/wallet/ic-wallet-jeton-light.svg | 1 - .../icon/wallet/ic-wallet-lite-coin-dark.svg | 1 + .../icon/wallet/ic-wallet-lite-coin-light.svg | 1 + .../icon/wallet/ic-wallet-markets.svg | 1 - .../wallet/ic-wallet-modal-tether-dark.svg | 1 + .../wallet/ic-wallet-modal-tether-light.svg | 1 + .../icon/wallet/ic-wallet-neteller-dark.svg | 1 - .../icon/wallet/ic-wallet-neteller-light.svg | 1 - .../icon/wallet/ic-wallet-neteller.svg | 1 - .../icon/wallet/ic-wallet-options-dark.svg | 1 + .../icon/wallet/ic-wallet-options-light.svg | 1 + .../wallet/ic-wallet-payment-agent-dark.svg | 1 + .../wallet/ic-wallet-payment-agent-light.svg | 2 +- .../icon/wallet/ic-wallet-paytrust-dark.svg | 1 - .../icon/wallet/ic-wallet-paytrust-light.svg | 1 - .../icon/wallet/ic-wallet-platforms.svg | 1 - .../icon/wallet/ic-wallet-skrill-dark.svg | 1 - .../icon/wallet/ic-wallet-skrill-light.svg | 1 - .../icon/wallet/ic-wallet-sticpay-dark.svg | 1 - .../icon/wallet/ic-wallet-sticpay-light.svg | 1 - .../icon/wallet/ic-wallet-sticpay.svg | 1 - .../icon/wallet/ic-wallet-success-message.svg | 1 + .../icon/wallet/ic-wallet-tether-dark.svg | 1 + .../icon/wallet/ic-wallet-tether-light.svg | 1 + .../icon/wallet/ic-wallet-trade-types.svg | 1 - .../wallet/ic-wallet-transactions-empty.svg | 1 - .../icon/wallet/ic-wallet-usd-coin-dark.svg | 1 + .../icon/wallet/ic-wallet-usd-coin-light.svg | 1 + .../icon/wallet/ic-wallet-wallets.svg | 1 - .../icon/wallet/ic-wallet-webmoney-light.svg | 1 - .../icon/wallet/ic-wallet-webmoney.svg | 1 - .../icon/wallet/ic-wallet-zingpay-dark.svg | 1 - .../icon/wallet/ic-wallet-zingpay-light.svg | 1 - .../components/src/components/input/input.tsx | 19 +- .../__tests__/alert-message.spec.tsx | 59 ++ .../__tests__/message-list.spec.tsx | 17 + .../components/message-list/alert-message.tsx | 66 ++ .../src/components/message-list/index.ts | 1 + .../components/message-list/message-list.scss | 53 ++ .../components/message-list/message-list.tsx | 54 ++ .../mobile-dialog/mobile-dialog.tsx | 20 +- .../components/src/components/modal/modal.tsx | 4 +- .../password-meter/password-meter.scss | 2 +- .../progress-bar-tracker.scss | 4 + .../progress-bar-tracker.tsx | 10 +- .../remaining-time/remaining-time.tsx | 4 +- .../src/components/wallet-card/index.ts | 3 + .../components/wallet-card/wallet-card.scss | 147 ++++ .../components/wallet-card/wallet-card.tsx | 113 +++ .../src/components/wallet-icon/index.ts | 3 + .../components/wallet-icon/wallet-icon.scss | 39 + .../components/wallet-icon/wallet-icon.tsx | 77 ++ packages/components/src/hooks/use-hover.ts | 1 + packages/components/src/index.ts | 8 + packages/components/stories/icon/icons.js | 70 +- packages/core/package.json | 2 +- .../Layout/Header/toggle-menu-drawer.jsx | 2 +- .../src/App/Components/dev-tools/index.tsx | 32 + .../App/Components/dev-tools/mock-dialog.scss | 65 ++ .../App/Components/dev-tools/mock-dialog.tsx | 163 ++++ .../account-signup-modal.jsx | 15 +- .../CitizenshipModal/set-citizenship-form.jsx | 3 +- .../Layout/header/default-mobile-links.tsx | 3 + .../Layout/header/dtrader-header.jsx | 6 + .../src/App/Containers/Modals/app-modals.jsx | 17 +- .../password-selection-modal.jsx | 7 +- .../__tests__/real-account-signup.spec.jsx | 4 +- .../RealAccountSignup/real-account-signup.jsx | 14 +- .../SetResidenceModal/set-residence-form.jsx | 3 +- .../SetResidenceModal/set-residence-modal.jsx | 10 +- .../Containers/app-notification-messages.jsx | 3 +- .../Stores/Helpers/client-notifications.js | 4 +- packages/core/src/Stores/base-store.js | 5 +- packages/core/src/Stores/client-store.js | 4 +- .../core/src/Stores/contract-trade-store.js | 8 +- .../core/src/Stores/notification-store.js | 57 +- packages/core/src/Stores/traders-hub-store.js | 31 +- packages/core/src/Stores/ui-store.js | 12 +- .../core/src/_common/base/api_middleware.js | 9 +- packages/core/src/_common/base/socket_base.js | 30 +- .../images/app/wallet/wallet-demo-bg-dark.svg | 1 + .../app/wallet/wallet-demo-bg-light.svg | 1 + packages/core/src/sass/app.scss | 1 + .../sass/app/_common/components/wallet.scss | 815 ++++++++++++++++++ .../_common/layout/traders-hub-header.scss | 3 +- .../src/__tests__/useActiveWallet.spec.tsx | 77 ++ .../hooks/src/__tests__/useAuthorize.spec.tsx | 40 + .../__tests__/useAvailableWallets.spec.tsx | 94 ++ .../src/__tests__/useContentFlag.spec.tsx | 90 ++ .../__tests__/useExistingCFDAccounts.spec.tsx | 173 ++++ .../__tests__/useLocalStorageData.spec.tsx | 65 ++ .../__tests__/usePlatformAccounts.spec.tsx | 8 +- .../useTransferBetweenAccounts.spec.tsx | 295 +++++++ .../src/__tests__/useWalletMigration.spec.tsx | 60 ++ .../__tests__/useWalletTransactions.spec.tsx | 150 ++++ .../src/__tests__/useWalletTransfer.spec.tsx | 252 ++++++ .../src/__tests__/useWalletsList.spec.tsx | 144 ++++ packages/hooks/src/index.ts | 13 +- packages/hooks/src/useActiveWallet.ts | 13 + packages/hooks/src/useAuthorize.ts | 26 + packages/hooks/src/useAvailableWallets.ts | 76 ++ packages/hooks/src/useContentFlag.ts | 17 + packages/hooks/src/useCurrencyConfig.ts | 3 +- packages/hooks/src/useExistingCFDAccounts.ts | 106 +++ packages/hooks/src/useLocalStorageData.ts | 29 + packages/hooks/src/usePlatformDemoAccount.ts | 5 +- packages/hooks/src/usePlatformRealAccounts.ts | 5 +- .../hooks/src/useTransferBetweenAccounts.ts | 144 ++++ packages/hooks/src/useWalletMigration.ts | 56 ++ packages/hooks/src/useWalletTransactions.ts | 336 ++++++++ packages/hooks/src/useWalletTransfer.ts | 54 ++ packages/hooks/src/useWalletsList.ts | 149 ++++ packages/p2p/crowdin/messages.json | 2 +- packages/p2p/src/components/app-content.jsx | 7 +- .../dp2p-blocked/dp2p-blocked-description.tsx | 2 + .../components/dp2p-blocked/dp2p-blocked.tsx | 3 + .../pages/advertiser-page/advertiser-page.jsx | 29 +- .../p2p/src/pages/buy-sell/buy-sell-row.jsx | 15 +- .../my-profile-name/my-profile-name.jsx | 14 +- packages/p2p/src/stores/general-store.js | 15 +- packages/p2p/src/stores/sendbird-store.ts | 14 +- packages/reports/package.json | 1 + packages/reports/src/Stores/base-store.js | 5 +- packages/shared/src/styles/constants.scss | 12 + packages/shared/src/styles/themes.scss | 30 + .../shared/src/utils/constants/contract.ts | 2 + packages/shared/src/utils/login/login.ts | 4 +- .../shared/src/utils/screen/responsive.ts | 1 + .../shared/src/utils/validator/validator.ts | 3 +- packages/stores/src/mockStore.ts | 33 +- .../stores/src/stores/FeatureFlagsStore.ts | 1 + packages/stores/types.ts | 163 +++- packages/trader/package.json | 2 +- ...count-verification-required-modal.spec.tsx | 23 +- ...company-wide-limit-exceeded-modal.spec.tsx | 10 + .../insufficient-balance-modal.spec.tsx | 10 + .../account-verification-required-modal.tsx | 66 +- .../company-wide-limit-exceeded-modal.tsx | 11 +- .../insufficient-balance-modal.tsx | 68 +- .../contract-type-description-video.spec.tsx | 9 + .../contract-type-description-video.tsx | 4 +- .../__tests__/purchase-button.spec.tsx | 4 +- .../__tests__/purchase-fieldset.spec.tsx | 6 +- .../ContractTypeInfo/contract-type-info.tsx | 16 +- .../__tests__/contract-type-info.spec.tsx | 9 + .../__tests__/contract-type-menu.spec.tsx | 10 + .../__tests__/contract-type-widget.spec.tsx | 16 + .../ContractType/contract-type-widget.tsx | 13 +- .../TradeParams/Multiplier/cancel-deal.jsx | 6 +- .../__tests__/barriers-list.spec.tsx | 30 +- .../__tests__/min-max-stake-info.spec.tsx | 8 +- .../Form/TradeParams/barriers-list-body.tsx | 106 ++- .../Form/TradeParams/min-max-stake-info.tsx | 11 +- .../Form/__tests__/form-layout.spec.tsx | 8 +- .../Form/__tests__/screen-large.spec.tsx | 2 + .../Containers/__tests__/purchase.spec.tsx | 6 +- .../trader/src/Utils/Validator/validator.ts | 1 + packages/utils/package.json | 12 +- .../src/__tests__/getLocalStorage.spec.ts | 47 + .../__tests__/getWalletCurrencyIcon.spec.ts | 95 ++ .../__tests__/groupTransactionsByDay.spec.ts | 33 + packages/utils/src/getLocalStorage.ts | 18 + packages/utils/src/getWalletCurrencyIcon.ts | 35 + packages/utils/src/groupTransactionsByDay.ts | 21 + packages/utils/src/index.ts | 3 + .../CreatePassword/CreatePassword.tsx | 3 + .../DesktopWalletsList/DesktopWalletsList.tsx | 3 + .../DxtradeEnterPasswordModal.tsx | 3 + .../JurisdictionModal/JurisdictionCard.tsx | 2 + .../JurisdictionModal/JurisdictionCardRow.tsx | 1 + .../JurisdictionModal/JurisdictionCardTag.tsx | 1 + .../MT5PasswordModal/MT5PasswordModal.tsx | 2 + .../components/ModalWrapper/ModalWrapper.tsx | 2 + .../src/components/WalletCard/WalletCard.tsx | 3 + .../WalletCardIcon/WalletCardIcon.tsx | 1 + .../WalletsAccordion/WalletsAccordion.tsx | 2 + 496 files changed, 15252 insertions(+), 1042 deletions(-) create mode 100644 packages/appstore/src/assets/svgs/wallets/how-it-works.svg create mode 100644 packages/appstore/src/assets/svgs/wallets/image-types.tsx create mode 100644 packages/appstore/src/assets/svgs/wallets/index.tsx create mode 100644 packages/appstore/src/assets/svgs/wallets/introducing-wallets-eu.svg create mode 100644 packages/appstore/src/assets/svgs/wallets/introducing-wallets.svg create mode 100644 packages/appstore/src/assets/svgs/wallets/ready_to_update_wallets_image.svg create mode 100644 packages/appstore/src/assets/svgs/wallets/ready_to_upgrade_wallets_image.svg create mode 100644 packages/appstore/src/assets/svgs/wallets/trading-accounts-eu.svg create mode 100644 packages/appstore/src/assets/svgs/wallets/trading-accounts.svg create mode 100644 packages/appstore/src/assets/svgs/wallets/wallets-image.tsx create mode 100644 packages/appstore/src/assets/svgs/wallets/wallets-ready-desktop-eu.svg create mode 100644 packages/appstore/src/assets/svgs/wallets/wallets-ready-desktop.svg create mode 100644 packages/appstore/src/assets/svgs/wallets/wallets-ready-mobile-eu.svg create mode 100644 packages/appstore/src/assets/svgs/wallets/wallets-ready-mobile.svg create mode 100644 packages/appstore/src/assets/svgs/wallets/wallets-upgrade-desktop.svg create mode 100644 packages/appstore/src/assets/svgs/wallets/wallets-upgrade-mobile.svg create mode 100644 packages/appstore/src/components/add-more-wallets/__test__/add-more-wallets.spec.tsx create mode 100644 packages/appstore/src/components/add-more-wallets/__test__/wallet-add-card.spec.tsx create mode 100644 packages/appstore/src/components/add-more-wallets/add-more-wallets.scss create mode 100644 packages/appstore/src/components/add-more-wallets/add-more-wallets.tsx create mode 100644 packages/appstore/src/components/add-more-wallets/carousel-buttons.tsx create mode 100644 packages/appstore/src/components/add-more-wallets/carousel-container.tsx create mode 100644 packages/appstore/src/components/add-more-wallets/index.ts create mode 100644 packages/appstore/src/components/add-more-wallets/wallet-add-card.tsx create mode 100644 packages/appstore/src/components/app-content.tsx create mode 100644 packages/appstore/src/components/containers/__tests__/wallets.spec.tsx create mode 100644 packages/appstore/src/components/containers/wallet.scss create mode 100644 packages/appstore/src/components/containers/wallet.tsx create mode 100644 packages/appstore/src/components/demo-reset-balance/__tests__/demo-reset-balance.spec.tsx create mode 100644 packages/appstore/src/components/demo-reset-balance/demo-reset-balance.scss create mode 100644 packages/appstore/src/components/demo-reset-balance/demo-reset-balance.tsx create mode 100644 packages/appstore/src/components/demo-reset-balance/index.ts create mode 100644 packages/appstore/src/components/eu-disclaimer/__tests__/eu-disclaimer.spec.tsx create mode 100644 packages/appstore/src/components/eu-disclaimer/eu-disclaimer.scss create mode 100644 packages/appstore/src/components/eu-disclaimer/eu-disclaimer.tsx create mode 100644 packages/appstore/src/components/eu-disclaimer/index.ts create mode 100644 packages/appstore/src/components/modals/real-wallets-upgrade/__tests__/real-wallets-upgrade.spec.tsx create mode 100644 packages/appstore/src/components/modals/real-wallets-upgrade/components/modal-elements/desktop-real-wallets-upgrade.tsx create mode 100644 packages/appstore/src/components/modals/real-wallets-upgrade/components/modal-elements/index.ts create mode 100644 packages/appstore/src/components/modals/real-wallets-upgrade/components/modal-elements/mobile-real-wallets-upgrade.tsx create mode 100644 packages/appstore/src/components/modals/real-wallets-upgrade/components/modal-elements/wallet_steps.tsx create mode 100644 packages/appstore/src/components/modals/real-wallets-upgrade/components/modal-elements/wallets-upgrade-content.tsx create mode 100644 packages/appstore/src/components/modals/real-wallets-upgrade/components/modal-elements/wallets-upgrade-footer.tsx create mode 100644 packages/appstore/src/components/modals/real-wallets-upgrade/components/ready-to-upgrade-wallets/index.ts create mode 100644 packages/appstore/src/components/modals/real-wallets-upgrade/components/ready-to-upgrade-wallets/ready-to-upgrade-wallets.scss create mode 100644 packages/appstore/src/components/modals/real-wallets-upgrade/components/ready-to-upgrade-wallets/ready-to-upgrade-wallets.tsx create mode 100644 packages/appstore/src/components/modals/real-wallets-upgrade/components/ready-to-upgrade-wallets/test/ready-to-upgrade-wallets.spec.tsx create mode 100644 packages/appstore/src/components/modals/real-wallets-upgrade/components/wallet-account/wallet-account.scss create mode 100644 packages/appstore/src/components/modals/real-wallets-upgrade/components/wallet-account/wallet-account.tsx create mode 100644 packages/appstore/src/components/modals/real-wallets-upgrade/components/wallet-link/wallet-link-wrapper.scss create mode 100644 packages/appstore/src/components/modals/real-wallets-upgrade/components/wallet-link/wallet-link-wrapper.tsx create mode 100644 packages/appstore/src/components/modals/real-wallets-upgrade/components/wallet-linking-step/wallet-linking-step.scss create mode 100644 packages/appstore/src/components/modals/real-wallets-upgrade/components/wallet-linking-step/wallet-linking-step.tsx create mode 100644 packages/appstore/src/components/modals/real-wallets-upgrade/components/wallets-intro/__tests__/wallets-intro.spec.tsx create mode 100644 packages/appstore/src/components/modals/real-wallets-upgrade/components/wallets-intro/index.ts create mode 100644 packages/appstore/src/components/modals/real-wallets-upgrade/components/wallets-intro/wallets-intro.scss create mode 100644 packages/appstore/src/components/modals/real-wallets-upgrade/components/wallets-intro/wallets-intro.tsx create mode 100644 packages/appstore/src/components/modals/real-wallets-upgrade/index.ts create mode 100644 packages/appstore/src/components/modals/real-wallets-upgrade/real-wallets-upgrade.scss create mode 100644 packages/appstore/src/components/modals/real-wallets-upgrade/real-wallets-upgrade.tsx create mode 100644 packages/appstore/src/components/modals/wallet-modal/__tests__/wallet-modal-body.spec.tsx create mode 100644 packages/appstore/src/components/modals/wallet-modal/__tests__/wallet-modal-header.spec.tsx create mode 100644 packages/appstore/src/components/modals/wallet-modal/__tests__/wallet-modal.spec.tsx create mode 100644 packages/appstore/src/components/modals/wallet-modal/index.ts create mode 100644 packages/appstore/src/components/modals/wallet-modal/provider.tsx create mode 100644 packages/appstore/src/components/modals/wallet-modal/wallet-modal-body.tsx create mode 100644 packages/appstore/src/components/modals/wallet-modal/wallet-modal-header.tsx create mode 100644 packages/appstore/src/components/modals/wallet-modal/wallet-modal.scss create mode 100644 packages/appstore/src/components/modals/wallet-modal/wallet-modal.tsx create mode 100644 packages/appstore/src/components/modals/wallets-migration-failed/__tests__/wallets-migration-failed.spec.tsx create mode 100644 packages/appstore/src/components/modals/wallets-migration-failed/index.ts create mode 100644 packages/appstore/src/components/modals/wallets-migration-failed/wallets-migration-failed.scss create mode 100644 packages/appstore/src/components/modals/wallets-migration-failed/wallets-migration-failed.tsx delete mode 100644 packages/appstore/src/components/routes/routes-wrapper.tsx create mode 100644 packages/appstore/src/components/transaction-list/index.ts create mode 100644 packages/appstore/src/components/transaction-list/non-pending-transaction.tsx create mode 100644 packages/appstore/src/components/transaction-list/transaction-for-day.tsx create mode 100644 packages/appstore/src/components/transaction-list/transaction-list.scss create mode 100644 packages/appstore/src/components/transaction-list/transaction-list.tsx create mode 100644 packages/appstore/src/components/wallet-button/__tests__/wallet-button.spec.tsx create mode 100644 packages/appstore/src/components/wallet-button/index.ts create mode 100644 packages/appstore/src/components/wallet-button/wallet-button.scss create mode 100644 packages/appstore/src/components/wallet-button/wallet-button.tsx create mode 100644 packages/appstore/src/components/wallet-cards-carousel/__tests__/wallet-cards-carousel.spec.tsx create mode 100644 packages/appstore/src/components/wallet-cards-carousel/cards-slider-swiper.tsx create mode 100644 packages/appstore/src/components/wallet-cards-carousel/index.ts create mode 100644 packages/appstore/src/components/wallet-cards-carousel/wallet-cards-carousel.scss create mode 100644 packages/appstore/src/components/wallet-cards-carousel/wallet-cards-carousel.tsx create mode 100644 packages/appstore/src/components/wallet-content/__tests__/wallet-content-divider.spec.tsx create mode 100644 packages/appstore/src/components/wallet-content/__tests__/wallet-content.spec.tsx create mode 100644 packages/appstore/src/components/wallet-content/__tests__/wallet-transfer-block.spec.tsx create mode 100644 packages/appstore/src/components/wallet-content/index.ts create mode 100644 packages/appstore/src/components/wallet-content/wallet-cfds-listing.tsx create mode 100644 packages/appstore/src/components/wallet-content/wallet-content-divider.tsx create mode 100644 packages/appstore/src/components/wallet-content/wallet-content.scss create mode 100644 packages/appstore/src/components/wallet-content/wallet-content.tsx create mode 100644 packages/appstore/src/components/wallet-content/wallet-option-multipliers-listing.tsx create mode 100644 packages/appstore/src/components/wallet-content/wallet-transfer-block.tsx create mode 100644 packages/appstore/src/components/wallet-deposit/index.ts create mode 100644 packages/appstore/src/components/wallet-deposit/wallet-deposit.scss create mode 100644 packages/appstore/src/components/wallet-deposit/wallet-deposit.tsx create mode 100644 packages/appstore/src/components/wallet-header/__tests__/wallet-header.spec.tsx create mode 100644 packages/appstore/src/components/wallet-header/index.ts create mode 100644 packages/appstore/src/components/wallet-header/wallet-currency-card.tsx create mode 100644 packages/appstore/src/components/wallet-header/wallet-header-balance.tsx create mode 100644 packages/appstore/src/components/wallet-header/wallet-header-buttons.tsx create mode 100644 packages/appstore/src/components/wallet-header/wallet-header-title.tsx create mode 100644 packages/appstore/src/components/wallet-header/wallet-header.scss create mode 100644 packages/appstore/src/components/wallet-header/wallet-header.tsx create mode 100644 packages/appstore/src/components/wallet-jurisdiction-badge/__tests__/wallet-jurisdiction-badge.spec.tsx create mode 100644 packages/appstore/src/components/wallet-jurisdiction-badge/index.ts create mode 100644 packages/appstore/src/components/wallet-jurisdiction-badge/wallet-jurisdiction-badge.tsx create mode 100644 packages/appstore/src/components/wallet-transfer/__tests__/wallet-transfer.spec.tsx create mode 100644 packages/appstore/src/components/wallet-transfer/index.ts create mode 100644 packages/appstore/src/components/wallet-transfer/transfer-account-selector/__tests__/transfer-account-list.spec.tsx create mode 100644 packages/appstore/src/components/wallet-transfer/transfer-account-selector/__tests__/transfer-account-selector.spec.tsx create mode 100644 packages/appstore/src/components/wallet-transfer/transfer-account-selector/index.ts create mode 100644 packages/appstore/src/components/wallet-transfer/transfer-account-selector/transfer-account-list.tsx create mode 100644 packages/appstore/src/components/wallet-transfer/transfer-account-selector/transfer-account-selector.scss create mode 100644 packages/appstore/src/components/wallet-transfer/transfer-account-selector/transfer-account-selector.tsx create mode 100644 packages/appstore/src/components/wallet-transfer/wallet-transfer-tile/__tests__/wallet-transfer-tile.spec.tsx create mode 100644 packages/appstore/src/components/wallet-transfer/wallet-transfer-tile/index.ts create mode 100644 packages/appstore/src/components/wallet-transfer/wallet-transfer-tile/wallet-transfer-tile.scss create mode 100644 packages/appstore/src/components/wallet-transfer/wallet-transfer-tile/wallet-transfer-tile.tsx create mode 100644 packages/appstore/src/components/wallet-transfer/wallet-transfer.scss create mode 100644 packages/appstore/src/components/wallet-transfer/wallet-transfer.tsx create mode 100644 packages/appstore/src/components/wallet-withdrawal/__tests__/wallet-withdrawal.test.tsx create mode 100644 packages/appstore/src/components/wallet-withdrawal/index.ts create mode 100644 packages/appstore/src/components/wallet-withdrawal/wallet-withdrawal.tsx create mode 100644 packages/appstore/src/components/wallets-banner/__tests__/wallets-banner.spec.tsx create mode 100644 packages/appstore/src/components/wallets-banner/index.ts create mode 100644 packages/appstore/src/components/wallets-banner/wallets-banner-ready.tsx create mode 100644 packages/appstore/src/components/wallets-banner/wallets-banner-upgrade.tsx create mode 100644 packages/appstore/src/components/wallets-banner/wallets-banner-upgrading.tsx create mode 100644 packages/appstore/src/components/wallets-banner/wallets-banner.scss create mode 100644 packages/appstore/src/components/wallets-banner/wallets-banner.tsx create mode 100644 packages/appstore/src/constants/mock_wallet_migration_response.ts delete mode 100644 packages/appstore/src/constants/routes-config.ts create mode 100644 packages/appstore/src/constants/upgrade-info-lists-config.tsx create mode 100644 packages/appstore/src/constants/wallet-mocked-response.ts create mode 100644 packages/appstore/src/constants/wallet_description_mapper.ts create mode 100644 packages/appstore/src/constants/wallets-intro-content-config.tsx create mode 100644 packages/appstore/src/modules/wallets/desktop-wallets-list.tsx create mode 100644 packages/appstore/src/modules/wallets/index.ts create mode 100644 packages/appstore/src/modules/wallets/mobile-wallets-carousel.tsx create mode 100644 packages/appstore/src/modules/wallets/wallets.scss create mode 100644 packages/appstore/src/modules/wallets/wallets.tsx create mode 100644 packages/appstore/src/public/images/wallet-header-demo-bg-dark.svg create mode 100644 packages/appstore/src/public/images/wallet-header-demo-bg.svg delete mode 100644 packages/appstore/src/services/websocket.ts delete mode 100644 packages/appstore/src/stores/base-store.ts delete mode 100644 packages/appstore/src/stores/config-store.ts delete mode 100644 packages/appstore/src/stores/root-store.ts delete mode 100644 packages/appstore/src/types/api.types.ts delete mode 100644 packages/appstore/src/types/params.types.ts delete mode 100644 packages/appstore/src/types/props.types.ts delete mode 100644 packages/appstore/src/types/stores.types.ts delete mode 100644 packages/appstore/src/types/ws.types.ts create mode 100644 packages/cashier/src/pages/withdrawal/withdrawal-verification-email/__tests__/withdrawal-verification-email.test.tsx create mode 100644 packages/components/src/components/amount-input/__tests__/amount-input.spec.tsx create mode 100644 packages/components/src/components/amount-input/amount-input.scss create mode 100644 packages/components/src/components/amount-input/amount-input.tsx create mode 100644 packages/components/src/components/amount-input/index.ts create mode 100644 packages/components/src/components/app-linked-with-wallet-icon/app-linked-with-wallet-icon.scss create mode 100644 packages/components/src/components/app-linked-with-wallet-icon/app-linked-with-wallet-icon.tsx create mode 100644 packages/components/src/components/app-linked-with-wallet-icon/index.ts create mode 100644 packages/components/src/components/badge/__tests__/badge.spec.tsx create mode 100644 packages/components/src/components/badge/badge.scss create mode 100644 packages/components/src/components/badge/badge.tsx create mode 100644 packages/components/src/components/badge/index.ts create mode 100644 packages/components/src/components/gradient-background-two-point/gradient-background-two-point.scss create mode 100644 packages/components/src/components/gradient-background-two-point/gradient-background-two-point.tsx create mode 100644 packages/components/src/components/gradient-background-two-point/index.ts create mode 100644 packages/components/src/components/gradient-background/gradient-background.scss create mode 100644 packages/components/src/components/gradient-background/gradient-background.tsx create mode 100644 packages/components/src/components/gradient-background/index.ts create mode 100644 packages/components/src/components/icon/appstore/ic-appstore-tick-white.svg create mode 100644 packages/components/src/components/icon/appstore/ic-appstore-tick.svg create mode 100644 packages/components/src/components/icon/appstore/ic-appstore-wallets-link.svg create mode 100644 packages/components/src/components/icon/cashier/ic-cashier-credit-debit-dark.svg create mode 100644 packages/components/src/components/icon/cashier/ic-cashier-credit-debit-light.svg create mode 100644 packages/components/src/components/icon/common/ic-demo-reset-balance-done.svg create mode 100644 packages/components/src/components/icon/common/ic-demo-reset-balance.svg create mode 100644 packages/components/src/components/icon/common/ic-withdraw-request-verification-sent.svg create mode 100644 packages/components/src/components/icon/common/ic-withdraw-request-verification.svg create mode 100644 packages/components/src/components/icon/rebranding/ic-rebranding-mt5-cfds.svg create mode 100644 packages/components/src/components/icon/rebranding/ic-rebranding-mt5-derived-dashboard.svg create mode 100644 packages/components/src/components/icon/rebranding/ic-rebranding-mt5-financial-dashboard.svg create mode 100644 packages/components/src/components/icon/rebranding/ic-rebranding-mt5-swap-free.svg create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-bitcoin-dark.svg create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-bitcoin-light.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-clear-funds.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-credit-debit-dark.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-credit-debit-light.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-credit.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-crypto-light.svg create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-currency-aud.svg create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-currency-eur.svg create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-currency-gbp.svg create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-currency-usd.svg create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-deriv-demo-dark.svg create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-deriv-demo-light.svg create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-deriv-p2p-dark.svg create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-deriv-p2p-light.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-deriv.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-dp2p-light.svg create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-error-message-with-cross.svg create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-ethereum-dark.svg create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-ethereum-light.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-explore.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-fasapay-dark.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-fasapay-light.svg create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-info-message-with-three-dots.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-jeton-dark.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-jeton-light.svg create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-lite-coin-dark.svg create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-lite-coin-light.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-markets.svg create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-modal-tether-dark.svg create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-modal-tether-light.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-neteller-dark.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-neteller-light.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-neteller.svg create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-options-dark.svg create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-options-light.svg create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-payment-agent-dark.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-paytrust-dark.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-paytrust-light.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-platforms.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-skrill-dark.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-skrill-light.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-sticpay-dark.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-sticpay-light.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-sticpay.svg create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-success-message.svg create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-tether-dark.svg create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-tether-light.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-trade-types.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-transactions-empty.svg create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-usd-coin-dark.svg create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-usd-coin-light.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-wallets.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-webmoney-light.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-webmoney.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-zingpay-dark.svg delete mode 100644 packages/components/src/components/icon/wallet/ic-wallet-zingpay-light.svg create mode 100644 packages/components/src/components/message-list/__tests__/alert-message.spec.tsx create mode 100644 packages/components/src/components/message-list/__tests__/message-list.spec.tsx create mode 100644 packages/components/src/components/message-list/alert-message.tsx create mode 100644 packages/components/src/components/message-list/index.ts create mode 100644 packages/components/src/components/message-list/message-list.scss create mode 100644 packages/components/src/components/message-list/message-list.tsx create mode 100644 packages/components/src/components/wallet-card/index.ts create mode 100644 packages/components/src/components/wallet-card/wallet-card.scss create mode 100644 packages/components/src/components/wallet-card/wallet-card.tsx create mode 100644 packages/components/src/components/wallet-icon/index.ts create mode 100644 packages/components/src/components/wallet-icon/wallet-icon.scss create mode 100644 packages/components/src/components/wallet-icon/wallet-icon.tsx create mode 100644 packages/core/src/App/Components/dev-tools/index.tsx create mode 100644 packages/core/src/App/Components/dev-tools/mock-dialog.scss create mode 100644 packages/core/src/App/Components/dev-tools/mock-dialog.tsx create mode 100644 packages/core/src/public/images/app/wallet/wallet-demo-bg-dark.svg create mode 100644 packages/core/src/public/images/app/wallet/wallet-demo-bg-light.svg create mode 100644 packages/core/src/sass/app/_common/components/wallet.scss create mode 100644 packages/hooks/src/__tests__/useActiveWallet.spec.tsx create mode 100644 packages/hooks/src/__tests__/useAuthorize.spec.tsx create mode 100644 packages/hooks/src/__tests__/useAvailableWallets.spec.tsx create mode 100644 packages/hooks/src/__tests__/useContentFlag.spec.tsx create mode 100644 packages/hooks/src/__tests__/useExistingCFDAccounts.spec.tsx create mode 100644 packages/hooks/src/__tests__/useLocalStorageData.spec.tsx create mode 100644 packages/hooks/src/__tests__/useTransferBetweenAccounts.spec.tsx create mode 100644 packages/hooks/src/__tests__/useWalletMigration.spec.tsx create mode 100644 packages/hooks/src/__tests__/useWalletTransactions.spec.tsx create mode 100644 packages/hooks/src/__tests__/useWalletTransfer.spec.tsx create mode 100644 packages/hooks/src/__tests__/useWalletsList.spec.tsx create mode 100644 packages/hooks/src/useActiveWallet.ts create mode 100644 packages/hooks/src/useAuthorize.ts create mode 100644 packages/hooks/src/useAvailableWallets.ts create mode 100644 packages/hooks/src/useContentFlag.ts create mode 100644 packages/hooks/src/useExistingCFDAccounts.ts create mode 100644 packages/hooks/src/useLocalStorageData.ts create mode 100644 packages/hooks/src/useTransferBetweenAccounts.ts create mode 100644 packages/hooks/src/useWalletMigration.ts create mode 100644 packages/hooks/src/useWalletTransactions.ts create mode 100644 packages/hooks/src/useWalletTransfer.ts create mode 100644 packages/hooks/src/useWalletsList.ts create mode 100644 packages/utils/src/__tests__/getLocalStorage.spec.ts create mode 100644 packages/utils/src/__tests__/getWalletCurrencyIcon.spec.ts create mode 100644 packages/utils/src/__tests__/groupTransactionsByDay.spec.ts create mode 100644 packages/utils/src/getLocalStorage.ts create mode 100644 packages/utils/src/getWalletCurrencyIcon.ts create mode 100644 packages/utils/src/groupTransactionsByDay.ts diff --git a/package-lock.json b/package-lock.json index 1a07aa63f95c..ed3a3131d945 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@datadog/browser-logs": "^4.36.0", "@datadog/browser-rum": "^4.37.0", "@deriv/api-types": "^1.0.118", - "@deriv/deriv-api": "^1.0.11", + "@deriv/deriv-api": "^1.0.13", "@deriv/deriv-charts": "1.4.0", "@deriv/js-interpreter": "^3.0.0", "@deriv/ui": "^0.6.0", @@ -39,7 +39,9 @@ "@types/jsdom": "^20.0.0", "@types/loadjs": "^4.0.1", "@types/lodash.debounce": "^4.0.7", + "@types/lodash.groupby": "^4.6.7", "@types/lodash.merge": "^4.6.7", + "@types/lodash.pickby": "^4.6.7", "@types/lodash.throttle": "^4.1.7", "@types/object.fromentries": "^2.0.0", "@types/qrcode.react": "^1.0.2", @@ -110,7 +112,9 @@ "loadjs": "^4.2.0", "localforage": "^1.9.0", "lodash.debounce": "^4.0.8", + "lodash.groupby": "^4.6.0", "lodash.merge": "^4.6.2", + "lodash.pickby": "^4.6.0", "lodash.throttle": "^4.1.1", "lz-string": "^1.4.4", "mini-css-extract-plugin": "^1.3.4", @@ -16113,6 +16117,14 @@ "@types/lodash": "*" } }, + "node_modules/@types/lodash.groupby": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/@types/lodash.groupby/-/lodash.groupby-4.6.7.tgz", + "integrity": "sha512-dFUR1pqdMgjIBbgPJ/8axJX6M1C7zsL+HF4qdYMQeJ7XOp0Qbf37I3zh9gpXr/ks6tgEYPDRqyZRAnFYvewYHQ==", + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/lodash.merge": { "version": "4.6.7", "resolved": "https://registry.npmjs.org/@types/lodash.merge/-/lodash.merge-4.6.7.tgz", @@ -16121,6 +16133,14 @@ "@types/lodash": "*" } }, + "node_modules/@types/lodash.pickby": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/@types/lodash.pickby/-/lodash.pickby-4.6.7.tgz", + "integrity": "sha512-4ebXRusuLflfscbD0PUX4eVknDHD9Yf+uMtBIvA/hrnTqeAzbuHuDjvnYriLjUrI9YrhCPVKUf4wkRSXJQ6gig==", + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/lodash.throttle": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/lodash.throttle/-/lodash.throttle-4.1.7.tgz", @@ -34350,6 +34370,11 @@ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "peer": true }, + "node_modules/lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==" + }, "node_modules/lodash.invokemap": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.invokemap/-/lodash.invokemap-4.6.0.tgz", @@ -34393,6 +34418,11 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.pickby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.6.0.tgz", + "integrity": "sha512-AZV+GsS/6ckvPOVQPXSiFFacKvKB4kOQu6ynt9wz0F3LO4R9Ij4K1ddYsIytDpSgLz88JHd9P+oaLeej5/Sl7Q==" + }, "node_modules/lodash.pullall": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.pullall/-/lodash.pullall-4.2.0.tgz", @@ -61280,6 +61310,14 @@ "@types/lodash": "*" } }, + "@types/lodash.groupby": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/@types/lodash.groupby/-/lodash.groupby-4.6.7.tgz", + "integrity": "sha512-dFUR1pqdMgjIBbgPJ/8axJX6M1C7zsL+HF4qdYMQeJ7XOp0Qbf37I3zh9gpXr/ks6tgEYPDRqyZRAnFYvewYHQ==", + "requires": { + "@types/lodash": "*" + } + }, "@types/lodash.merge": { "version": "4.6.7", "resolved": "https://registry.npmjs.org/@types/lodash.merge/-/lodash.merge-4.6.7.tgz", @@ -61288,6 +61326,14 @@ "@types/lodash": "*" } }, + "@types/lodash.pickby": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/@types/lodash.pickby/-/lodash.pickby-4.6.7.tgz", + "integrity": "sha512-4ebXRusuLflfscbD0PUX4eVknDHD9Yf+uMtBIvA/hrnTqeAzbuHuDjvnYriLjUrI9YrhCPVKUf4wkRSXJQ6gig==", + "requires": { + "@types/lodash": "*" + } + }, "@types/lodash.throttle": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/lodash.throttle/-/lodash.throttle-4.1.7.tgz", @@ -74282,6 +74328,11 @@ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "peer": true }, + "lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==" + }, "lodash.invokemap": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.invokemap/-/lodash.invokemap-4.6.0.tgz", @@ -74319,6 +74370,11 @@ "version": "4.6.2", "dev": true }, + "lodash.pickby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.6.0.tgz", + "integrity": "sha512-AZV+GsS/6ckvPOVQPXSiFFacKvKB4kOQu6ynt9wz0F3LO4R9Ij4K1ddYsIytDpSgLz88JHd9P+oaLeej5/Sl7Q==" + }, "lodash.pullall": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.pullall/-/lodash.pullall-4.2.0.tgz", diff --git a/packages/account/src/Components/Routes/__tests__/binary-routes.spec.tsx b/packages/account/src/Components/Routes/__tests__/binary-routes.spec.tsx index 022c108082f6..4e04aeb3261e 100644 --- a/packages/account/src/Components/Routes/__tests__/binary-routes.spec.tsx +++ b/packages/account/src/Components/Routes/__tests__/binary-routes.spec.tsx @@ -1,9 +1,11 @@ import React from 'react'; import { Router } from 'react-router-dom'; import { createBrowserHistory } from 'history'; -import { render, screen } from '@testing-library/react'; + import { PlatformContext } from '@deriv/shared'; -import { StoreProvider, mockStore } from '@deriv/stores'; +import { mockStore, StoreProvider } from '@deriv/stores'; +import { render, screen } from '@testing-library/react'; + import BinaryRoutes from '../binary-routes'; jest.mock('../route-with-sub-routes', () => jest.fn(() =>
RouteWithSubRoutes
)); diff --git a/packages/account/src/Components/Routes/binary-routes.tsx b/packages/account/src/Components/Routes/binary-routes.tsx index de884830b996..6e361da601f1 100644 --- a/packages/account/src/Components/Routes/binary-routes.tsx +++ b/packages/account/src/Components/Routes/binary-routes.tsx @@ -1,9 +1,12 @@ import React from 'react'; import { Switch } from 'react-router-dom'; -import { Localize } from '@deriv/translations'; + import { observer, useStore } from '@deriv/stores'; +import { Localize } from '@deriv/translations'; + import getRoutesConfig from '../../Constants/routes-config'; import { TBinaryRoutes, TRoute } from '../../Types'; + import RouteWithSubRoutes from './route-with-sub-routes'; const BinaryRoutes = observer((props: TBinaryRoutes) => { diff --git a/packages/account/src/Components/address-details/address-details.tsx b/packages/account/src/Components/address-details/address-details.tsx index ccc23484d607..07a71265283a 100644 --- a/packages/account/src/Components/address-details/address-details.tsx +++ b/packages/account/src/Components/address-details/address-details.tsx @@ -12,7 +12,6 @@ import { } from 'formik'; import { StatesList } from '@deriv/api-types'; import { - Modal, Autocomplete, AutoHeightWrapper, DesktopWrapper, @@ -20,9 +19,10 @@ import { FormSubmitButton, Loading, MobileWrapper, - ThemedScrollbars, + Modal, SelectNative, Text, + ThemedScrollbars, } from '@deriv/components'; import { useStatesList } from '@deriv/hooks'; import { getLocation } from '@deriv/shared'; diff --git a/packages/account/src/Components/form-body-section/__tests__/form-body-section.spec.tsx b/packages/account/src/Components/form-body-section/__tests__/form-body-section.spec.tsx index 662843ee5c84..40bcbdcbc9c7 100644 --- a/packages/account/src/Components/form-body-section/__tests__/form-body-section.spec.tsx +++ b/packages/account/src/Components/form-body-section/__tests__/form-body-section.spec.tsx @@ -1,7 +1,9 @@ import React from 'react'; -import { screen, render } from '@testing-library/react'; + +import { mockStore, StoreProvider } from '@deriv/stores'; +import { render, screen } from '@testing-library/react'; + import FormBodySection, { TFormBodySection } from '../form-body-section'; -import { StoreProvider, mockStore } from '@deriv/stores'; const MockFormBodySection = (props: TFormBodySection) => { const mock_store = mockStore({}); diff --git a/packages/account/src/Components/form-body-section/form-body-section.tsx b/packages/account/src/Components/form-body-section/form-body-section.tsx index 7f41228e1d76..5d7b06784184 100644 --- a/packages/account/src/Components/form-body-section/form-body-section.tsx +++ b/packages/account/src/Components/form-body-section/form-body-section.tsx @@ -1,8 +1,9 @@ +import React from 'react'; +import classNames from 'classnames'; + import { Text } from '@deriv/components'; import { isMobile } from '@deriv/shared'; import { Localize } from '@deriv/translations'; -import classNames from 'classnames'; -import React from 'react'; export type TFormBodySection = { /** diff --git a/packages/account/src/Components/forms/__tests__/personal-details-form.spec.tsx b/packages/account/src/Components/forms/__tests__/personal-details-form.spec.tsx index c0d94f69c46e..cffedda0aa1d 100644 --- a/packages/account/src/Components/forms/__tests__/personal-details-form.spec.tsx +++ b/packages/account/src/Components/forms/__tests__/personal-details-form.spec.tsx @@ -1,7 +1,9 @@ import React from 'react'; import { Formik } from 'formik'; + import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; + import PersonalDetailsForm from '../personal-details-form'; jest.mock('react-router-dom', () => ({ diff --git a/packages/account/src/Components/forms/confirmation-checkbox/__tests__/confirmation-checkbox.spec.tsx b/packages/account/src/Components/forms/confirmation-checkbox/__tests__/confirmation-checkbox.spec.tsx index c9131afc2773..4601c86759be 100644 --- a/packages/account/src/Components/forms/confirmation-checkbox/__tests__/confirmation-checkbox.spec.tsx +++ b/packages/account/src/Components/forms/confirmation-checkbox/__tests__/confirmation-checkbox.spec.tsx @@ -1,7 +1,9 @@ import React from 'react'; +import { Form, Formik } from 'formik'; + import { render, screen } from '@testing-library/react'; + import { ConfirmationCheckbox } from '../confirmation-checkbox'; -import { Formik, Form } from 'formik'; describe('ConfirmationCheckbox', () => { const props: React.ComponentProps = { diff --git a/packages/account/src/Components/forms/confirmation-checkbox/confirmation-checkbox.tsx b/packages/account/src/Components/forms/confirmation-checkbox/confirmation-checkbox.tsx index 24015e6bd133..c67256f7d1ea 100644 --- a/packages/account/src/Components/forms/confirmation-checkbox/confirmation-checkbox.tsx +++ b/packages/account/src/Components/forms/confirmation-checkbox/confirmation-checkbox.tsx @@ -1,7 +1,8 @@ import React from 'react'; +import { useFormikContext } from 'formik'; + import { Checkbox, Text } from '@deriv/components'; import { isMobile } from '@deriv/shared'; -import { useFormikContext } from 'formik'; /** * Props for the confirmation checkbox component. diff --git a/packages/account/src/Components/poi/poi-form-on-signup/idv-doc-submit-on-signup/idv-doc-submit-on-signup.tsx b/packages/account/src/Components/poi/poi-form-on-signup/idv-doc-submit-on-signup/idv-doc-submit-on-signup.tsx index 429e32a6f55b..5aba4d1a9360 100644 --- a/packages/account/src/Components/poi/poi-form-on-signup/idv-doc-submit-on-signup/idv-doc-submit-on-signup.tsx +++ b/packages/account/src/Components/poi/poi-form-on-signup/idv-doc-submit-on-signup/idv-doc-submit-on-signup.tsx @@ -6,12 +6,12 @@ import { GetSettings, ResidenceList } from '@deriv/api-types'; import { Button } from '@deriv/components'; import { filterObjProperties, toMoment, removeEmptyPropertiesFromObject } from '@deriv/shared'; import { - validate, - validateName, - isDocumentTypeValid, isAdditionalDocumentValid, isDocumentNumberValid, + isDocumentTypeValid, shouldHideHelperImage, + validate, + validateName, } from '../../../../Helpers/utils'; import FormSubHeader from '../../../form-sub-header'; import IDVForm from '../../../forms/idv-form'; diff --git a/packages/account/src/Sections/Profile/LanguageSettings/__tests__/language-settings.spec.tsx b/packages/account/src/Sections/Profile/LanguageSettings/__tests__/language-settings.spec.tsx index 4fbf68caa5c0..b2eb19d9925c 100644 --- a/packages/account/src/Sections/Profile/LanguageSettings/__tests__/language-settings.spec.tsx +++ b/packages/account/src/Sections/Profile/LanguageSettings/__tests__/language-settings.spec.tsx @@ -52,7 +52,7 @@ describe('LanguageSettings', () => { const lang_1 = screen.getByText('Test Lang 1'); const lang_2 = screen.getByText('Test Lang 2'); - expect(flags_icons.length).toBe(2); + expect(flags_icons).toHaveLength(2); expect(lang_1).toBeInTheDocument(); expect(/(active)/i.test(lang_1.className)).toBeTruthy(); expect(lang_2).toBeInTheDocument(); diff --git a/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-submission.jsx b/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-submission.jsx index abf5f9c59326..a183b40ff426 100644 --- a/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-submission.jsx +++ b/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-submission.jsx @@ -6,8 +6,9 @@ import IdvDocumentSubmit from 'Components/poi/idv-document-submit'; import IdvUploadComplete from 'Components/poi/idv-status/idv-submit-complete'; import Unsupported from 'Components/poi/status/unsupported'; import UploadComplete from 'Components/poi/status/upload-complete'; + import OnfidoUpload from './onfido-sdk-view-container'; -import { identity_status_codes, submission_status_code, service_code } from './proof-of-identity-utils'; +import { identity_status_codes, service_code, submission_status_code } from './proof-of-identity-utils'; const POISubmission = observer( ({ diff --git a/packages/account/src/Sections/Verification/ProofOfOwnership/__test__/proof-of-ownership-form.spec.js b/packages/account/src/Sections/Verification/ProofOfOwnership/__test__/proof-of-ownership-form.spec.js index 7f7720899a95..8f20ebe21696 100644 --- a/packages/account/src/Sections/Verification/ProofOfOwnership/__test__/proof-of-ownership-form.spec.js +++ b/packages/account/src/Sections/Verification/ProofOfOwnership/__test__/proof-of-ownership-form.spec.js @@ -1,6 +1,9 @@ import React from 'react'; + import { fireEvent, render, screen } from '@testing-library/react'; + import ProofOfOwnershipForm from '../proof-of-ownership-form.jsx'; + import { grouped_payment_method_data } from './test-data'; describe('proof-of-ownership-form.jsx', () => { diff --git a/packages/api/package.json b/packages/api/package.json index c439c01abfc2..a095ceb55c71 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -4,7 +4,7 @@ "version": "1.0.0", "main": "src/index.ts", "dependencies": { - "@deriv/deriv-api": "^1.0.11", + "@deriv/deriv-api": "^1.0.13", "@deriv/utils": "^1.0.0", "@deriv/shared": "^1.0.0", "react": "^17.0.2", diff --git a/packages/api/src/hooks/useResidenceList.ts b/packages/api/src/hooks/useResidenceList.ts index ed7073fa6512..ec65b0076050 100644 --- a/packages/api/src/hooks/useResidenceList.ts +++ b/packages/api/src/hooks/useResidenceList.ts @@ -1,4 +1,5 @@ import { useMemo } from 'react'; + import useQuery from '../useQuery'; /** A custom hook that gets the residence list. */ diff --git a/packages/api/src/hooks/useTransactions.ts b/packages/api/src/hooks/useTransactions.ts index cc66005a0145..1491b69c6265 100644 --- a/packages/api/src/hooks/useTransactions.ts +++ b/packages/api/src/hooks/useTransactions.ts @@ -1,5 +1,7 @@ import { useMemo } from 'react'; + import useInfiniteQuery from '../useInfiniteQuery'; + import useAuthorize from './useAuthorize'; /** A custom hook to get the summary of account transactions */ diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index 87cdbaaa7ed6..3294a9b84a5b 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -1,6 +1,6 @@ -import useQuery from './useQuery'; -import useMutation from './useMutation'; import useInfiniteQuery from './useInfiniteQuery'; +import useMutation from './useMutation'; +import useQuery from './useQuery'; export { default as APIProvider } from './APIProvider'; export { default as useInvalidateQuery } from './useInvalidateQuery'; diff --git a/packages/api/src/useAPI.ts b/packages/api/src/useAPI.ts index ffe43b32beb8..a5969884df3f 100644 --- a/packages/api/src/useAPI.ts +++ b/packages/api/src/useAPI.ts @@ -1,5 +1,5 @@ import { useCallback, useContext } from 'react'; -import APIContext from './APIContext'; + import type { TSocketEndpointNames, TSocketPaginateableEndpointNames, @@ -8,6 +8,8 @@ import type { TSocketSubscribableEndpointNames, } from '../types'; +import APIContext from './APIContext'; + const useAPI = () => { const api = useContext(APIContext); diff --git a/packages/api/src/useInfiniteQuery.ts b/packages/api/src/useInfiniteQuery.ts index 2c4be0cb3415..55793a71715d 100644 --- a/packages/api/src/useInfiniteQuery.ts +++ b/packages/api/src/useInfiniteQuery.ts @@ -1,5 +1,5 @@ import { useInfiniteQuery as _useInfiniteQuery } from '@tanstack/react-query'; -import useAPI from './useAPI'; + import type { TSocketAcceptableProps, TSocketPaginatateableRequestCleaned, @@ -7,6 +7,8 @@ import type { TSocketRequestInfiniteQueryOptions, TSocketRequestPayload, } from '../types'; + +import useAPI from './useAPI'; import { getQueryKeys } from './utils'; const useInfiniteQuery = ( diff --git a/packages/api/src/usePaginatedFetch.ts b/packages/api/src/usePaginatedFetch.ts index bb025842b853..3551c6915124 100644 --- a/packages/api/src/usePaginatedFetch.ts +++ b/packages/api/src/usePaginatedFetch.ts @@ -1,12 +1,14 @@ import { useCallback, useState } from 'react'; -import useQuery from './useQuery'; + import type { TSocketAcceptableProps, + TSocketPaginateableEndpointNames, TSocketRequestPayload, TSocketRequestQueryOptions, - TSocketPaginateableEndpointNames, } from '../types'; +import useQuery from './useQuery'; + const usePaginatedFetch = ( name: T, ...props: TSocketAcceptableProps diff --git a/packages/api/src/useQuery.ts b/packages/api/src/useQuery.ts index 034ec8544c12..6c7c04a8929f 100644 --- a/packages/api/src/useQuery.ts +++ b/packages/api/src/useQuery.ts @@ -1,5 +1,5 @@ import { useQuery as _useQuery } from '@tanstack/react-query'; -import useAPI from './useAPI'; + import type { TSocketAcceptableProps, TSocketEndpointNames, @@ -7,6 +7,8 @@ import type { TSocketRequestQueryOptions, TSocketResponseData, } from '../types'; + +import useAPI from './useAPI'; import { getQueryKeys } from './utils'; const useQuery = (name: T, ...props: TSocketAcceptableProps) => { diff --git a/packages/api/types.ts b/packages/api/types.ts index e40e35f53911..776460025621 100644 --- a/packages/api/types.ts +++ b/packages/api/types.ts @@ -1,12 +1,12 @@ import type { - APITokenRequest, - APITokenResponse, AccountLimitsRequest, AccountLimitsResponse, AccountStatusRequest, AccountStatusResponse, ActiveSymbolsRequest, ActiveSymbolsResponse, + APITokenRequest, + APITokenResponse, ApplicationDeleteRequest, ApplicationDeleteResponse, ApplicationGetDetailsRequest, @@ -71,10 +71,10 @@ import type { LandingCompanyDetailsResponse, LandingCompanyRequest, LandingCompanyResponse, - LogOutRequest, - LogOutResponse, LoginHistoryRequest, LoginHistoryResponse, + LogOutRequest, + LogOutResponse, MT5AccountsListRequest, MT5AccountsListResponse, MT5DepositRequest, @@ -103,10 +103,6 @@ import type { P2PAdvertCreateResponse, P2PAdvertInformationRequest, P2PAdvertInformationResponse, - P2PAdvertListRequest, - P2PAdvertListResponse, - P2PAdvertUpdateRequest, - P2PAdvertUpdateResponse, P2PAdvertiserAdvertsRequest, P2PAdvertiserAdvertsResponse, P2PAdvertiserCreateRequest, @@ -121,6 +117,10 @@ import type { P2PAdvertiserRelationsResponse, P2PAdvertiserUpdateRequest, P2PAdvertiserUpdateResponse, + P2PAdvertListRequest, + P2PAdvertListResponse, + P2PAdvertUpdateRequest, + P2PAdvertUpdateResponse, P2PChatCreateRequest, P2PChatCreateResponse, P2POrderCancelRequest, @@ -229,6 +229,32 @@ import type { import type { useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query'; type TPrivateSocketEndpoints = { + wallet_migration: { + request: { + /** + * Must be `state`, `start` or `reset` + */ + wallet_migration: 'state' | 'start' | 'reset'; + }; + response: { + wallet_migration: { + /** + * State of wallet migration. + */ + state: 'ineligible' | 'eligible' | 'in_progress' | 'migrated' | 'failed'; + }; + /** + * Echo of the request made. + */ + echo_req: { + [k: string]: unknown; + }; + /** + * Action name of the request made. + */ + msg_type: 'wallet_migration'; + }; + }; cashier_payments: { request: { /** @@ -929,7 +955,7 @@ type TPrivateSocketEndpoints = { echo_req: { [k: string]: unknown; }; - /**z + /** * Action name of the request made. */ msg_type: 'trading_platform_available_accounts'; diff --git a/packages/appstore/package.json b/packages/appstore/package.json index a34c8c5c4281..77cd51eb805a 100644 --- a/packages/appstore/package.json +++ b/packages/appstore/package.json @@ -24,28 +24,34 @@ "author": "Deriv", "license": "Apache-2.0", "dependencies": { + "@deriv/api": "^1.0.0", "@deriv/account": "^1.0.0", "@deriv/api-types": "^1.0.118", "@deriv/cashier": "^1.0.0", - "@deriv/components": "^1.0.0", "@deriv/cfd": "^1.0.0", + "@deriv/components": "^1.0.0", "@deriv/shared": "^1.0.0", "@deriv/stores": "^1.0.0", "@deriv/trader": "^3.8.0", "@deriv/translations": "^1.0.0", + "@deriv/utils": "^1.0.0", + "@testing-library/jest-dom": "^5.12.0", "@deriv/hooks": "^1.0.0", "@deriv/wallets": "^1.0.0", "classnames": "^2.2.6", "formik": "^2.1.4", + "lodash.debounce": "^4.0.8", "mobx": "^6.6.1", "mobx-react-lite": "^3.4.0", "object.fromentries": "^2.0.0", "prop-types": "^15.7.2", "react": "^17.0.2", "react-content-loader": "^6.2.0", - "react-router": "^5.2.0", "react-joyride": "^2.5.3", - "react-router-dom": "^5.2.0" + "react-router": "^5.2.0", + "react-router-dom": "^5.2.0", + "react-transition-group": "4.4.2", + "embla-carousel-react": "^8.0.0-rc12" }, "devDependencies": { "@babel/eslint-parser": "^7.17.0", @@ -65,8 +71,8 @@ "@types/react-router-dom": "^5.1.6", "babel-core": "^6.26.3", "babel-loader": "^8.1.0", - "copy-webpack-plugin": "^9.0.1", "concurrently": "^5.3.0", + "copy-webpack-plugin": "^9.0.1", "cross-env": "^5.2.0", "css-loader": "^5.0.1", "css-minimizer-webpack-plugin": "^3.0.1", diff --git a/packages/appstore/src/assets/svgs/wallets/how-it-works.svg b/packages/appstore/src/assets/svgs/wallets/how-it-works.svg new file mode 100644 index 000000000000..7c76148973a5 --- /dev/null +++ b/packages/appstore/src/assets/svgs/wallets/how-it-works.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/appstore/src/assets/svgs/wallets/image-types.tsx b/packages/appstore/src/assets/svgs/wallets/image-types.tsx new file mode 100644 index 000000000000..d94ca85744ce --- /dev/null +++ b/packages/appstore/src/assets/svgs/wallets/image-types.tsx @@ -0,0 +1,10 @@ +import { WalletsImagesList } from './wallets-image'; + +export type TWalletsImagesListKey = keyof typeof WalletsImagesList; +export type TImageTestID = `dt_${TWalletsImagesListKey}`; + +export type WalletsImageProps = { + image: T; + className?: string; + width?: number; +}; diff --git a/packages/appstore/src/assets/svgs/wallets/index.tsx b/packages/appstore/src/assets/svgs/wallets/index.tsx new file mode 100644 index 000000000000..dfd3f51c7e64 --- /dev/null +++ b/packages/appstore/src/assets/svgs/wallets/index.tsx @@ -0,0 +1,3 @@ +import WalletsImage from './wallets-image'; + +export default WalletsImage; diff --git a/packages/appstore/src/assets/svgs/wallets/introducing-wallets-eu.svg b/packages/appstore/src/assets/svgs/wallets/introducing-wallets-eu.svg new file mode 100644 index 000000000000..d5a19341f5eb --- /dev/null +++ b/packages/appstore/src/assets/svgs/wallets/introducing-wallets-eu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/appstore/src/assets/svgs/wallets/introducing-wallets.svg b/packages/appstore/src/assets/svgs/wallets/introducing-wallets.svg new file mode 100644 index 000000000000..a87524f4c528 --- /dev/null +++ b/packages/appstore/src/assets/svgs/wallets/introducing-wallets.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/appstore/src/assets/svgs/wallets/ready_to_update_wallets_image.svg b/packages/appstore/src/assets/svgs/wallets/ready_to_update_wallets_image.svg new file mode 100644 index 000000000000..e9440a5c4717 --- /dev/null +++ b/packages/appstore/src/assets/svgs/wallets/ready_to_update_wallets_image.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/appstore/src/assets/svgs/wallets/ready_to_upgrade_wallets_image.svg b/packages/appstore/src/assets/svgs/wallets/ready_to_upgrade_wallets_image.svg new file mode 100644 index 000000000000..e9440a5c4717 --- /dev/null +++ b/packages/appstore/src/assets/svgs/wallets/ready_to_upgrade_wallets_image.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/appstore/src/assets/svgs/wallets/trading-accounts-eu.svg b/packages/appstore/src/assets/svgs/wallets/trading-accounts-eu.svg new file mode 100644 index 000000000000..c0e7c9598fd4 --- /dev/null +++ b/packages/appstore/src/assets/svgs/wallets/trading-accounts-eu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/appstore/src/assets/svgs/wallets/trading-accounts.svg b/packages/appstore/src/assets/svgs/wallets/trading-accounts.svg new file mode 100644 index 000000000000..7802f43e7c6e --- /dev/null +++ b/packages/appstore/src/assets/svgs/wallets/trading-accounts.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/appstore/src/assets/svgs/wallets/wallets-image.tsx b/packages/appstore/src/assets/svgs/wallets/wallets-image.tsx new file mode 100644 index 000000000000..b7de44de33ab --- /dev/null +++ b/packages/appstore/src/assets/svgs/wallets/wallets-image.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { TImageTestID, TWalletsImagesListKey, WalletsImageProps } from './image-types'; +import HowItWorks from 'Assets/svgs/wallets/how-it-works.svg'; +import IntroducingWallets from 'Assets/svgs/wallets/introducing-wallets.svg'; +import IntroducingWalletsEU from 'Assets/svgs/wallets/introducing-wallets-eu.svg'; +import ReadyToUpgradeWalletsImage from './ready_to_upgrade_wallets_image.svg'; +import ReadyDesktopEuImage from 'Assets/svgs/wallets/wallets-ready-desktop-eu.svg'; +import ReadyDesktopImage from 'Assets/svgs/wallets/wallets-ready-desktop.svg'; +import ReadyMobileEuImage from 'Assets/svgs/wallets/wallets-ready-mobile-eu.svg'; +import ReadyMobileImage from 'Assets/svgs/wallets/wallets-ready-mobile.svg'; +import TradingAccounts from 'Assets/svgs/wallets/trading-accounts.svg'; +import TradingAccountsEU from 'Assets/svgs/wallets/trading-accounts-eu.svg'; +import UpgradeDesktopImage from 'Assets/svgs/wallets/wallets-upgrade-desktop.svg'; +import UpgradeMobileImage from 'Assets/svgs/wallets/wallets-upgrade-mobile.svg'; + +export const WalletsImagesList = { + how_it_works: HowItWorks, + introducing_wallets: IntroducingWallets, + introducing_wallets_eu: IntroducingWalletsEU, + ready_desktop: ReadyDesktopImage, + ready_desktop_eu: ReadyDesktopEuImage, + ready_mobile: ReadyMobileImage, + ready_mobile_eu: ReadyMobileEuImage, + trading_accounts: TradingAccounts, + trading_accounts_eu: TradingAccountsEU, + upgrade_desktop: UpgradeDesktopImage, + upgrading_desktop: ReadyDesktopImage, + upgrading_desktop_eu: ReadyDesktopEuImage, + upgrade_mobile: UpgradeMobileImage, + upgrading_mobile: ReadyMobileImage, + upgrading_mobile_eu: ReadyMobileEuImage, + ready_to_upgrade_wallets_image: ReadyToUpgradeWalletsImage, +} as const; + +const WalletsImage = ({ image, className, width }: WalletsImageProps) => { + const Component = WalletsImagesList[image] as React.ElementType; + const data_testid: TImageTestID = `dt_${image}`; + + return ; +}; + +export default WalletsImage; diff --git a/packages/appstore/src/assets/svgs/wallets/wallets-ready-desktop-eu.svg b/packages/appstore/src/assets/svgs/wallets/wallets-ready-desktop-eu.svg new file mode 100644 index 000000000000..b6a2921adc75 --- /dev/null +++ b/packages/appstore/src/assets/svgs/wallets/wallets-ready-desktop-eu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/appstore/src/assets/svgs/wallets/wallets-ready-desktop.svg b/packages/appstore/src/assets/svgs/wallets/wallets-ready-desktop.svg new file mode 100644 index 000000000000..8968ce4e2d42 --- /dev/null +++ b/packages/appstore/src/assets/svgs/wallets/wallets-ready-desktop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/appstore/src/assets/svgs/wallets/wallets-ready-mobile-eu.svg b/packages/appstore/src/assets/svgs/wallets/wallets-ready-mobile-eu.svg new file mode 100644 index 000000000000..2df69c4e3170 --- /dev/null +++ b/packages/appstore/src/assets/svgs/wallets/wallets-ready-mobile-eu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/appstore/src/assets/svgs/wallets/wallets-ready-mobile.svg b/packages/appstore/src/assets/svgs/wallets/wallets-ready-mobile.svg new file mode 100644 index 000000000000..2a7f784a6dcc --- /dev/null +++ b/packages/appstore/src/assets/svgs/wallets/wallets-ready-mobile.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/appstore/src/assets/svgs/wallets/wallets-upgrade-desktop.svg b/packages/appstore/src/assets/svgs/wallets/wallets-upgrade-desktop.svg new file mode 100644 index 000000000000..81a9e3b68662 --- /dev/null +++ b/packages/appstore/src/assets/svgs/wallets/wallets-upgrade-desktop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/appstore/src/assets/svgs/wallets/wallets-upgrade-mobile.svg b/packages/appstore/src/assets/svgs/wallets/wallets-upgrade-mobile.svg new file mode 100644 index 000000000000..92c92111f8fd --- /dev/null +++ b/packages/appstore/src/assets/svgs/wallets/wallets-upgrade-mobile.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/appstore/src/components/add-more-wallets/__test__/add-more-wallets.spec.tsx b/packages/appstore/src/components/add-more-wallets/__test__/add-more-wallets.spec.tsx new file mode 100644 index 000000000000..5ae97d7ff1ad --- /dev/null +++ b/packages/appstore/src/components/add-more-wallets/__test__/add-more-wallets.spec.tsx @@ -0,0 +1,136 @@ +import React from 'react'; +import { screen, render } from '@testing-library/react'; +import { APIProvider } from '@deriv/api'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import AddMoreWallets from '../add-more-wallets'; + +jest.mock('../wallet-add-card', () => { + const AddWalletCard = () =>
AddWalletCard
; + return AddWalletCard; +}); + +jest.mock('../carousel-container', () => { + const CarouselContainer = ({ children }: React.PropsWithChildren) => ( +
{children}
+ ); + return CarouselContainer; +}); + +jest.mock('@deriv/api', () => ({ + ...jest.requireActual('@deriv/api'), + useFetch: jest.fn((name: string) => { + if (name === 'authorize') { + return { + data: { + authorize: { + account_list: [ + { account_category: 'wallet', landing_company_name: 'svg', is_virtual: 0, currency: 'USD' }, + ], + landing_company_name: 'svg', + }, + }, + }; + } + + if (name === 'get_account_types') { + return { + data: { + get_account_types: { + wallet: { + crypto: { + currencies: ['BTC', 'ETH'], + }, + doughflow: { + currencies: ['USD', 'EUR'], + }, + }, + }, + }, + }; + } + + return { data: undefined }; + }), +})); + +describe('AddMoreWallets', () => { + const wrapper = (mock: ReturnType) => { + const Component = ({ children }: React.PropsWithChildren) => { + return ( + + {children} + + ); + }; + return Component; + }; + + it('should render the component without errors', () => { + const mock = mockStore({ + client: { + loginid: 'CRW909900', + accounts: { + CRW909900: { + token: '12345', + }, + }, + is_crypto: (currency: string) => currency === 'BTC', + }, + }); + + const { container } = render(, { wrapper: wrapper(mock) }); + expect(container).toBeInTheDocument(); + }); + + it('should render the title correctly', () => { + const mock = mockStore({ + client: { + loginid: 'CRW909900', + accounts: { + CRW909900: { + token: '12345', + }, + }, + is_crypto: (currency: string) => currency === 'BTC', + }, + }); + + render(, { wrapper: wrapper(mock) }); + expect(screen.getByText('Add more Wallets')).toBeInTheDocument(); + }); + + it('should render the carousel', () => { + const mock = mockStore({ + client: { + loginid: 'CRW909900', + accounts: { + CRW909900: { + token: '12345', + }, + }, + is_crypto: (currency: string) => currency === 'BTC', + }, + }); + + render(, { wrapper: wrapper(mock) }); + expect(screen.getByTestId('dt_carousel_container')).toBeInTheDocument(); + }); + + it('should render the wallet add card', () => { + const mock = mockStore({ + client: { + loginid: 'CRW909900', + accounts: { + CRW909900: { + token: '12345', + }, + }, + is_crypto: (currency: string) => currency === 'BTC', + }, + }); + + render(, { wrapper: wrapper(mock) }); + const wallet_cards = screen.getAllByText(/AddWalletCard/i); + expect(wallet_cards).toHaveLength(4); + }); +}); diff --git a/packages/appstore/src/components/add-more-wallets/__test__/wallet-add-card.spec.tsx b/packages/appstore/src/components/add-more-wallets/__test__/wallet-add-card.spec.tsx new file mode 100644 index 000000000000..8666b2da0b61 --- /dev/null +++ b/packages/appstore/src/components/add-more-wallets/__test__/wallet-add-card.spec.tsx @@ -0,0 +1,118 @@ +import React from 'react'; +import { APIProvider } from '@deriv/api'; +import { screen, render } from '@testing-library/react'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import AddWalletCard from '../wallet-add-card'; + +const wallet_info = { + currency: 'BTC', + gradient_card_class: '', + landing_company_name: 'svg', + is_added: false, +}; + +jest.mock('@deriv/api', () => ({ + ...jest.requireActual('@deriv/api'), + useFetch: jest.fn((name: string) => { + if (name === 'authorize') { + return { + data: { + authorize: { + account_list: [ + { account_category: 'wallet', landing_company_name: 'svg', is_virtual: 0, currency: 'USD' }, + ], + landing_company_name: 'svg', + }, + }, + }; + } + + if (name === 'get_account_types') { + return { + data: { + get_account_types: { + wallet: { + crypto: { + currencies: ['BTC'], + }, + doughflow: { + currencies: ['USD'], + }, + }, + }, + }, + }; + } + + if (name === 'website_status') { + return { + data: { + website_status: { + currencies_config: { + USD: { type: 'fiat', name: 'US Dollar' }, + BTC: { type: 'crypto', name: 'Bitcoin' }, + UST: { type: 'crypto', name: 'USDT' }, + }, + }, + }, + }; + } + + return { data: undefined }; + }), +})); + +describe('AddWalletCard', () => { + it('should render currency card', () => { + const mock = mockStore({}); + + render( + + + + + + ); + + const add_btn = screen.getByRole('button', { name: /Add/i }); + expect(screen.getByText('BTC Wallet')).toBeInTheDocument(); + expect(screen.getByText('SVG')).toBeInTheDocument(); + expect(add_btn).toBeInTheDocument(); + expect(add_btn).toBeEnabled(); + expect( + screen.getByText( + "Deposit and withdraw Bitcoin, the world's most popular cryptocurrency, hosted on the Bitcoin blockchain." + ) + ).toBeInTheDocument(); + }); + + it('should disabled button when it is disabled', () => { + const mock = mockStore({}); + + render( + + + + + + ); + + const added_btn = screen.getByRole('button', { name: /Added/i }); + expect(added_btn).toBeInTheDocument(); + expect(added_btn).toBeDisabled(); + }); + + it('should show USDT instead of UST for UST currency', () => { + const mock = mockStore({}); + + render( + + + + + + ); + expect(screen.getByText('USDT Wallet')).toBeInTheDocument(); + expect(screen.queryByText('UST Wallet')).not.toBeInTheDocument(); + }); +}); diff --git a/packages/appstore/src/components/add-more-wallets/add-more-wallets.scss b/packages/appstore/src/components/add-more-wallets/add-more-wallets.scss new file mode 100644 index 000000000000..a9a60392bb33 --- /dev/null +++ b/packages/appstore/src/components/add-more-wallets/add-more-wallets.scss @@ -0,0 +1,91 @@ +@mixin align-center { + display: flex; + align-items: center; +} + +.add-wallets { + display: flex; + padding-top: 2.4rem; + flex-direction: column; + align-items: flex-start; + gap: 1.6rem; + width: 100%; + + &__title { + @include mobile { + padding-left: 1.6rem; + } + } + &__content { + background-color: var(--general-main-1); + border-radius: $BORDER_RADIUS; + position: relative; + width: 100%; + } + &__card { + width: 23rem; + height: 29rem; + padding: 1.6rem; + border-radius: 1.6rem; + border: 0.1rem solid var(--general-active); + background-color: var(--general-main-1); + box-shadow: $wallet-box-shadow; + cursor: pointer; + &-description, + &-wrapper { + display: flex; + flex-direction: column; + } + &-description { + align-items: flex-start; + gap: 0.8rem; + } + &-wrapper { + align-items: center; + gap: 2.4rem; + @include mobile { + gap: 1.6rem; + } + } + } +} + +.carousel { + overflow: hidden; + padding: 3.2rem 1.6rem; + &__wrapper { + height: 100%; + @include align-center; + justify-content: flex-start; + gap: 2.4rem; + } + &__btn { + background-color: var(--general-main-1); + z-index: 1; + color: var(--text-general); + position: absolute; + @include align-center; + justify-content: center; + top: 45%; + cursor: pointer; + width: 4rem; + height: 4rem; + border: 1px solid var(--general-background-main); + border-radius: 50%; + box-shadow: $btn-shadow; + &-prev { + left: 1.6rem; + } + &-next { + right: 1.6rem; + } + &-icon { + width: 50%; + height: 35%; + } + &:disabled { + opacity: 0.3; + display: none; + } + } +} diff --git a/packages/appstore/src/components/add-more-wallets/add-more-wallets.tsx b/packages/appstore/src/components/add-more-wallets/add-more-wallets.tsx new file mode 100644 index 000000000000..0af06208e35e --- /dev/null +++ b/packages/appstore/src/components/add-more-wallets/add-more-wallets.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { Text, Loading } from '@deriv/components'; +import { Localize } from '@deriv/translations'; +import { useAvailableWallets } from '@deriv/hooks'; +import { observer, useStore } from '@deriv/stores'; +import CarouselContainer from './carousel-container'; +import AddWalletCard from './wallet-add-card'; + +import './add-more-wallets.scss'; + +const AddMoreWallets = observer(() => { + const { data, isLoading } = useAvailableWallets(); + const { + ui: { is_mobile }, + } = useStore(); + + return ( +
+ + + + + {isLoading ? ( + + ) : ( + data?.map(wallet => ) + )} + +
+ ); +}); + +export default AddMoreWallets; diff --git a/packages/appstore/src/components/add-more-wallets/carousel-buttons.tsx b/packages/appstore/src/components/add-more-wallets/carousel-buttons.tsx new file mode 100644 index 000000000000..2e3353819d72 --- /dev/null +++ b/packages/appstore/src/components/add-more-wallets/carousel-buttons.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { Icon } from '@deriv/components'; + +type PrevNextButtonProps = { + enabled: boolean; + nav_action: 'prev' | 'next'; + onClick: () => void; +}; + +export const CarouselButton: React.FC = props => { + const { enabled, onClick, nav_action } = props; + const icon = nav_action === 'prev' ? 'IcChevronLeftBold' : 'IcChevronRightBold'; + const className = nav_action === 'prev' ? 'carousel__btn carousel__btn-prev' : 'carousel__btn carousel__btn-next'; + + return ( + + ); +}; diff --git a/packages/appstore/src/components/add-more-wallets/carousel-container.tsx b/packages/appstore/src/components/add-more-wallets/carousel-container.tsx new file mode 100644 index 000000000000..5ce8f9b059f2 --- /dev/null +++ b/packages/appstore/src/components/add-more-wallets/carousel-container.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import useEmblaCarousel, { EmblaCarouselType, EmblaOptionsType } from 'embla-carousel-react'; +import { observer, useStore } from '@deriv/stores'; +import { CarouselButton } from './carousel-buttons'; + +const CarouselContainer: React.FC> = observer(({ children }) => { + const { ui } = useStore(); + const { is_mobile } = ui; + + const options: EmblaOptionsType = { + align: 0, + containScroll: 'trimSnaps', + watchDrag: is_mobile, + }; + + const [emblaRef, emblaApi] = useEmblaCarousel(options); + const [is_hovered, setIsHovered] = React.useState(is_mobile); + const [prev_btn_enabled, setPrevBtnEnabled] = React.useState(false); + const [next_btn_enabled, setNextBtnEnabled] = React.useState(false); + + const scrollPrev = React.useCallback(() => emblaApi?.scrollPrev(), [emblaApi]); + const scrollNext = React.useCallback(() => emblaApi?.scrollNext(), [emblaApi]); + + const onSelect = React.useCallback((embla_api: EmblaCarouselType) => { + setPrevBtnEnabled(embla_api.canScrollPrev()); + setNextBtnEnabled(embla_api.canScrollNext()); + }, []); + + React.useEffect(() => { + if (!emblaApi) return; + + onSelect(emblaApi); + emblaApi.on('reInit', onSelect); + emblaApi.on('select', onSelect); + }, [emblaApi, onSelect]); + + return ( +
!is_mobile && setIsHovered(true)} + onMouseLeave={() => !is_mobile && setIsHovered(false)} + > +
+
{children}
+
+ {!is_mobile && is_hovered && ( + + + + + )} +
+ ); +}); + +export default CarouselContainer; diff --git a/packages/appstore/src/components/add-more-wallets/index.ts b/packages/appstore/src/components/add-more-wallets/index.ts new file mode 100644 index 000000000000..db03122683b1 --- /dev/null +++ b/packages/appstore/src/components/add-more-wallets/index.ts @@ -0,0 +1,3 @@ +import AddMoreWallets from './add-more-wallets'; + +export default AddMoreWallets; diff --git a/packages/appstore/src/components/add-more-wallets/wallet-add-card.tsx b/packages/appstore/src/components/add-more-wallets/wallet-add-card.tsx new file mode 100644 index 000000000000..5e74b263ee52 --- /dev/null +++ b/packages/appstore/src/components/add-more-wallets/wallet-add-card.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { TWalletInfo } from 'Types'; +import { Text, WalletCard } from '@deriv/components'; +import { useCurrencyConfig } from '@deriv/hooks'; +import { observer, useStore } from '@deriv/stores'; +import { Localize } from '@deriv/translations'; +import { getWalletCurrencyIcon } from '@deriv/utils'; +import wallet_description_mapper from 'Constants/wallet_description_mapper'; + +type TAddWalletCard = { + wallet_info: React.PropsWithChildren; +}; + +const AddWalletCard = observer(({ wallet_info }: TAddWalletCard) => { + const { + ui: { is_dark_mode_on, is_mobile }, + } = useStore(); + + const { currency = '', landing_company_name, is_added, gradient_card_class } = wallet_info; + const { getConfig } = useCurrencyConfig(); + const currency_config = getConfig(currency); + + const wallet_details = { + currency, + icon: getWalletCurrencyIcon(currency, is_dark_mode_on), + icon_type: currency_config?.type, + jurisdiction_title: landing_company_name?.toUpperCase(), + name: currency_config?.name, + gradient_class: gradient_card_class, + }; + + return ( +
+
+ +
+ + + + + {wallet_description_mapper[currency]} + +
+
+
+ ); +}); + +export default AddWalletCard; diff --git a/packages/appstore/src/components/app-content.tsx b/packages/appstore/src/components/app-content.tsx new file mode 100644 index 000000000000..5f0ff1ba57cb --- /dev/null +++ b/packages/appstore/src/components/app-content.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { routes } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; +import Routes from 'Components/routes/routes'; +import classNames from 'classnames'; +import './app.scss'; + +const AppContent: React.FC = observer(() => { + const { ui } = useStore(); + const { is_dark_mode_on } = ui; + + return ( +
+
+ +
+
+ ); +}); + +export default AppContent; diff --git a/packages/appstore/src/components/app.tsx b/packages/appstore/src/components/app.tsx index 1a9c0e436dfb..88996a0cfe80 100644 --- a/packages/appstore/src/components/app.tsx +++ b/packages/appstore/src/components/app.tsx @@ -1,45 +1,24 @@ -import classNames from 'classnames'; -import * as React from 'react'; -import { setWebsocket, routes } from '@deriv/shared'; -import { StoreProvider, observer } from '@deriv/stores'; +import React from 'react'; import CashierStoreProvider from '@deriv/cashier/src/cashier-providers'; import CFDStoreProvider from '@deriv/cfd/src/cfd-providers'; -import Routes from 'Components/routes/routes'; -import { useStores, initContext } from 'Stores'; -import { TRootStore } from 'Types'; +import { StoreProvider } from '@deriv/stores'; +import AppContent from './app-content'; import './app.scss'; -type TAppProps = { +type TProps = { passthrough: { - root_store: TRootStore; - WS: Record; + root_store: React.ComponentProps['store']; }; }; -const App = ({ passthrough: { WS, root_store } }: TAppProps) => { - initContext(root_store, WS); - setWebsocket(WS); - const { ui }: TRootStore = useStores(); +const App: React.FC = ({ passthrough: { root_store } }) => ( + + + + + + + +); - return ( - - - -
-
- -
-
-
-
-
- ); -}; - -export default observer(App); +export default App; diff --git a/packages/appstore/src/components/cfds-listing/__tests__/index.spec.tsx b/packages/appstore/src/components/cfds-listing/__tests__/index.spec.tsx index 2653f7213d26..ca6cac2aa4bc 100644 --- a/packages/appstore/src/components/cfds-listing/__tests__/index.spec.tsx +++ b/packages/appstore/src/components/cfds-listing/__tests__/index.spec.tsx @@ -10,10 +10,9 @@ jest.mock('Components/containers/listing-container', () => describe('CFDsListing', () => { const mock = mockStore({ traders_hub: { - selected_region: 'NON-EU', + selected_region: 'Non-EU', has_any_real_account: true, is_real: true, - can_get_more_cfd_mt5_accounts: true, no_MF_account: true, is_demo_low_risk: true, }, diff --git a/packages/appstore/src/components/cfds-listing/index.tsx b/packages/appstore/src/components/cfds-listing/index.tsx index 9ce334e15dd4..96166410afb1 100644 --- a/packages/appstore/src/components/cfds-listing/index.tsx +++ b/packages/appstore/src/components/cfds-listing/index.tsx @@ -1,6 +1,7 @@ import React from 'react'; -import { observer, useStore } from '@deriv/stores'; import { Text, StaticUrl } from '@deriv/components'; +import { useCFDCanGetMoreMT5Accounts } from '@deriv/hooks'; +import { observer, useStore } from '@deriv/stores'; import { isMobile, formatMoney, getAuthenticationStatusInfo, Jurisdiction } from '@deriv/shared'; import { localize, Localize } from '@deriv/translations'; import ListingContainer from 'Components/containers/listing-container'; @@ -13,7 +14,6 @@ import { Actions } from 'Components/containers/trading-app-card-actions'; import { getHasDivider } from 'Constants/utils'; import { AvailableAccount, TDetailsOfEachMT5Loginid } from 'Types'; import './cfds-listing.scss'; -import { useCFDCanGetMoreMT5Accounts } from '@deriv/hooks'; type TDetailedExistingAccount = AvailableAccount & TDetailsOfEachMT5Loginid & @@ -153,7 +153,7 @@ const CFDsListing = observer(() => { title={ !isMobile() && (
- + {localize('CFDs')} diff --git a/packages/appstore/src/components/containers/__tests__/wallets.spec.tsx b/packages/appstore/src/components/containers/__tests__/wallets.spec.tsx new file mode 100644 index 000000000000..f2e97f99c0ca --- /dev/null +++ b/packages/appstore/src/components/containers/__tests__/wallets.spec.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { mockStore, StoreProvider } from '@deriv/stores'; +import Wallet from '../wallet'; +import { TWalletAccount } from 'Types'; + +const mockedRootStore = mockStore({}); + +jest.mock('react-transition-group', () => ({ + CSSTransition: jest.fn(({ children }) =>
{children}
), +})); + +jest.mock('@deriv/account', () => ({ + ...jest.requireActual('@deriv/account'), + getStatusBadgeConfig: jest.fn(() => ({ icon: '', text: '' })), +})); + +jest.mock('@deriv/hooks', () => ({ + ...jest.requireActual('@deriv/hooks'), + useWalletModalActionHandler: jest.fn(() => ({ setWalletModalActiveTabIndex: jest.fn(), handleAction: jest.fn() })), +})); + +jest.mock('./../currency-switcher-container', () => jest.fn(({ children }) =>
{children}
)); +jest.mock('./../../wallet-content', () => jest.fn(() => wallet test content)); + +describe('', () => { + let mocked_props: TWalletAccount; + beforeEach(() => { + // @ts-expect-error need to give a value to all props + mocked_props = { + currency: 'USD', + landing_company_name: 'svg', + balance: 10000, + loginid: 'CRW123123', + is_selected: false, + is_demo: true, + is_malta_wallet: false, + gradient_header_class: 'wallet-header__usd-bg', + gradient_card_class: 'wallet-card__usd-bg', + }; + }); + it('Check class for NOT demo', () => { + const { container } = render( + + + + ); + expect(container.childNodes[0]).toHaveClass('wallet'); + expect(container.childNodes[0]).not.toHaveClass('wallet__demo'); + }); + + it('Check class for demo', () => { + const { container } = render( + + + + ); + + expect(container.childNodes[0]).toHaveClass('wallet'); + expect(container.childNodes[0]).toHaveClass('wallet__demo'); + }); + + it('Check for demo wallet header', () => { + render( + + + + ); + + const currency_card = screen.queryByTestId('dt_demo'); + expect(currency_card).toBeInTheDocument(); + }); +}); diff --git a/packages/appstore/src/components/containers/currency-switcher-container.tsx b/packages/appstore/src/components/containers/currency-switcher-container.tsx index 332211ee85a6..d1e980020ac3 100644 --- a/packages/appstore/src/components/containers/currency-switcher-container.tsx +++ b/packages/appstore/src/components/containers/currency-switcher-container.tsx @@ -1,16 +1,54 @@ -import React, { HTMLAttributes, ReactNode } from 'react'; +import React from 'react'; import classNames from 'classnames'; import { Icon } from '@deriv/components'; +import { Jurisdiction } from '@deriv/shared'; +import { useStore, observer } from '@deriv/stores'; import CurrencyIcon, { Currency } from 'Assets/svgs/currency'; +import TradingPlatformIcon from 'Assets/svgs/trading-platform'; import './currency-switcher-container.scss'; -import { useStore, observer } from '@deriv/stores'; -interface CurrentSwitcherContainerProps extends Omit, 'title'> { - actions?: ReactNode; +interface CurrentSwitcherContainerProps extends Omit, 'title'> { + actions?: React.ReactNode; has_interaction?: boolean; - icon: Currency; - title: ReactNode; + icon: Currency | 'Options'; + title?: React.ReactNode; + show_dropdown?: boolean; } +type CurrencyPlatformIconProps = { + icon: Currency | 'Options'; +}; + +type DropdownProps = Omit< + CurrentSwitcherContainerProps, + 'actions' | 'children' | 'className' | 'has_interaction' | 'icon' | 'title' | 'show_dropdown' +>; + +const CurrencyPlatformIcon = ({ icon }: CurrencyPlatformIconProps) => + icon === 'Options' ? ( + + ) : ( + + ); + +const Dropdown = observer(({ ...props }: DropdownProps) => { + const store = useStore(); + const { modules, traders_hub } = store; + const { is_eu_user, is_demo } = traders_hub; + const { current_list } = modules.cfd; + + const has_mf_mt5_account = Object.keys(current_list) + .map(key => current_list[key]) + .some(account => account.landing_company_short === Jurisdiction.MALTA_INVEST); + + if ((is_eu_user && has_mf_mt5_account) || is_demo) { + return null; + } + return ( +
+ +
+ ); +}); const CurrentSwitcherContainer = observer( ({ @@ -20,34 +58,13 @@ const CurrentSwitcherContainer = observer( has_interaction = false, icon, title, + show_dropdown = true, ...props }: CurrentSwitcherContainerProps) => { - const { client, modules, traders_hub } = useStore(); + const store = useStore(); + const { client } = store; const { document_status } = client.authentication_status; - const { is_eu_user, is_demo } = traders_hub; - const { current_list } = modules.cfd; - - const has_mf_mt5_account = Object.keys(current_list) - .map(key => current_list[key]) - .some(account => account.landing_company_short === 'maltainvest'); - - const Dropdown = () => { - const icon_dropdown = ( -
- -
- ); - - if ((is_eu_user && has_mf_mt5_account) || is_demo) { - return null; - } - return icon_dropdown; - }; return (
- +
{actions} - + {show_dropdown && }
); diff --git a/packages/appstore/src/components/containers/listing-container.tsx b/packages/appstore/src/components/containers/listing-container.tsx index 7317666c4085..969cec68f38a 100644 --- a/packages/appstore/src/components/containers/listing-container.tsx +++ b/packages/appstore/src/components/containers/listing-container.tsx @@ -1,54 +1,65 @@ import React, { HTMLAttributes, ReactNode } from 'react'; +import classNames from 'classnames'; +import { observer, useStore } from '@deriv/stores'; +import { TWalletAccount } from 'Types'; import CurrencySwitcherCard from 'Components/currency-switcher-card'; import GridContainer from 'Components/containers/grid-container'; -import './listing-container.scss'; -import { useStores } from 'Stores/index'; -import { observer } from 'mobx-react-lite'; import TitleCardLoader from 'Components/pre-loader/title-card-loader'; +import WalletTransferBlock from 'Components/wallet-content/wallet-transfer-block'; +import './listing-container.scss'; -type ListingContainerProps = { +type TListingContainerProps = { title: ReactNode; description: ReactNode; is_deriv_platform?: boolean; + wallet_account?: TWalletAccount; + className?: string; + is_outside_grid_container?: boolean; +}; +type TOptionsProps = Pick; +type TSwitcherProps = Pick; + +const Options = observer(({ title, description, is_deriv_platform }: TOptionsProps) => { + const { + client: { is_landing_company_loaded }, + } = useStore(); + + if (is_landing_company_loaded || !is_deriv_platform) { + return ( +
+ {title} + {description} +
+ ); + } + + return ; +}); + +const Switcher = ({ wallet_account, is_deriv_platform }: TSwitcherProps) => { + if (!is_deriv_platform) return null; + if (wallet_account) return ; + return ; }; const ListingContainer = ({ title, description, is_deriv_platform = false, + is_outside_grid_container, + wallet_account, children, -}: ListingContainerProps & Omit, 'title'>) => { - const { client } = useStores(); - const { is_landing_company_loaded } = client; - - const Options = () => { - if (is_landing_company_loaded) { - return ( -
- {title} - {description} -
- ); - } else if (!is_deriv_platform) { - return ( -
- {title} - {description} -
- ); - } - return ; - }; - + className, +}: TListingContainerProps & Omit, 'title'>) => { return ( -
+
- - {is_deriv_platform && } + +
- {children} + {is_outside_grid_container ? children : {children}}
); }; -export default observer(ListingContainer); +export default ListingContainer; diff --git a/packages/appstore/src/components/containers/trading-app-card.tsx b/packages/appstore/src/components/containers/trading-app-card.tsx index 46a3a867a257..d7c4996b55c8 100644 --- a/packages/appstore/src/components/containers/trading-app-card.tsx +++ b/packages/appstore/src/components/containers/trading-app-card.tsx @@ -9,13 +9,18 @@ import { BrandConfig, DERIV_PLATFORM_NAMES, } from 'Constants/platform-config'; -import './trading-app-card.scss'; import TradingAppCardActions, { Actions } from './trading-app-card-actions'; import { AvailableAccount, TDetailsOfEachMT5Loginid } from 'Types'; import { useStores } from 'Stores/index'; import { observer } from 'mobx-react-lite'; import { localize } from '@deriv/translations'; import { CFD_PLATFORMS, ContentFlag, getStaticUrl, getUrlSmartTrader, getUrlBinaryBot } from '@deriv/shared'; +import { useActiveWallet } from '@deriv/hooks'; +import './trading-app-card.scss'; + +type TWalletsProps = { + wallet_account?: ReturnType; +}; const TradingAppCard = ({ availability, @@ -33,7 +38,8 @@ const TradingAppCard = ({ mt5_acc_auth_status, selected_mt5_jurisdiction, openFailedVerificationModal, -}: Actions & BrandConfig & AvailableAccount & TDetailsOfEachMT5Loginid) => { + wallet_account, +}: Actions & BrandConfig & AvailableAccount & TDetailsOfEachMT5Loginid & TWalletsProps) => { const { common, traders_hub, @@ -43,6 +49,9 @@ const TradingAppCard = ({ const { current_language } = common; const { is_account_being_created } = cfd; + const demo_label = localize('Demo'); + const is_real_account = wallet_account ? !wallet_account.is_virtual : is_real; + const low_risk_cr_non_eu = content_flag === ContentFlag.LOW_RISK_CR_NON_EU; const app_platform = @@ -111,9 +120,9 @@ const TradingAppCard = ({
- {!is_real && sub_title ? `${sub_title} ${localize('Demo')}` : sub_title} + {!is_real_account && sub_title ? `${sub_title} ${demo_label}` : sub_title} - {short_code_and_region && ( + {!wallet_account && short_code_and_region && ( - {!is_real && !sub_title && !is_deriv_platform ? `${name} ${localize('Demo')}` : name} + {!is_real_account && !sub_title && !is_deriv_platform ? `${name} ${demo_label}` : name} {appDescription()} diff --git a/packages/appstore/src/components/containers/wallet.scss b/packages/appstore/src/components/containers/wallet.scss new file mode 100644 index 000000000000..312f6136b87a --- /dev/null +++ b/packages/appstore/src/components/containers/wallet.scss @@ -0,0 +1,44 @@ +.wallet { + background-color: var(--general-main-1); + border-radius: $BORDER_RADIUS * 4; + align-self: stretch; + + &__demo { + background-color: var(--wallet-demo-bg-color); + } + + &__content { + padding: 2.4rem; + } + + &__content-transition { + &-enter { + transform: translateY(-3rem); + opacity: 0; + } + + &-enter-active { + transition: all 240ms ease-in-out; + transform: translateY(0); + position: relative; + opacity: 1; + } + + &-enter-done, + &-exit { + transform: translateY(0); + opacity: 1; + } + + &-exit-active { + transition: all 240ms ease-in-out; + transform: translateY(-3rem); + position: relative; + opacity: 0; + } + + &-exit-done { + opacity: 0; + } + } +} diff --git a/packages/appstore/src/components/containers/wallet.tsx b/packages/appstore/src/components/containers/wallet.tsx new file mode 100644 index 000000000000..895a73c68e16 --- /dev/null +++ b/packages/appstore/src/components/containers/wallet.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import classNames from 'classnames'; +import WalletHeader from 'Components/wallet-header'; +import WalletContent from 'Components/wallet-content'; +import { CSSTransition } from 'react-transition-group'; +import { TWalletAccount } from 'Types'; +import './wallet.scss'; + +type TWallet = { + wallet_account: TWalletAccount; +}; + +const Wallet = ({ wallet_account }: TWallet) => { + const headerRef = React.useRef(null); + const { is_selected, is_demo, is_malta_wallet } = wallet_account; + + return ( +
+ + { + if (headerRef?.current) { + headerRef.current.style.scrollMargin = '20px'; + headerRef.current.scrollIntoView({ behavior: 'smooth' }); + } + }} + classNames='wallet__content-transition' + unmountOnExit + > + + +
+ ); +}; + +export default Wallet; diff --git a/packages/appstore/src/components/demo-reset-balance/__tests__/demo-reset-balance.spec.tsx b/packages/appstore/src/components/demo-reset-balance/__tests__/demo-reset-balance.spec.tsx new file mode 100644 index 000000000000..e0f4c1ee1e16 --- /dev/null +++ b/packages/appstore/src/components/demo-reset-balance/__tests__/demo-reset-balance.spec.tsx @@ -0,0 +1,135 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { APIProvider, useRequest } from '@deriv/api'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import DemoResetBalance from '../demo-reset-balance'; + +jest.mock('@deriv/api', () => ({ + ...jest.requireActual('@deriv/api'), + useRequest: jest.fn(() => ({ mutate: jest.fn() })), +})); + +const mockUseRequest = useRequest as jest.MockedFunction>; + +describe('', () => { + it('should render demo reset balance component correctly', () => { + const mock = mockStore({}); + // @ts-expect-error need to come up with a way to mock the return type of useRequest + mockUseRequest.mockReturnValue({}); + const setActiveTabIndex = jest.fn(); + + render(, { + wrapper: ({ children }) => ( + + {children} + + ), + }); + + expect(screen.getByText('Reset balance to 10,000.00 USD')).toBeInTheDocument(); + expect( + screen.getByText('Reset your virtual balance if it falls below 10,000.00 USD or exceeds 10,000.00 USD.') + ).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Reset balance' })).toBeInTheDocument(); + }); + + it('should disable reset balance button if the balance is equal to 10000 usd', () => { + const mock = mockStore({ + client: { + accounts: { + VRW1002: { + balance: 10000, + }, + }, + loginid: 'VRW1002', + }, + }); + // @ts-expect-error need to come up with a way to mock the return type of useRequest + mockUseRequest.mockReturnValue({}); + const setActiveTabIndex = jest.fn(); + + render(, { + wrapper: ({ children }) => ( + + {children} + + ), + }); + + expect(screen.getByRole('button', { name: /Reset balance/i })).toBeDisabled(); + }); + + it('should call reset balance API when click on Reset balance', () => { + const mock = mockStore({ + client: { + accounts: { + VRW1002: { + balance: 9880, + }, + }, + loginid: 'VRW1002', + }, + }); + // @ts-expect-error need to come up with a way to mock the return type of useRequest + mockUseRequest.mockReturnValue({ + mutate: jest.fn(), + }); + const { mutate } = mockUseRequest('topup_virtual'); + const setActiveTabIndex = jest.fn(); + + render(, { + wrapper: ({ children }) => ( + + {children} + + ), + }); + + const reset_balance_button = screen.getByRole('button', { name: /Reset balance/i }); + userEvent.click(reset_balance_button); + expect(mutate).toBeCalledTimes(1); + }); + + it('should change tab when click on transfer funds button', () => { + const mock = mockStore({}); + // @ts-expect-error need to come up with a way to mock the return type of useRequest + mockUseRequest.mockReturnValue({ + isSuccess: true, + }); + const setActiveTabIndex = jest.fn(); + + render(, { + wrapper: ({ children }) => ( + + {children} + + ), + }); + + const transfer_funds_button = screen.getByRole('button', { name: /Transfer funds/i }); + + userEvent.click(transfer_funds_button); + expect(setActiveTabIndex).toBeCalledTimes(1); + }); + + it('should show success message and transfer funds button if reset balance is reset successfully', () => { + const mock = mockStore({}); + // @ts-expect-error need to come up with a way to mock the return type of useRequest + mockUseRequest.mockReturnValue({ + isSuccess: true, + }); + const setActiveTabIndex = jest.fn(); + + render(, { + wrapper: ({ children }) => ( + + {children} + + ), + }); + + expect(screen.getByText('Your balance has been reset to 10,000.00 USD.')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /Transfer funds/i })).toBeInTheDocument(); + }); +}); diff --git a/packages/appstore/src/components/demo-reset-balance/demo-reset-balance.scss b/packages/appstore/src/components/demo-reset-balance/demo-reset-balance.scss new file mode 100644 index 000000000000..82243f29aec9 --- /dev/null +++ b/packages/appstore/src/components/demo-reset-balance/demo-reset-balance.scss @@ -0,0 +1,20 @@ +.reset-balance { + height: 100%; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + + &__title, + &__button { + margin-top: 2.4rem; + + @include mobile { + margin-top: 1.6rem; + } + } + + &__text { + margin-top: 0.8rem; + } +} diff --git a/packages/appstore/src/components/demo-reset-balance/demo-reset-balance.tsx b/packages/appstore/src/components/demo-reset-balance/demo-reset-balance.tsx new file mode 100644 index 000000000000..3294963d2d58 --- /dev/null +++ b/packages/appstore/src/components/demo-reset-balance/demo-reset-balance.tsx @@ -0,0 +1,82 @@ +import React from 'react'; +import { Icon, Text, Button, Div100vhContainer } from '@deriv/components'; +import { Localize } from '@deriv/translations'; +import { useRequest } from '@deriv/api'; +import { observer, useStore } from '@deriv/stores'; +import './demo-reset-balance.scss'; + +type TDemoResetBalanceProps = { + setActiveTabIndex?: (index: number) => void; +}; + +const DemoResetBalance = observer(({ setActiveTabIndex }: TDemoResetBalanceProps) => { + const { mutate, isSuccess: isResetBalanceSuccess } = useRequest('topup_virtual'); + const { client, ui } = useStore(); + const { accounts, loginid } = client; + const { is_mobile } = ui; + + const can_reset_balance = loginid && (accounts[loginid]?.balance || 0) !== 10000; + + const resetBalance = () => { + mutate(); + }; + + const redirectToTransferTab = () => { + setActiveTabIndex?.(0); + }; + + return ( + +
+ + + + {isResetBalanceSuccess ? ( + + ) : ( + + )} + + + + {isResetBalanceSuccess ? ( + + ) : ( + + )} + + + {isResetBalanceSuccess ? ( + + ) : ( + + )} +
+
+ ); +}); + +export default DemoResetBalance; diff --git a/packages/appstore/src/components/demo-reset-balance/index.ts b/packages/appstore/src/components/demo-reset-balance/index.ts new file mode 100644 index 000000000000..0bb492bb7d66 --- /dev/null +++ b/packages/appstore/src/components/demo-reset-balance/index.ts @@ -0,0 +1,3 @@ +import DemoResetBalance from './demo-reset-balance'; + +export default DemoResetBalance; diff --git a/packages/appstore/src/components/elements/text/__tests__/balance-text.spec.tsx b/packages/appstore/src/components/elements/text/__tests__/balance-text.spec.tsx index cdc113b5b034..00616cfbe344 100644 --- a/packages/appstore/src/components/elements/text/__tests__/balance-text.spec.tsx +++ b/packages/appstore/src/components/elements/text/__tests__/balance-text.spec.tsx @@ -3,16 +3,18 @@ import BalanceText from '../balance-text'; import { render, screen } from '@testing-library/react'; import { StoreProvider, mockStore } from '@deriv/stores'; +const createWrapper = (mock: ReturnType) => { + const wrapper = ({ children }: { children: JSX.Element }) => {children}; + + return wrapper; +}; + describe('BalanceText', () => { it('should render the component', () => { const mock = mockStore({}); - const wrapper = ({ children }: { children: JSX.Element }) => ( - {children} - ); - const { container } = render(, { - wrapper, + wrapper: createWrapper(mock), }); expect(container).toBeInTheDocument(); }); @@ -20,12 +22,8 @@ describe('BalanceText', () => { it('should render the correct balance and currency', () => { const mock = mockStore({}); - const wrapper = ({ children }: { children: JSX.Element }) => ( - {children} - ); - const { container } = render(, { - wrapper, + wrapper: createWrapper(mock), }); expect(container).toBeInTheDocument(); expect(screen.getByText('1,000.00')).toBeInTheDocument(); @@ -35,15 +33,11 @@ describe('BalanceText', () => { it('should render the correct div class for dotted underline_style', () => { const mock = mockStore({}); - const wrapper = ({ children }: { children: JSX.Element }) => ( - {children} - ); - const { container } = render(, { - wrapper, + wrapper: createWrapper(mock), }); expect(container).toBeInTheDocument(); - expect(screen.getByTestId('dt_balance-text__container')).toHaveClass('balance-text--dotted'); + expect(screen.getByTestId('dt_balance_text_container')).toHaveClass('balance-text--dotted'); }); it('should have classname ending with demo if user has selected_account_type demo and has an active real account ', () => { @@ -56,12 +50,8 @@ describe('BalanceText', () => { }, }); - const wrapper = ({ children }: { children: JSX.Element }) => ( - {children} - ); - const { container } = render(, { - wrapper, + wrapper: createWrapper(mock), }); expect(container).toBeInTheDocument(); expect(screen.getByText('1,000.00')).toHaveClass('balance-text__text--demo'); @@ -77,12 +67,8 @@ describe('BalanceText', () => { }, }); - const wrapper = ({ children }: { children: JSX.Element }) => ( - {children} - ); - const { container } = render(, { - wrapper, + wrapper: createWrapper(mock), }); expect(container).toBeInTheDocument(); expect(screen.getByText('1,000.00')).toHaveClass('balance-text__text--real'); @@ -95,12 +81,8 @@ describe('BalanceText', () => { }, }); - const wrapper = ({ children }: { children: JSX.Element }) => ( - {children} - ); - const { container } = render(, { - wrapper, + wrapper: createWrapper(mock), }); expect(container).toBeInTheDocument(); expect(screen.getByText('1,000.00')).not.toHaveClass('balance-text__text--real'); @@ -110,14 +92,10 @@ describe('BalanceText', () => { it('should have classname as container if underline_style is none', () => { const mock = mockStore({}); - const wrapper = ({ children }: { children: JSX.Element }) => ( - {children} - ); - const { container } = render(, { - wrapper, + wrapper: createWrapper(mock), }); expect(container).toBeInTheDocument(); - expect(screen.getByTestId('dt_balance-text__container')).toHaveClass('balance-text__container'); + expect(screen.getByTestId('dt_balance_text_container')).toHaveClass('balance-text__container'); }); }); diff --git a/packages/appstore/src/components/elements/text/balance-text.tsx b/packages/appstore/src/components/elements/text/balance-text.tsx index 1c08fe0b1884..cb9a5996578b 100644 --- a/packages/appstore/src/components/elements/text/balance-text.tsx +++ b/packages/appstore/src/components/elements/text/balance-text.tsx @@ -2,7 +2,7 @@ import React from 'react'; import classNames from 'classnames'; import { Text } from '@deriv/components'; import { formatMoney } from '@deriv/shared'; -import { useStore, observer } from '@deriv/stores'; +import { observer, useStore } from '@deriv/stores'; import './balance-text.scss'; // Todo: this definitely needs to be somewhere else @@ -32,7 +32,7 @@ const BalanceText = observer(({ balance, currency, size = 'm', underline_style = return (
{formatMoney(currency, balance, true)} diff --git a/packages/appstore/src/components/eu-disclaimer/__tests__/eu-disclaimer.spec.tsx b/packages/appstore/src/components/eu-disclaimer/__tests__/eu-disclaimer.spec.tsx new file mode 100644 index 000000000000..cab7453ebbf8 --- /dev/null +++ b/packages/appstore/src/components/eu-disclaimer/__tests__/eu-disclaimer.spec.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { mockStore, StoreProvider } from '@deriv/stores'; +import EUDisclaimer from '../eu-disclaimer'; + +const mockedRootStore = mockStore({}); + +describe('', () => { + it('Check disclaimer exists', () => { + render( + + + + ); + + const eu_statutory_disclaimer = screen.getByText('EU statutory disclaimer'); + + expect(eu_statutory_disclaimer).toBeInTheDocument(); + }); + + it('Check disclaimer for wallets exists', () => { + render( + + + + ); + + const eu_statutory_disclaimer = screen.queryByText('EU statutory disclaimer'); + + expect(eu_statutory_disclaimer).not.toBeInTheDocument(); + }); + + it('Check classes when dont pass the props', () => { + render( + + + + ); + + const wrapper = screen.getByTestId('dt_disclaimer_wrapper'); + const text = screen.getByTestId('dt_disclaimer_text'); + + expect(wrapper).toHaveClass('disclaimer'); + expect(text).toHaveClass('disclaimer-text'); + }); + + it('Check classes when pass the props', () => { + render( + + + + ); + + const wrapper = screen.getByTestId('dt_disclaimer_wrapper'); + const text = screen.getByTestId('dt_disclaimer_text'); + + expect(wrapper).not.toHaveClass('disclaimer'); + expect(text).not.toHaveClass('disclaimer-text'); + expect(wrapper).toHaveClass('wrapper-class'); + expect(text).toHaveClass('text-class'); + }); +}); diff --git a/packages/appstore/src/components/eu-disclaimer/eu-disclaimer.scss b/packages/appstore/src/components/eu-disclaimer/eu-disclaimer.scss new file mode 100644 index 000000000000..65a239cee975 --- /dev/null +++ b/packages/appstore/src/components/eu-disclaimer/eu-disclaimer.scss @@ -0,0 +1,24 @@ +.disclaimer { + position: fixed; + bottom: 3.6rem; + width: 100%; + height: 5rem; + z-index: 3; + display: flex; + align-items: center; + backface-visibility: hidden; + background: var(--icon-grey-background); + + @include mobile { + height: 8rem; + border: 1px solid var(--icon-grey-background); + } + + &-text { + padding: 0 3rem; + + @include mobile { + padding: 0 1.5rem; + } + } +} diff --git a/packages/appstore/src/components/eu-disclaimer/eu-disclaimer.tsx b/packages/appstore/src/components/eu-disclaimer/eu-disclaimer.tsx new file mode 100644 index 000000000000..452de6a693e3 --- /dev/null +++ b/packages/appstore/src/components/eu-disclaimer/eu-disclaimer.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { Text } from '@deriv/components'; +import { Localize } from '@deriv/translations'; +import { useStore, observer } from '@deriv/stores'; +import './eu-disclaimer.scss'; + +type TEUDisclaimerProps = { + is_wallet?: boolean; + wrapperClassName?: string; + textClassName?: string; +}; +type TDisclaimerLocalizedText = { + is_wallet?: boolean; +}; + +const DisclaimerLocalizedText = ({ is_wallet }: TDisclaimerLocalizedText) => + is_wallet ? ( + 73% of retail investor accounts lose money when trading CFDs with this provider. You should consider whether you understand how CFDs work and whether you can afford to take the high risk of losing your money.' + } + components={[]} + /> + ) : ( + EU statutory disclaimer: CFDs are complex instruments and come with a high risk of losing money rapidly due to leverage. <0>73% of retail investor accounts lose money when trading CFDs with this provider. You should consider whether you understand how CFDs work and whether you can afford to take the high risk of losing your money.' + } + components={[]} + /> + ); + +const EUDisclaimer = observer(({ is_wallet, wrapperClassName, textClassName }: TEUDisclaimerProps) => { + const { + ui: { is_mobile }, + } = useStore(); + + return ( +
+ + + +
+ ); +}); + +export default EUDisclaimer; diff --git a/packages/appstore/src/components/eu-disclaimer/index.ts b/packages/appstore/src/components/eu-disclaimer/index.ts new file mode 100644 index 000000000000..26dd46512928 --- /dev/null +++ b/packages/appstore/src/components/eu-disclaimer/index.ts @@ -0,0 +1,3 @@ +import EUDisclaimer from './eu-disclaimer'; + +export default EUDisclaimer; diff --git a/packages/appstore/src/components/main-title-bar/__tests__/index.spec.tsx b/packages/appstore/src/components/main-title-bar/__tests__/index.spec.tsx index 1525ed6aab21..13cdd09da0f9 100644 --- a/packages/appstore/src/components/main-title-bar/__tests__/index.spec.tsx +++ b/packages/appstore/src/components/main-title-bar/__tests__/index.spec.tsx @@ -1,14 +1,65 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import { StoreProvider, mockStore } from '@deriv/stores'; +import { APIProvider /*useFetch*/ } from '@deriv/api'; import MainTitleBar from '..'; -describe('MainTitleBar', () => { - const render_container = () => { - const mock = mockStore({}); +//TODO: Uncomment once useWalletMigration hook is optimized for production release. +// jest.mock('Components/wallets-banner', () => jest.fn(() => 'WalletsBanner')); +// const mockUseFetch = useFetch as jest.MockedFunction>; + +jest.mock('@deriv/api', () => ({ + ...jest.requireActual('@deriv/api'), + useFetch: jest.fn((name: string) => { + if (name === 'authorize') { + return { + data: { + authorize: { + account_list: [ + { + account_category: 'wallet', + currency: 'USD', + is_virtual: 0, + }, + ], + }, + }, + }; + } else if (name === 'balance') { + return { + data: { + balance: { + accounts: { + CRW909900: { + balance: 1000, + }, + }, + }, + }, + }; + } else if (name === 'website_status') { + return { + data: { + website_status: { + currencies_config: { + USD: { type: 'fiat' }, + }, + }, + }, + }; + } + + return undefined; + }), +})); - const wrapper = ({ children }: { children: JSX.Element }) => ( - {children} +describe('MainTitleBar', () => { + const render_container = (mock_store_override?: ReturnType) => { + const mock_store = mockStore({ feature_flags: { data: { wallet: false } } }); + const wrapper = ({ children }: React.PropsWithChildren) => ( + + {children} + ); return render(, { @@ -21,6 +72,36 @@ describe('MainTitleBar', () => { expect(container).toBeInTheDocument(); }); + //TODO: Uncomment once useWalletMigration hook is optimized for production release. + // it('should not render WalletsBanner component if wallet feature flag is disabled', () => { + // render_container(); + // expect(screen.queryByText('WalletsBanner')).not.toBeInTheDocument(); + // }); + + //TODO: Uncomment once useWalletMigration hook is optimized for production release. + // it('should render WalletsBanner component if wallet feature flag is enabled', () => { + // const mock_store = mockStore({ + // client: { accounts: { CR123456: { token: '12345' } }, loginid: 'CR123456' }, + // feature_flags: { data: { wallet: true } }, + // }); + // // @ts-expect-error need to come up with a way to mock the return type of useFetch + // mockUseFetch.mockReturnValue({ + // data: { + // authorize: { + // account_list: [ + // { + // account_category: 'trading', + // currency: 'USD', + // is_virtual: 0, + // }, + // ], + // }, + // }, + // }); + // render_container(mock_store); + // expect(screen.getByText('WalletsBanner')).toBeInTheDocument(); + // }); + it('should render the correct title text', () => { render_container(); expect(screen.getByText(/Trader's Hub/)).toBeInTheDocument(); diff --git a/packages/appstore/src/components/main-title-bar/index.tsx b/packages/appstore/src/components/main-title-bar/index.tsx index de0ea7fd2dd6..a7ebde37f2ea 100644 --- a/packages/appstore/src/components/main-title-bar/index.tsx +++ b/packages/appstore/src/components/main-title-bar/index.tsx @@ -1,18 +1,21 @@ import React from 'react'; import { Text, DesktopWrapper, MobileWrapper, Tabs, Icon } from '@deriv/components'; +// import { useFeatureFlags } from '@deriv/hooks'; import { ContentFlag } from '@deriv/shared'; import { observer, useStore } from '@deriv/stores'; +import { Localize, localize } from '@deriv/translations'; +import RegulationsSwitcherLoader from 'Components/pre-loader/regulations-switcher-loader'; +// import WalletsBanner from 'Components/wallets-banner'; import AccountTypeDropdown from './account-type-dropdown'; import AssetSummary from './asset-summary'; import RegulatorSwitcher from './regulators-switcher'; -import { localize } from '@deriv/translations'; import './main-title-bar.scss'; -import RegulationsSwitcherLoader from 'Components/pre-loader/regulations-switcher-loader'; const MainTitleBar = () => { - const { traders_hub, client } = useStore(); + const { traders_hub, client, notifications } = useStore(); const { selected_region, handleTabItemClick, toggleRegulatorsCompareModal, content_flag } = traders_hub; const { is_landing_company_loaded, is_switching } = client; + const { removeAllNotificationMessages, filterNotificationMessages } = notifications; const is_low_risk_cr_real_account = content_flag === ContentFlag.LOW_RISK_CR_NON_EU || content_flag === ContentFlag.LOW_RISK_CR_EU; @@ -21,13 +24,26 @@ const MainTitleBar = () => { setActiveIndex(selected_region === 'Non-EU' ? 0 : 1); }, [selected_region]); + // TODO: Uncomment once useWalletMigration hook is optimized for production release. + // const { is_wallet_enabled } = useFeatureFlags(); + + // TODO: Remove this when we have BE API ready + removeAllNotificationMessages(true); + + React.useEffect(() => { + filterNotificationMessages(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + return ( + {/* TODO: Uncomment once useWalletMigration hook is optimized for production release. */} + {/* {is_wallet_enabled && } */}
- {localize("Trader's Hub")} +
@@ -37,7 +53,7 @@ const MainTitleBar = () => { - {localize("Trader's Hub")} +
diff --git a/packages/appstore/src/components/modals/modal-manager.tsx b/packages/appstore/src/components/modals/modal-manager.tsx index a80d73360f20..b35cec7b0fd9 100644 --- a/packages/appstore/src/components/modals/modal-manager.tsx +++ b/packages/appstore/src/components/modals/modal-manager.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { observer } from 'mobx-react-lite'; import { ResetTradingPasswordModal } from '@deriv/account'; +import { useFeatureFlags } from '@deriv/hooks'; import { TTradingPlatformAvailableAccount } from './account-type-modal/types'; import MT5AccountTypeModal from './account-type-modal'; import RegulatorsCompareModal from './regulators-compare-modal'; @@ -18,6 +19,9 @@ import { TOpenAccountTransferMeta } from 'Types'; import { DetailsOfEachMT5Loginid } from '@deriv/api-types'; import FailedVerificationModal from './failed-veriification-modal'; import AccountTransferModal from 'Components/account-transfer-modal'; +import RealWalletsUpgrade from './real-wallets-upgrade/real-wallets-upgrade'; +import WalletsMigrationFailed from './wallets-migration-failed'; +import WalletModal from './wallet-modal'; type TCurrentList = DetailsOfEachMT5Loginid & { enabled: number; @@ -25,6 +29,7 @@ type TCurrentList = DetailsOfEachMT5Loginid & { const ModalManager = () => { const store = useStores(); + const { is_wallet_enabled } = useFeatureFlags(); const { common, client, modules, traders_hub, ui } = store; const { is_logged_in, @@ -53,7 +58,8 @@ const ModalManager = () => { is_reset_trading_password_modal_visible, setResetTradingPasswordModalOpen, } = ui; - const { is_demo, is_account_transfer_modal_open, toggleAccountTransferModal } = traders_hub; + const { is_demo, is_account_transfer_modal_open, toggleAccountTransferModal, is_real_wallets_upgrade_on } = + traders_hub; const [password_manager, setPasswordManager] = React.useState<{ is_visible: boolean; @@ -166,6 +172,9 @@ const ModalManager = () => { toggleModal={toggleAccountTransferModal} /> + {is_real_wallets_upgrade_on && } + + {is_wallet_enabled && } ); }; diff --git a/packages/appstore/src/components/modals/real-wallets-upgrade/__tests__/real-wallets-upgrade.spec.tsx b/packages/appstore/src/components/modals/real-wallets-upgrade/__tests__/real-wallets-upgrade.spec.tsx new file mode 100644 index 000000000000..e9d77292b34e --- /dev/null +++ b/packages/appstore/src/components/modals/real-wallets-upgrade/__tests__/real-wallets-upgrade.spec.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import RealWalletsUpgrade from '../real-wallets-upgrade'; +import { render } from '@testing-library/react'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import { APIProvider } from '@deriv/api'; + +describe('', () => { + const wrapper = (mock: ReturnType) => { + const Component = ({ children }: { children: JSX.Element }) => ( + + {children} + + ); + return Component; + }; + it('should render the Modal', () => { + const mock = mockStore({ + traders_hub: { + is_real_wallets_upgrade_on: true, + toggleWalletsUpgrade: true, + }, + }); + + const { container } = render(, { wrapper: wrapper(mock) }); + + expect(container).toBeInTheDocument(); + }); + + it('should not render the Modal if is_real_wallets_upgrade_on is false', () => { + const mock = mockStore({ + traders_hub: { + is_real_wallets_upgrade_on: false, + toggleWalletsUpgrade: false, + }, + }); + + const { container } = render(, { wrapper: wrapper(mock) }); + + expect(container).toBeEmptyDOMElement(); + }); +}); diff --git a/packages/appstore/src/components/modals/real-wallets-upgrade/components/modal-elements/desktop-real-wallets-upgrade.tsx b/packages/appstore/src/components/modals/real-wallets-upgrade/components/modal-elements/desktop-real-wallets-upgrade.tsx new file mode 100644 index 000000000000..4bfa0ceb0c83 --- /dev/null +++ b/packages/appstore/src/components/modals/real-wallets-upgrade/components/modal-elements/desktop-real-wallets-upgrade.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { Modal } from '@deriv/components'; +import { useStore, observer } from '@deriv/stores'; +import WalletsUpgradeFooter from './wallets-upgrade-footer'; +import WalletsUpgradeContent from './wallets-upgrade-content'; +import { TRealWalletsUpgradeSteps } from 'Types'; + +const DesktopRealWalletsUpgrade = observer(({ wallet_upgrade_steps }: TRealWalletsUpgradeSteps) => { + const { traders_hub: is_real_wallets_upgrade_on } = useStore(); + + const { handleClose } = wallet_upgrade_steps; + + return ( + + + + + + + ); +}); + +export default DesktopRealWalletsUpgrade; diff --git a/packages/appstore/src/components/modals/real-wallets-upgrade/components/modal-elements/index.ts b/packages/appstore/src/components/modals/real-wallets-upgrade/components/modal-elements/index.ts new file mode 100644 index 000000000000..23ebba8288de --- /dev/null +++ b/packages/appstore/src/components/modals/real-wallets-upgrade/components/modal-elements/index.ts @@ -0,0 +1,4 @@ +import DesktopRealWalletsUpgrade from './desktop-real-wallets-upgrade'; +import MobileRealWalletsUpgrade from './mobile-real-wallets-upgrade'; + +export { DesktopRealWalletsUpgrade, MobileRealWalletsUpgrade }; diff --git a/packages/appstore/src/components/modals/real-wallets-upgrade/components/modal-elements/mobile-real-wallets-upgrade.tsx b/packages/appstore/src/components/modals/real-wallets-upgrade/components/modal-elements/mobile-real-wallets-upgrade.tsx new file mode 100644 index 000000000000..4991c830864f --- /dev/null +++ b/packages/appstore/src/components/modals/real-wallets-upgrade/components/modal-elements/mobile-real-wallets-upgrade.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { MobileDialog, Modal } from '@deriv/components'; +import { observer, useStore } from '@deriv/stores'; +import { TRealWalletsUpgradeSteps } from 'Types'; +import WalletsUpgradeContent from './wallets-upgrade-content'; +import WalletsUpgradeFooter from './wallets-upgrade-footer'; + +const MobileRealWalletsUpgrade = observer(({ wallet_upgrade_steps }: TRealWalletsUpgradeSteps) => { + const { traders_hub: is_real_wallets_upgrade_on } = useStore(); + + return ( + } + > + + + + + ); +}); + +export default MobileRealWalletsUpgrade; diff --git a/packages/appstore/src/components/modals/real-wallets-upgrade/components/modal-elements/wallet_steps.tsx b/packages/appstore/src/components/modals/real-wallets-upgrade/components/modal-elements/wallet_steps.tsx new file mode 100644 index 000000000000..78b4b571ed05 --- /dev/null +++ b/packages/appstore/src/components/modals/real-wallets-upgrade/components/modal-elements/wallet_steps.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { WalletsIntro } from '../wallets-intro'; +import { EndFooter, InitialFooter } from './wallets-upgrade-footer'; +import WalletLinkingStep from '../wallet-linking-step/wallet-linking-step'; +import ReadyToUpgradeWallets from '../ready-to-upgrade-wallets'; +import getMockWalletMigrationResponse from 'Constants/mock_wallet_migration_response'; +import { TWalletSteps } from 'Types'; + +const WalletSteps = ({ + handleBack, + handleClose, + handleNext, + is_disabled, + toggleCheckbox, + upgradeToWallets, +}: TWalletSteps) => [ + { + name: 'intro_wallets', + component: , + footer: , + }, + { + name: 'intro_wallets', + component: , + }, + { + name: 'intro_wallets', + component: , + }, + { + name: 'linking_step', + component: , + }, + { + name: 'linking_step', + component: , + }, + { + name: 'linking_step', + component: , + }, + { + name: 'ready_to_upgrade', + component: , + footer: , + }, +]; + +export default WalletSteps; diff --git a/packages/appstore/src/components/modals/real-wallets-upgrade/components/modal-elements/wallets-upgrade-content.tsx b/packages/appstore/src/components/modals/real-wallets-upgrade/components/modal-elements/wallets-upgrade-content.tsx new file mode 100644 index 000000000000..9549d9b3d97c --- /dev/null +++ b/packages/appstore/src/components/modals/real-wallets-upgrade/components/modal-elements/wallets-upgrade-content.tsx @@ -0,0 +1,12 @@ +import WalletSteps from './wallet_steps'; +import { TRealWalletsUpgradeSteps } from 'Types'; + +const WalletsUpgradeContent = ({ wallet_upgrade_steps }: TRealWalletsUpgradeSteps) => { + const wallet_steps_array = WalletSteps({ ...wallet_upgrade_steps }); + + const { current_step } = wallet_upgrade_steps; + + return wallet_steps_array?.[current_step]?.component || wallet_steps_array?.[0].component; +}; + +export default WalletsUpgradeContent; diff --git a/packages/appstore/src/components/modals/real-wallets-upgrade/components/modal-elements/wallets-upgrade-footer.tsx b/packages/appstore/src/components/modals/real-wallets-upgrade/components/modal-elements/wallets-upgrade-footer.tsx new file mode 100644 index 000000000000..d23350524844 --- /dev/null +++ b/packages/appstore/src/components/modals/real-wallets-upgrade/components/modal-elements/wallets-upgrade-footer.tsx @@ -0,0 +1,72 @@ +import React from 'react'; +import { Button, Modal } from '@deriv/components'; +import { Localize } from '@deriv/translations'; +import WalletSteps from './wallet_steps'; +import { TRealWalletsUpgradeSteps } from 'Types'; + +type TDefaultFooter = { + handleBack: () => void; + handleNext: () => void; +}; + +type TInitialFooter = { + handleClose: () => void; + handleNext: () => void; +}; + +type TEndFooter = { + handleBack: () => void; + is_disabled: boolean; + upgradeToWallets: (value: boolean) => void; +}; + +export const DefaultFooter = ({ handleBack, handleNext }: TDefaultFooter) => ( + + + + +); + +export const InitialFooter = ({ handleClose, handleNext }: TInitialFooter) => ( + + + + +); + +export const EndFooter = ({ handleBack, is_disabled, upgradeToWallets }: TEndFooter) => ( + + + + +); + +const WalletsUpgradeFooter = ({ wallet_upgrade_steps }: TRealWalletsUpgradeSteps) => { + const wallet_steps_array = WalletSteps({ ...wallet_upgrade_steps }); + + const { current_step, handleBack, handleNext } = wallet_upgrade_steps; + + return ( + wallet_steps_array?.[current_step]?.footer || + ); +}; + +export default WalletsUpgradeFooter; diff --git a/packages/appstore/src/components/modals/real-wallets-upgrade/components/ready-to-upgrade-wallets/index.ts b/packages/appstore/src/components/modals/real-wallets-upgrade/components/ready-to-upgrade-wallets/index.ts new file mode 100644 index 000000000000..0fee66b7ca8d --- /dev/null +++ b/packages/appstore/src/components/modals/real-wallets-upgrade/components/ready-to-upgrade-wallets/index.ts @@ -0,0 +1,3 @@ +import ReadyToUpgradeWallets from './ready-to-upgrade-wallets'; + +export default ReadyToUpgradeWallets; diff --git a/packages/appstore/src/components/modals/real-wallets-upgrade/components/ready-to-upgrade-wallets/ready-to-upgrade-wallets.scss b/packages/appstore/src/components/modals/real-wallets-upgrade/components/ready-to-upgrade-wallets/ready-to-upgrade-wallets.scss new file mode 100644 index 000000000000..a2a96aa021e9 --- /dev/null +++ b/packages/appstore/src/components/modals/real-wallets-upgrade/components/ready-to-upgrade-wallets/ready-to-upgrade-wallets.scss @@ -0,0 +1,48 @@ +.wallet-steps { + &__text { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 0.8rem; + } + + &__image { + position: relative; + left: 1.5rem; + } + + &__info-section { + padding: 1.6rem; + margin-top: 2.4rem; + background-color: var(--transparent-info); + border-radius: 0.8rem; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: center; + gap: 0.8rem; + + @include mobile { + margin-top: 1.6rem; + } + + &-text { + display: grid; + grid-template-columns: 1.6rem 1fr; + grid-column-gap: 0.8rem; + align-items: center; + } + } + + &__checkbox { + margin-top: 2.4rem; + display: flex; + justify-content: center; + .dc-checkbox__label { + @include mobile { + font-size: var(--text-size-xxs); + } + } + } +} diff --git a/packages/appstore/src/components/modals/real-wallets-upgrade/components/ready-to-upgrade-wallets/ready-to-upgrade-wallets.tsx b/packages/appstore/src/components/modals/real-wallets-upgrade/components/ready-to-upgrade-wallets/ready-to-upgrade-wallets.tsx new file mode 100644 index 000000000000..197ee30202bd --- /dev/null +++ b/packages/appstore/src/components/modals/real-wallets-upgrade/components/ready-to-upgrade-wallets/ready-to-upgrade-wallets.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { Checkbox, Text, Icon } from '@deriv/components'; +import { Localize, localize } from '@deriv/translations'; +import { observer, useStore } from '@deriv/stores'; +import WalletsImage from 'Assets/svgs/wallets'; +import getUpgradeInformationList from 'Constants/upgrade-info-lists-config'; +import './ready-to-upgrade-wallets.scss'; +import { useContentFlag } from '@deriv/hooks'; + +type TReadyToUpgradeWallets = { + value: boolean; + toggleCheckbox: () => void; +}; + +const ReadyToUpgradeWallets = observer(({ value, toggleCheckbox }: TReadyToUpgradeWallets) => { + const { ui } = useStore(); + const { is_mobile } = ui; + const text_body_size = is_mobile ? 'xs' : 's'; + const text_info_size = is_mobile ? 'xxs' : 'xs'; + const form_line_height = is_mobile ? 'm' : 'l'; + + const { is_eu_demo, is_eu_real, is_low_risk_cr_eu } = useContentFlag(); + const is_eu = is_eu_demo || is_eu_real || is_low_risk_cr_eu; + + return ( +
+ +
+ + + + + } + /> + +
+
+ {getUpgradeInformationList({ is_eu, text_info_size, form_line_height }) + .filter(info => info.visibility) + .map(({ name, content }) => ( +
+ + + {content} + +
+ ))} +
+ +
+ ); +}); + +export default ReadyToUpgradeWallets; diff --git a/packages/appstore/src/components/modals/real-wallets-upgrade/components/ready-to-upgrade-wallets/test/ready-to-upgrade-wallets.spec.tsx b/packages/appstore/src/components/modals/real-wallets-upgrade/components/ready-to-upgrade-wallets/test/ready-to-upgrade-wallets.spec.tsx new file mode 100644 index 000000000000..40542909bddf --- /dev/null +++ b/packages/appstore/src/components/modals/real-wallets-upgrade/components/ready-to-upgrade-wallets/test/ready-to-upgrade-wallets.spec.tsx @@ -0,0 +1,93 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import ReadyToUpgradeWallets from '../ready-to-upgrade-wallets'; +import { StoreProvider, mockStore } from '@deriv/stores'; + +describe('ReadyToUpgradeWallets', () => { + const containerReadyToUpgradeWallets = (mock: ReturnType) => { + const toggleCheckbox = jest.fn(); + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + return render(, { + wrapper, + }); + }; + + it('should render ReadyToUpgradeWallets component', () => { + const mock = mockStore({}); + const { container } = containerReadyToUpgradeWallets(mock); + + expect(container).toBeInTheDocument(); + }); + + it('should render checkbox', () => { + const mock = mockStore({}); + containerReadyToUpgradeWallets(mock); + + expect(screen.getByRole('checkbox')).toBeInTheDocument(); + }); + + it('should render proper info sections for non-eu user', () => { + const mock = mockStore({}); + containerReadyToUpgradeWallets(mock); + + expect( + screen.getByText( + 'During the upgrade, deposits, withdrawals, transfers, and adding new accounts will be unavailable.' + ) + ).toBeInTheDocument(); + expect( + screen.getByText("Your open positions won't be affected and you can continue trading.") + ).toBeInTheDocument(); + expect(screen.getByText(/you can use/i)).toBeInTheDocument(); + expect(screen.getByText(/payment agents'/i)).toBeInTheDocument(); + expect( + screen.getByText(/services to deposit by adding a Payment Agent Wallet after the upgrade./i) + ).toBeInTheDocument(); + expect(screen.getByText(/Deriv P2P/)).toBeInTheDocument(); + expect(screen.getByText(/is unavailable in Wallets at this time/i)).toBeInTheDocument(); + }); + + describe('should render proper info sections for eu user with different content flags', () => { + const validateAssertion = () => { + expect( + screen.getByText( + 'During the upgrade, deposits, withdrawals, transfers, and adding new accounts will be unavailable.' + ) + ).toBeInTheDocument(); + expect( + screen.getByText("Your open positions won't be affected and you can continue trading.") + ).toBeInTheDocument(); + expect(screen.queryByText(/you can use/i)).not.toBeInTheDocument(); + expect(screen.queryByText(/payment agents'/i)).not.toBeInTheDocument(); + expect( + screen.queryByText(/services to deposit by adding a Payment Agent Wallet after the upgrade./i) + ).not.toBeInTheDocument(); + expect(screen.queryByText(/Deriv P2P/)).not.toBeInTheDocument(); + expect(screen.queryByText(/is unavailable in Wallets at this time/i)).not.toBeInTheDocument(); + }; + + it('should render proper info sections for eu user with eu_demo content flag', () => { + const mock = mockStore({ traders_hub: { content_flag: 'eu_demo' } }); + containerReadyToUpgradeWallets(mock); + + validateAssertion(); + }); + + it('should render proper info sections for eu user with eu_real content flag', () => { + const mock = mockStore({ traders_hub: { content_flag: 'eu_real' } }); + containerReadyToUpgradeWallets(mock); + + validateAssertion(); + }); + + it('should render proper info sections for eu user with low_risk_cr_eu content flag', () => { + const mock = mockStore({ traders_hub: { content_flag: 'low_risk_cr_eu' } }); + containerReadyToUpgradeWallets(mock); + + validateAssertion(); + }); + }); +}); diff --git a/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallet-account/wallet-account.scss b/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallet-account/wallet-account.scss new file mode 100644 index 000000000000..079da1291c28 --- /dev/null +++ b/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallet-account/wallet-account.scss @@ -0,0 +1,25 @@ +.wallet-account { + width: 28.8rem; + height: 4.8rem; + display: flex; + align-items: center; + justify-content: flex-start; + border: 1px solid $color-grey-2; + border-radius: 0.8rem; + + @include mobile { + width: 24.4rem; + } + + &__details { + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: center; + margin-left: 1.6rem; + } + + &__icon { + margin-left: 1.6rem; + } +} diff --git a/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallet-account/wallet-account.tsx b/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallet-account/wallet-account.tsx new file mode 100644 index 000000000000..e092d2cf7b7f --- /dev/null +++ b/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallet-account/wallet-account.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { Icon, Text } from '@deriv/components'; +import './wallet-account.scss'; + +type TWalletAccount = { + balance: number; + currency: string; + name: string; + icon: string; +}; + +const WalletAccount = ({ balance, name, currency, icon }: TWalletAccount) => ( +
+ +
+ + {name} + + + {balance} {currency} + +
+
+); + +export default WalletAccount; diff --git a/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallet-link/wallet-link-wrapper.scss b/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallet-link/wallet-link-wrapper.scss new file mode 100644 index 000000000000..846a4867ed8c --- /dev/null +++ b/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallet-link/wallet-link-wrapper.scss @@ -0,0 +1,148 @@ +.wallet-link-wrapper { + display: grid; + grid-template-columns: repeat(3, auto); + align-items: center; + justify-content: flex-start; + gap: 1.6rem; + + &__title { + display: flex; + align-items: center; + justify-content: center; + width: 28.8rem; + + &-text { + width: fit-content; + padding: 0.4rem 0.8rem; + border-radius: $BORDER_RADIUS * 6; + text-align: center; + background-color: var(--general-section-2); + + @include mobile { + background-color: var(--general-section-1); + } + } + } + + @include mobile { + width: 100%; + display: flex; + flex-direction: column; + gap: 1.2rem; + } + + &__accounts { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 1.6rem; + + @include mobile { + gap: 0.8rem; + + &-title { + width: 100%; + display: block; + border-radius: $BORDER_RADIUS_2 $BORDER_RADIUS_2 0 0; + } + } + } + + &__link { + height: 100%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + + @include mobile { + width: 100%; + flex-direction: column; + } + + &-bracket { + width: 1.5rem; + height: calc(100% - 4.8rem); + border: 1px solid var(--brand-red-coral); + border-left: none; + + &--single { + height: 0; + border: none; + border-top: 1px solid var(--brand-red-coral); + } + + @include mobile { + width: 24.4rem; + height: 0.8rem; + border-top: none; + } + } + + &-icon { + display: flex; + flex-direction: row; + align-items: center; + gap: 1.4rem; + + @include mobile { + flex-direction: column; + gap: 0.6rem; + } + + &:before, + &:after { + content: ''; + display: inline-block; + background: var(--brand-red-coral); + } + + &:before { + width: 4.8rem; + height: 0.1rem; + + @include mobile { + width: 0.1rem; + height: 1.6rem; + } + } + + &:after { + width: 6.3rem; + height: 0.1rem; + + @include mobile { + width: 0.1rem; + height: 2.4rem; + } + } + } + } + + &__card-wrapper { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + &:after { + display: inline-block; + content: ''; + } + + &-title { + display: block; + position: absolute; + bottom: 0; + width: 100%; + border-radius: 0 0 $BORDER_RADIUS_2 $BORDER_RADIUS_2; + } + + @include mobile { + display: block; + height: 14rem; + } + } +} diff --git a/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallet-link/wallet-link-wrapper.tsx b/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallet-link/wallet-link-wrapper.tsx new file mode 100644 index 000000000000..6602310076b8 --- /dev/null +++ b/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallet-link/wallet-link-wrapper.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import classNames from 'classnames'; +import { Icon, Text, WalletCard } from '@deriv/components'; +import { Localize } from '@deriv/translations'; +import WalletAccount from '../wallet-account/wallet-account'; +import { observer, useStore } from '@deriv/stores'; +import './wallet-link-wrapper.scss'; + +export type TWalletLinkWrapper = { + wallet_details: React.ComponentProps['wallet']; + account_list: { + balance: number; + currency: string; + account_name: string; + icon: string; + }[]; +}; + +const WalletLinkWrapper = observer(({ wallet_details, account_list }: TWalletLinkWrapper) => { + const { ui } = useStore(); + const { is_mobile } = ui; + return ( +
+
+ {is_mobile && ( + + + + )} + {account_list.map(account => { + return ( + + ); + })} +
+
+
+
+ +
+
+
+ + {is_mobile && ( + + + + )} +
+
+ ); +}); + +export default WalletLinkWrapper; diff --git a/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallet-linking-step/wallet-linking-step.scss b/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallet-linking-step/wallet-linking-step.scss new file mode 100644 index 000000000000..1f7a86dd5cd1 --- /dev/null +++ b/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallet-linking-step/wallet-linking-step.scss @@ -0,0 +1,60 @@ +.wallet-linking-step { + width: 100%; + height: calc(57.4rem); // 73.4rem (modal-height) - 16rem (~15.2rem height of header + footer + margin from top) + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + + @include mobile { + height: 100%; + margin: 4rem 0 7.4rem; + } + + &__description { + margin-top: 0.8rem; + } + + &__content { + display: flex; + flex-direction: column; + margin-top: 2rem; + padding: 0.4rem 1.6rem; + gap: 2.2rem; + + @include mobile { + gap: 3.2rem; + padding: 0.4rem 3.8rem; + } + } + + &__title-small { + width: 100%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + gap: 36.1rem; + margin-top: 5.6rem; + + &-text { + padding: 0.4rem 0.8rem; + border-radius: $BORDER_RADIUS * 6; + } + } + + &__note { + margin-top: 2.4rem; + padding: 0.8rem 1.6rem; + border-radius: $BORDER_RADIUS; + } + + &__title-text { + width: fit-content; + background-color: var(--general-section-2); + + @include mobile { + background-color: var(--general-section-1); + } + } +} diff --git a/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallet-linking-step/wallet-linking-step.tsx b/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallet-linking-step/wallet-linking-step.tsx new file mode 100644 index 000000000000..33fb901133a9 --- /dev/null +++ b/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallet-linking-step/wallet-linking-step.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { Text, ThemedScrollbars } from '@deriv/components'; +import { Localize } from '@deriv/translations'; +import { observer, useStore } from '@deriv/stores'; +import WalletLinkWrapper, { TWalletLinkWrapper } from '../wallet-link/wallet-link-wrapper'; +import './wallet-linking-step.scss'; + +type TWalletLinkingStep = { + data: { title: string; wallets: TWalletLinkWrapper[] }; +}; + +const WalletLinkingStep = observer(({ data }: TWalletLinkingStep) => { + const { ui } = useStore(); + const { is_mobile } = ui; + return ( +
+ + {data.title} + + + + + + + + {!is_mobile && ( +
+ + + + + + +
+ )} + + {data.wallets.map(({ wallet_details, account_list }) => { + return ( + + ); + })} + +
+ ); +}); + +export default WalletLinkingStep; diff --git a/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallets-intro/__tests__/wallets-intro.spec.tsx b/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallets-intro/__tests__/wallets-intro.spec.tsx new file mode 100644 index 000000000000..98b2c65a8a62 --- /dev/null +++ b/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallets-intro/__tests__/wallets-intro.spec.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import WalletsImage from 'Assets/svgs/wallets'; +import { WalletsIntroComponent } from '../wallets-intro'; +import { render, screen } from '@testing-library/react'; +import { StoreProvider, mockStore } from '@deriv/stores'; + +let mock = mockStore({}); + +const mocked_props = { + image: , + title: 'Upgrade To Wallets', + description: 'A better way to manage your funds', + bullets: ['Bullet 1', 'Bullet 2', 'Bullet 3'], +}; + +const checkContainerWalletsIntroComponent = () => { + const wrapper = ({ children }: { children: JSX.Element }) => {children}; + + const { container } = render(, { + wrapper, + }); + expect(container).toBeInTheDocument(); +}; + +describe('WalletsIntroComponent', () => { + beforeEach(() => { + mock = mockStore({}); + }); + it('should render Wallet Intro Component', () => { + checkContainerWalletsIntroComponent(); + }); + + it('should render icon', () => { + checkContainerWalletsIntroComponent(); + expect(screen.queryByTestId('dt_how_it_works')).toBeInTheDocument(); + }); + + it('should render title, description and bullets', () => { + checkContainerWalletsIntroComponent(); + expect(screen.getByText(mocked_props.title)).toBeInTheDocument(); + expect(screen.getByText(mocked_props.description)).toBeInTheDocument(); + mocked_props.bullets.forEach(bullet => { + expect(screen.getByText(bullet)).toBeInTheDocument(); + }); + }); +}); diff --git a/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallets-intro/index.ts b/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallets-intro/index.ts new file mode 100644 index 000000000000..4cfcc528ec65 --- /dev/null +++ b/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallets-intro/index.ts @@ -0,0 +1,3 @@ +import { WalletsIntro, WalletsIntroComponent } from './wallets-intro'; + +export { WalletsIntro, WalletsIntroComponent }; diff --git a/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallets-intro/wallets-intro.scss b/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallets-intro/wallets-intro.scss new file mode 100644 index 000000000000..3f94b7c93e78 --- /dev/null +++ b/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallets-intro/wallets-intro.scss @@ -0,0 +1,54 @@ +.wallet-steps { + &__title { + padding-top: 2rem; + + @include mobile { + font-size: var(--text-size-m); + } + } + + &__description { + padding: 0.5rem 0 1.5rem; + + @include mobile { + font-size: var(--text-size-sm); + } + } + + &__bullet { + display: flex; + align-items: center; + padding-bottom: 1rem; + text-align: center; + justify-content: center; + + @include mobile { + justify-content: left; + } + + &-points { + display: grid; + grid-template-columns: 1.6rem 1fr; + align-items: center; + gap: 0.8rem; + + @include mobile { + align-items: baseline; + } + } + + @include mobile { + justify-content: left; + } + + &-text { + @include mobile { + text-align: left; + } + } + + &-icon { + margin-right: 1rem; + } + } +} diff --git a/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallets-intro/wallets-intro.tsx b/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallets-intro/wallets-intro.tsx new file mode 100644 index 000000000000..9742af03357a --- /dev/null +++ b/packages/appstore/src/components/modals/real-wallets-upgrade/components/wallets-intro/wallets-intro.tsx @@ -0,0 +1,92 @@ +import React from 'react'; +import { Text, Icon } from '@deriv/components'; +import { observer, useStore } from '@deriv/stores'; +import getWalletsIntroContent from 'Constants/wallets-intro-content-config'; +import { useContentFlag } from '@deriv/hooks'; +import './wallets-intro.scss'; + +type TWalletsIntro = { + title: string; + description: string; + bullets: string[]; + eu_user?: boolean; + image?: React.ReactNode; +}; + +type TWalletsIntroComponent = { + current_step: number; +}; + +const WalletsIntroComponent = observer(({ image, title, description, bullets }: TWalletsIntro) => { + const { ui } = useStore(); + const { is_mobile } = ui; + + const text_title_size = is_mobile ? 'xsm' : 'l'; + const text_body_size = is_mobile ? 's' : 'm'; + const text_info_size = is_mobile ? 'xs' : 's'; + const form_line_height = is_mobile ? 'm' : 'l'; + + return ( + + {image} + + {title} + + + {description} + + {bullets.map(bullet => ( +
+ {bullet && ( +
+ + + {bullet} + +
+ )} +
+ ))} +
+ ); +}); + +const WalletsIntro = ({ current_step }: TWalletsIntroComponent) => { + const { is_eu_demo, is_eu_real, is_low_risk_cr_eu } = useContentFlag(); + const is_eu = is_eu_demo || is_eu_real || is_low_risk_cr_eu; + const step = getWalletsIntroContent(is_eu)?.[current_step] || []; + + return ( +
+ +
+ ); +}; + +export { WalletsIntro, WalletsIntroComponent }; diff --git a/packages/appstore/src/components/modals/real-wallets-upgrade/index.ts b/packages/appstore/src/components/modals/real-wallets-upgrade/index.ts new file mode 100644 index 000000000000..dc268ba30609 --- /dev/null +++ b/packages/appstore/src/components/modals/real-wallets-upgrade/index.ts @@ -0,0 +1,3 @@ +import RealWalletsUpgrade from './real-wallets-upgrade'; + +export default RealWalletsUpgrade; diff --git a/packages/appstore/src/components/modals/real-wallets-upgrade/real-wallets-upgrade.scss b/packages/appstore/src/components/modals/real-wallets-upgrade/real-wallets-upgrade.scss new file mode 100644 index 000000000000..2fc3b82c960b --- /dev/null +++ b/packages/appstore/src/components/modals/real-wallets-upgrade/real-wallets-upgrade.scss @@ -0,0 +1,34 @@ +.wallet-steps { + width: 100%; + height: 100%; + display: flex; + + @include mobile { + align-items: center; + } + + &__content { + max-width: 77.6rem; + margin: 5rem auto 0; + text-align: center; + + @include mobile { + margin: 2rem auto 8rem; + } + } + + &__footer { + width: 100%; + position: sticky; + bottom: 0; + background-color: inherit; + + @include mobile { + justify-content: center; + background: var(--fill-normal); + &-button { + width: 50%; + } + } + } +} diff --git a/packages/appstore/src/components/modals/real-wallets-upgrade/real-wallets-upgrade.tsx b/packages/appstore/src/components/modals/real-wallets-upgrade/real-wallets-upgrade.tsx new file mode 100644 index 000000000000..32f6d224104a --- /dev/null +++ b/packages/appstore/src/components/modals/real-wallets-upgrade/real-wallets-upgrade.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { useWalletMigration } from '@deriv/hooks'; +import { observer, useStore } from '@deriv/stores'; +import { DesktopRealWalletsUpgrade, MobileRealWalletsUpgrade } from './components/modal-elements'; +import './real-wallets-upgrade.scss'; + +const RealWalletsUpgrade = observer(() => { + const { traders_hub, ui } = useStore(); + const { is_real_wallets_upgrade_on, toggleWalletsUpgrade } = traders_hub; + const { is_mobile } = ui; + + const [current_step, setCurrentStep] = React.useState(0); + const [is_disabled, setIsDisabled] = React.useState(false); + + React.useEffect(() => { + if (!is_real_wallets_upgrade_on) { + setCurrentStep(0); + setIsDisabled(false); + } + }, [is_real_wallets_upgrade_on]); + + const handleNext = () => setCurrentStep(prev_step => prev_step + 1); + + const handleBack = () => setCurrentStep(prev_step => prev_step - 1); + + const handleClose = () => toggleWalletsUpgrade(false); + + const { start_migration } = useWalletMigration(); + + const upgradeToWallets = () => { + start_migration(); + toggleWalletsUpgrade(false); + }; + + const toggleCheckbox = () => { + setIsDisabled(prevDisabled => !prevDisabled); + }; + + const wallet_upgrade_steps = { + current_step, + handleBack, + handleClose, + handleNext, + is_disabled, + toggleCheckbox, + upgradeToWallets, + }; + + return ( + + {is_mobile ? ( + + ) : ( + + )} + + ); +}); + +export default RealWalletsUpgrade; diff --git a/packages/appstore/src/components/modals/wallet-modal/__tests__/wallet-modal-body.spec.tsx b/packages/appstore/src/components/modals/wallet-modal/__tests__/wallet-modal-body.spec.tsx new file mode 100644 index 000000000000..490a3e1efb79 --- /dev/null +++ b/packages/appstore/src/components/modals/wallet-modal/__tests__/wallet-modal-body.spec.tsx @@ -0,0 +1,119 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { BrowserRouter } from 'react-router-dom'; +import WalletModalBody from '../wallet-modal-body'; +import { mockStore, StoreProvider } from '@deriv/stores'; + +jest.mock('Components/wallet-transfer', () => jest.fn(() =>
WalletTransfer
)); +jest.mock('Components/transaction-list', () => jest.fn(() =>
Transactions
)); +jest.mock('Components/wallet-deposit', () => jest.fn(() =>
Deposit
)); + +describe('WalletModalBody', () => { + let mocked_props: React.ComponentProps; + + beforeEach(() => { + mocked_props = { + contentScrollHandler: jest.fn(), + is_dark: false, + is_mobile: false, + is_wallet_name_visible: true, + setIsWalletNameVisible: jest.fn(), + wallet: { + balance: 1000, + currency: 'USD', + currency_config: { + display_code: 'USD', + is_crypto: false, + } as typeof mocked_props['wallet']['currency_config'], + gradient_card_class: 'wallet-card__usd', + gradient_header_class: 'wallet-header__usd', + icon: '', + is_demo: true, + is_disabled: 0, + is_malta_wallet: false, + is_selected: true, + is_virtual: 1, + landing_company_name: 'svg', + wallet_currency_type: 'Demo', + }, + }; + }); + + const renderWithRouter = (component: JSX.Element) => { + render({component}); + }; + + it('Should render proper tabs for demo wallet', () => { + const mocked_store = mockStore({ + traders_hub: { + active_modal_tab: 'Transfer', + }, + }); + renderWithRouter( + + + + ); + + expect(screen.getByText('Transfer')).toBeInTheDocument(); + expect(screen.getByText('Transactions')).toBeInTheDocument(); + expect(screen.getByText('Reset balance')).toBeInTheDocument(); + }); + + it('Should render proper content under the Transfer tab', () => { + const mocked_store = mockStore({ + traders_hub: { + active_modal_tab: 'Transfer', + }, + }); + renderWithRouter( + + + + ); + + const el_transfer_tab = screen.getByText('Transfer'); + userEvent.click(el_transfer_tab); + + expect(screen.getByText('WalletTransfer')).toBeInTheDocument(); + }); + + it('Should trigger setWalletModalActiveTab callback when the user clicked on the tab', () => { + mocked_props.wallet.is_demo = false; + const mocked_store = mockStore({ + traders_hub: { + active_modal_tab: 'Deposit', + }, + }); + renderWithRouter( + + + + ); + + const el_transactions_tab = screen.getByText('Transactions'); + userEvent.click(el_transactions_tab); + + expect(mocked_store.traders_hub.setWalletModalActiveTab).toHaveBeenCalledTimes(1); + }); + + it('Should trigger contentScrollHandler callback when the user scrolls the content', () => { + mocked_props.wallet.is_demo = false; + const mocked_store = mockStore({ + traders_hub: { + active_modal_tab: 'Deposit', + }, + }); + renderWithRouter( + + + + ); + + const el_themed_scrollbars = screen.getByTestId('dt_themed_scrollbars'); + fireEvent.scroll(el_themed_scrollbars); + + expect(mocked_props.contentScrollHandler).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/appstore/src/components/modals/wallet-modal/__tests__/wallet-modal-header.spec.tsx b/packages/appstore/src/components/modals/wallet-modal/__tests__/wallet-modal-header.spec.tsx new file mode 100644 index 000000000000..c6911fe75bce --- /dev/null +++ b/packages/appstore/src/components/modals/wallet-modal/__tests__/wallet-modal-header.spec.tsx @@ -0,0 +1,79 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import WalletModalHeader from '../wallet-modal-header'; + +jest.mock('@deriv/hooks', () => ({ + useCurrencyConfig: () => ({ getConfig: () => ({ display_code: 'USD' }) }), +})); + +jest.mock('@deriv/api', () => ({ + ...jest.requireActual('@deriv/api'), + useFetch: jest.fn(() => ({ + data: { + website_status: { + currencies_config: { + USD: { + fractional_digits: 2, + is_deposit_suspended: 0, + is_suspended: 0, + is_withdrawal_suspended: 0, + name: 'US Dollar', + stake_default: 10, + type: 'fiat', + }, + }, + }, + }, + })), +})); + +describe('WalletModalHeader', () => { + let mocked_props: React.ComponentProps; + + beforeEach(() => { + mocked_props = { + closeModal: jest.fn(), + is_dark: false, + is_mobile: false, + is_wallet_name_visible: true, + wallet: { + balance: 1000, + currency: 'USD', + currency_config: { + display_code: 'USD', + is_crypto: false, + } as typeof mocked_props['wallet']['currency_config'], + gradient_card_class: 'wallet-card__usd', + gradient_header_class: 'wallet-header__usd', + icon: 'IcWalletIcon', + is_demo: true, + is_disabled: 0, + is_malta_wallet: false, + is_selected: true, + is_virtual: 1, + landing_company_name: 'svg', + wallet_currency_type: 'USD', + }, + }; + }); + + it('Should render header with proper title, balance, badge and icons', () => { + render(); + + expect(screen.getByText('USD Wallet')).toBeInTheDocument(); + expect(screen.getByText('Demo')).toBeInTheDocument(); + expect(screen.getByText('1,000.00 USD')).toBeInTheDocument(); + expect(screen.getByTestId('dt_wallet_icon')).toBeInTheDocument(); + expect(screen.getByTestId('dt_close_icon')).toBeInTheDocument(); + }); + + it('Should trigger onClose callback when the user clicked on the cross close button', () => { + render(); + + const el_close_btn = screen.getByTestId('dt_close_icon'); + userEvent.click(el_close_btn); + + expect(mocked_props.closeModal).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/appstore/src/components/modals/wallet-modal/__tests__/wallet-modal.spec.tsx b/packages/appstore/src/components/modals/wallet-modal/__tests__/wallet-modal.spec.tsx new file mode 100644 index 000000000000..741468feae89 --- /dev/null +++ b/packages/appstore/src/components/modals/wallet-modal/__tests__/wallet-modal.spec.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { mockStore, StoreProvider } from '@deriv/stores'; +import WalletModal from '../wallet-modal'; +import { useActiveWallet } from '@deriv/hooks'; +import { APIProvider } from '@deriv/api'; + +jest.mock('../wallet-modal-header', () => jest.fn(() =>
WalletModalHeader
)); +jest.mock('../wallet-modal-body', () => jest.fn(() =>
WalletModalBody
)); + +jest.mock('@deriv/hooks', () => ({ + ...jest.requireActual('@deriv/hooks'), + useActiveWallet: jest.fn(), +})); + +const mockUseActiveWallet = useActiveWallet as jest.MockedFunction; + +describe('WalletModal', () => { + let modal_root_el: HTMLDivElement; + beforeAll(() => { + modal_root_el = document.createElement('div'); + modal_root_el.setAttribute('id', 'modal_root'); + document.body.appendChild(modal_root_el); + }); + + it('Should render cashier modal if is_wallet_modal_visible is true', () => { + const mocked_store = mockStore({ + ui: { is_wallet_modal_visible: true }, + client: { is_authorize: true }, + traders_hub: { active_modal_wallet_id: 'CRW000000' }, + }); + + // @ts-expect-error need to come up with a way to mock the return type of useFetch + mockUseActiveWallet.mockReturnValue({ loginid: 'CRW000000', is_demo: false }); + + render( + + + + + + ); + + expect(screen.getByText('WalletModalHeader')).toBeInTheDocument(); + expect(screen.getByText('WalletModalBody')).toBeInTheDocument(); + }); + + it('Should not render cashier modal and show loader if authorize is false', () => { + const mocked_store = mockStore({ + ui: { is_wallet_modal_visible: true }, + client: { is_authorize: false }, + traders_hub: { active_modal_wallet_id: 'CRW000000' }, + }); + + // @ts-expect-error need to come up with a way to mock the return type of useFetch + mockUseActiveWallet.mockReturnValue({ loginid: 'CRW100000', is_demo: false }); + + render( + + + + ); + + expect(screen.getByTestId('dt_initial_loader')).toBeInTheDocument(); + }); +}); diff --git a/packages/appstore/src/components/modals/wallet-modal/index.ts b/packages/appstore/src/components/modals/wallet-modal/index.ts new file mode 100644 index 000000000000..b3326d3837de --- /dev/null +++ b/packages/appstore/src/components/modals/wallet-modal/index.ts @@ -0,0 +1,4 @@ +import WalletModal from './wallet-modal'; +import './wallet-modal.scss'; + +export default WalletModal; diff --git a/packages/appstore/src/components/modals/wallet-modal/provider.tsx b/packages/appstore/src/components/modals/wallet-modal/provider.tsx new file mode 100644 index 000000000000..ed32b044f676 --- /dev/null +++ b/packages/appstore/src/components/modals/wallet-modal/provider.tsx @@ -0,0 +1,101 @@ +import React from 'react'; +import { localize } from '@deriv/translations'; +import DemoResetBalance from 'Components/demo-reset-balance'; +import TransactionList from 'Components/transaction-list'; +import WalletDeposit from 'Components/wallet-deposit'; +import WalletTransfer from 'Components/wallet-transfer'; +import WalletWithdrawal from '../../wallet-withdrawal'; + +export type TWalletType = 'real' | 'demo' | 'p2p' | 'payment_agent'; + +export const getCashierOptions = (type: TWalletType) => { + switch (type) { + case 'real': + return [ + { + icon: 'IcAdd', + label: localize('Deposit'), + content: () => , + }, + { icon: 'IcMinus', label: localize('Withdraw'), content: () => }, + { + icon: 'IcAccountTransfer', + label: localize('Transfer'), + content: (props: React.ComponentProps) => , + }, + { + icon: 'IcStatement', + label: localize('Transactions'), + content: () => , + }, + ]; + case 'demo': + return [ + { + icon: 'IcAccountTransfer', + label: localize('Transfer'), + content: (props: React.ComponentProps) => , + }, + { + icon: 'IcStatement', + label: localize('Transactions'), + content: () => , + }, + { + icon: 'IcAdd', + label: localize('Reset balance'), + content: (props: React.ComponentProps) => , + }, + ]; + case 'p2p': + return [ + { + icon: 'IcAdd', + label: localize('Buy/Sell'), + content: () =>

Transfer Real

, + }, + { + icon: 'IcStatement', + label: localize('Orders'), + content: () =>

Transfer Real

, + }, + { + icon: 'IcStatement', + label: localize('My ads'), + content: () =>

Transfer Real

, + }, + { + icon: 'IcStatement', + label: localize('My profile'), + content: () =>

Transfer Real

, + }, + { + icon: 'IcAccountTransfer', + label: localize('Transfer'), + content: () =>

Transfer Real

, + }, + { + icon: 'IcStatement', + label: localize('Transactions'), + content: () =>

Transfer Real

, + }, + ]; + case 'payment_agent': + return [ + { icon: 'IcAdd', label: localize('Deposit'), content: () =>

Transfer Real

}, + { icon: 'IcMinus', label: localize('Withdraw'), content: () =>

Transfer Real

}, + { + icon: 'IcAccountTransfer', + label: localize('Transfer'), + content: () =>

Transfer Real

, + }, + { + icon: 'IcStatement', + label: localize('Transactions'), + content: () =>

Transfer Real

, + }, + ]; + default: + return []; + } +}; diff --git a/packages/appstore/src/components/modals/wallet-modal/wallet-modal-body.tsx b/packages/appstore/src/components/modals/wallet-modal/wallet-modal-body.tsx new file mode 100644 index 000000000000..828324573ce5 --- /dev/null +++ b/packages/appstore/src/components/modals/wallet-modal/wallet-modal-body.tsx @@ -0,0 +1,103 @@ +import React from 'react'; +import classNames from 'classnames'; +import { Tabs, ThemedScrollbars, Div100vhContainer } from '@deriv/components'; +import { getCashierOptions } from './provider'; +import { observer, useStore } from '@deriv/stores'; +import type { TWalletAccount } from 'Types'; + +type TWalletModalBodyProps = { + contentScrollHandler: React.UIEventHandler; + is_dark: boolean; + is_mobile: boolean; + setIsWalletNameVisible: (value: boolean) => void; + is_wallet_name_visible: boolean; + wallet: TWalletAccount; +}; + +const real_tabs = { + Deposit: 0, + Withdraw: 1, + Transfer: 2, + Transactions: 3, +} as const; + +const demo_tabs = { + Deposit: 2, + Transfer: 0, + Transactions: 1, + Withdraw: undefined, +} as const; + +const WalletModalBody = observer( + ({ + contentScrollHandler, + is_dark, + is_mobile, + setIsWalletNameVisible, + is_wallet_name_visible, + wallet, + }: TWalletModalBodyProps) => { + const store = useStore(); + + const { is_demo } = wallet; + + const { + traders_hub: { active_modal_tab, setWalletModalActiveTab }, + } = store; + + const getHeightOffset = React.useCallback(() => { + const desktop_header_height = '24.4rem'; + const mobile_header_height = '8.2rem'; + + return is_mobile ? mobile_header_height : desktop_header_height; + }, [is_mobile]); + + const tabs = is_demo ? demo_tabs : real_tabs; + + return ( + { + const tab_name = Object.keys(tabs).find( + key => tabs[key as keyof typeof tabs] === index + ) as typeof active_modal_tab; + setWalletModalActiveTab(tab_name); + }} + > + {getCashierOptions(is_demo ? 'demo' : 'real').map(option => { + return ( +
+ + +
+ {option.content({ + is_wallet_name_visible, + contentScrollHandler, + setIsWalletNameVisible, + })} +
+
+
+
+ ); + })} +
+ ); + } +); + +export default WalletModalBody; diff --git a/packages/appstore/src/components/modals/wallet-modal/wallet-modal-header.tsx b/packages/appstore/src/components/modals/wallet-modal/wallet-modal-header.tsx new file mode 100644 index 000000000000..ed4613c80cfc --- /dev/null +++ b/packages/appstore/src/components/modals/wallet-modal/wallet-modal-header.tsx @@ -0,0 +1,102 @@ +import React from 'react'; +import classNames from 'classnames'; +import { Icon, Text, WalletIcon } from '@deriv/components'; +import { formatMoney } from '@deriv/shared'; +import { getAccountName } from 'Constants/utils'; +import { WalletJurisdictionBadge } from 'Components/wallet-jurisdiction-badge'; +import type { TWalletAccount } from 'Types'; + +type TWalletModalHeaderProps = { + closeModal: VoidFunction; + is_dark: boolean; + is_mobile: boolean; + is_wallet_name_visible: boolean; + wallet: TWalletAccount; +}; + +const WalletModalHeader = ({ + closeModal, + is_dark, + is_mobile, + is_wallet_name_visible, + wallet, +}: TWalletModalHeaderProps) => { + const { balance, currency, icon, currency_config, is_demo, gradient_header_class, landing_company_name } = wallet; + const is_crypto = currency_config?.is_crypto; + const display_currency_code = currency_config?.display_code; + + const header_class_name = 'wallet-modal--header'; + + const getCloseIcon = React.useCallback(() => { + if (is_demo && is_dark) return 'IcAppstoreCloseLight'; + if (is_demo && !is_dark) return 'IcAppstoreCloseDark'; + if (is_dark) return 'IcAppstoreCloseDark'; + return 'IcAppstoreCloseLight'; + }, [is_dark, is_demo]); + + const getWalletIcon = React.useCallback(() => { + if (currency && ['USDT', 'eUSDT', 'tUSDT', 'UST'].includes(currency)) { + return is_dark ? 'IcWalletModalTetherDark' : 'IcWalletModalTetherLight'; + } + return icon; + }, [currency, icon, is_dark]); + + const getStylesByClassName = (class_name: string) => { + return classNames(class_name, { + [`${class_name}-demo`]: is_demo, + }); + }; + + const getWalletIconType = (): React.ComponentProps['type'] => { + if (is_demo) return 'demo'; + return is_crypto ? 'crypto' : 'fiat'; + }; + + const getWalletIconSize = (): React.ComponentProps['size'] => { + if (is_mobile) return is_demo || is_crypto ? 'large' : 'xlarge'; + return 'xxlarge'; + }; + + return ( +
+
+
+
+ + {getAccountName({ + display_currency_code: wallet.currency_config?.display_code, + account_type: 'wallet', + })} + + +
+ + {formatMoney(currency || '', balance, true)} {display_currency_code} + +
+
+ +
+
+ +
+
+
+ ); +}; + +export default WalletModalHeader; diff --git a/packages/appstore/src/components/modals/wallet-modal/wallet-modal.scss b/packages/appstore/src/components/modals/wallet-modal/wallet-modal.scss new file mode 100644 index 000000000000..043ee42f64c0 --- /dev/null +++ b/packages/appstore/src/components/modals/wallet-modal/wallet-modal.scss @@ -0,0 +1,218 @@ +.dc-modal__container_wallet-modal { + display: flex; + align-items: center; + position: fixed; + inset: 0; + min-height: calc(100vh - 84px) !important; + min-width: 100vw !important; + margin: 4.8rem 0 3.6rem; + border-radius: unset; + background-color: var(--general-main-1); + z-index: 9997; + box-shadow: none; + + @include mobile { + margin: 0; + } + + // styles for mobile and desktop modal body (tabs with content) + .dc-tabs { + &--wallet-modal { + width: 100%; + margin-top: -4.8rem; + z-index: 9999; + + @include mobile { + top: 8.2rem; + margin-top: 0; + transition: top 0.2s ease; + + &.is_scrolled { + top: 4.2rem; + } + } + + &-themed-scrollbar { + width: 100%; + } + + &-content-wrapper { + max-width: 128rem; + margin: 0 auto; + padding: 2.4rem 4rem; + + @include mobile { + padding: 1.6rem; + } + } + } + &__list { + padding: 0 4rem; + + @include mobile { + padding: 0 1.6rem; + } + + &--wallet-modal { + max-width: 128rem; + width: 100%; + margin: 0 auto; + + @include mobile { + width: 100%; + z-index: 3; + overflow-x: scroll; + + /* IE and Edge */ + -ms-overflow-style: none; + /* Firefox */ + scrollbar-width: none; + + &::-webkit-scrollbar { + display: none; + } + + &::-webkit-scrollbar-thumb { + display: none; + } + + &.is_scrolled { + top: 4.4rem; + } + } + } + } + + &__item { + display: flex; + align-items: center; + justify-content: center; + padding: 0 3.2rem; + height: 4.8rem; + + @include mobile { + padding: 0 1.6rem; + height: 4rem; + font-size: var(--text-size-xxs); + } + + &__icon { + padding: 0; + margin-right: 0.8rem; + } + } + + &__active { + background-color: var(--general-main-1); + border-radius: 1.6rem 1.6rem 0 0; + } + + &__content { + width: 100%; + font-size: var(--text-size-l); + color: var(--text-prominent); + display: flex; + align-items: center; + justify-content: center; + + @include mobile { + font-size: var(--text-size-xs); + } + } + } +} + +.wallet-modal--header { + max-width: 128rem; + width: 100%; + display: flex; + position: relative; + height: 16rem; + padding: 2.4rem 4rem 7.2rem; + + @include mobile { + height: 12.2rem; + padding: 1.6rem 1.6rem 5.6rem; + transition: height 0.2s ease; + + .title-visibility { + height: 2rem; + } + + .title-visibility, + .icon-visibility { + visibility: visible; + transition: visibility 0s, height 0.2s ease; + } + } + + //TODO: check do we need this after bg change to radial-gradient + &__title-wrapper { + position: relative; + } + + &__title { + display: flex; + align-items: center; + + &-wallet { + padding-right: 0.8rem; + color: var(--text-general); + + &-demo { + color: var(--demo-text-color-1); + } + } + + &-balance { + color: var(--text-prominent); + + &-demo { + color: var(--demo-text-color-2); + } + } + } + + &--hidden-title { + height: 8.2rem; + align-items: center; + justify-content: space-between; + + .title-visibility, + .icon-visibility { + visibility: hidden; + height: 0; + } + } + + &__currency-icon { + z-index: 3; + margin-left: auto; + margin-right: 1.6rem; + + @include mobile { + margin-right: 0.8rem; + } + } + + &__close-icon { + position: relative; + + .dc-icon { + cursor: pointer; + } + } + + &-background { + width: 100%; + display: flex; + justify-content: center; + position: relative; + overflow: hidden; + + @include mobile { + position: fixed; + z-index: 3; + } + } +} diff --git a/packages/appstore/src/components/modals/wallet-modal/wallet-modal.tsx b/packages/appstore/src/components/modals/wallet-modal/wallet-modal.tsx new file mode 100644 index 000000000000..0bcadb75798c --- /dev/null +++ b/packages/appstore/src/components/modals/wallet-modal/wallet-modal.tsx @@ -0,0 +1,80 @@ +import React, { useEffect } from 'react'; +import { Loading, Modal } from '@deriv/components'; +import { useActiveWallet } from '@deriv/hooks'; +import { observer, useStore } from '@deriv/stores'; +import WalletModalHeader from './wallet-modal-header'; +import WalletModalBody from './wallet-modal-body'; + +const WalletModal = observer(() => { + const store = useStore(); + + const { + client: { is_authorize, switchAccount }, + ui: { is_dark_mode_on, is_wallet_modal_visible, is_mobile, setIsWalletModalVisible }, + traders_hub: { active_modal_tab, active_modal_wallet_id, setWalletModalActiveTab }, + } = store; + + const active_wallet = useActiveWallet(); + + useEffect(() => { + let timeout_id: NodeJS.Timeout; + + if (is_wallet_modal_visible && active_wallet?.loginid !== active_modal_wallet_id) { + /** Adding a delay as per requirement because the modal must appear first, then switch the account */ + timeout_id = setTimeout(() => switchAccount(active_modal_wallet_id), 500); + } + + return () => clearTimeout(timeout_id); + }, [active_modal_wallet_id, active_wallet?.loginid, is_wallet_modal_visible, switchAccount]); + + const [is_wallet_name_visible, setIsWalletNameVisible] = React.useState(true); + + React.useEffect(() => { + return setIsWalletNameVisible(true); + }, [active_modal_tab, is_wallet_modal_visible]); + + const closeModal = () => { + setIsWalletModalVisible(false); + setWalletModalActiveTab(active_modal_tab); + }; + + const contentScrollHandler = React.useCallback( + (e: React.UIEvent) => { + if (is_mobile && is_wallet_modal_visible) { + const target = e.target as HTMLDivElement; + setIsWalletNameVisible(target.scrollTop <= 0); + } + }, + [is_mobile, is_wallet_modal_visible] + ); + + const is_loading = active_wallet?.loginid !== active_modal_wallet_id || !is_authorize || !active_wallet; + + return ( + + {is_loading ? ( + + ) : ( + + + + + )} + + ); +}); + +export default WalletModal; diff --git a/packages/appstore/src/components/modals/wallets-migration-failed/__tests__/wallets-migration-failed.spec.tsx b/packages/appstore/src/components/modals/wallets-migration-failed/__tests__/wallets-migration-failed.spec.tsx new file mode 100644 index 000000000000..764d74276ff2 --- /dev/null +++ b/packages/appstore/src/components/modals/wallets-migration-failed/__tests__/wallets-migration-failed.spec.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import WalletsMigrationFailed from '../wallets-migration-failed'; + +jest.mock('@deriv/components', () => ({ + ...jest.requireActual('@deriv/components'), +})); + +describe('', () => { + it('Should render the Modal', () => { + const mockRootStore = mockStore({ + traders_hub: { + is_wallet_migration_failed: true, + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + const { container } = render(, { wrapper }); + + expect(container).toBeInTheDocument(); + }); + + it('Should not render the Modal if is_wallet_migration_failed is false', () => { + const mockRootStore = mockStore({}); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + const { container } = render(, { wrapper }); + + expect(container).toBeEmptyDOMElement(); + }); +}); diff --git a/packages/appstore/src/components/modals/wallets-migration-failed/index.ts b/packages/appstore/src/components/modals/wallets-migration-failed/index.ts new file mode 100644 index 000000000000..1099aa7c3845 --- /dev/null +++ b/packages/appstore/src/components/modals/wallets-migration-failed/index.ts @@ -0,0 +1,3 @@ +import WalletsMigrationFailed from './wallets-migration-failed'; + +export default WalletsMigrationFailed; diff --git a/packages/appstore/src/components/modals/wallets-migration-failed/wallets-migration-failed.scss b/packages/appstore/src/components/modals/wallets-migration-failed/wallets-migration-failed.scss new file mode 100644 index 000000000000..f4078494c8a5 --- /dev/null +++ b/packages/appstore/src/components/modals/wallets-migration-failed/wallets-migration-failed.scss @@ -0,0 +1,29 @@ +.wallets-migration-failed { + padding: 2.4rem !important; + + @include mobile { + padding: 1.6rem !important; + } + + &__title { + margin-bottom: 2.4rem; + + @include mobile { + margin-bottom: 1.6rem; + } + } + + &__footer { + padding: 0 2.4rem 2.4rem; + + @include mobile { + padding: 0 1.6rem 1.6rem; + } + } + + &__text { + @include mobile { + font-size: 1.2rem !important; + } + } +} diff --git a/packages/appstore/src/components/modals/wallets-migration-failed/wallets-migration-failed.tsx b/packages/appstore/src/components/modals/wallets-migration-failed/wallets-migration-failed.tsx new file mode 100644 index 000000000000..ddca901304c0 --- /dev/null +++ b/packages/appstore/src/components/modals/wallets-migration-failed/wallets-migration-failed.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { Text, useOnClickOutside, Modal, Button } from '@deriv/components'; +import { useStore, observer } from '@deriv/stores'; +import { Localize } from '@deriv/translations'; +import './wallets-migration-failed.scss'; + +const WalletsMigrationFailed = observer(() => { + const { traders_hub, ui } = useStore(); + const { is_wallet_migration_failed, setWalletsMigrationFailedPopup } = traders_hub; + const { is_mobile } = ui; + + const wallets_migration_failed_ref = React.useRef(null); + + const handleClose = () => { + setWalletsMigrationFailedPopup(false); + }; + + const handLiveChatButtonClick = () => { + window.LC_API?.open_chat_window(); + setWalletsMigrationFailedPopup(false); + }; + + const validateClickOutside = (e: MouseEvent) => { + return is_wallet_migration_failed && !wallets_migration_failed_ref?.current?.contains(e.target as Node); + }; + + useOnClickOutside(wallets_migration_failed_ref, handleClose, validateClickOutside); + + return ( + +
+ + + + + + + + + + + + +
+
+ ); +}); + +export default WalletsMigrationFailed; diff --git a/packages/appstore/src/components/options-multipliers-listing/index.tsx b/packages/appstore/src/components/options-multipliers-listing/index.tsx index b76afb64a0fc..eeff8ab41626 100644 --- a/packages/appstore/src/components/options-multipliers-listing/index.tsx +++ b/packages/appstore/src/components/options-multipliers-listing/index.tsx @@ -1,22 +1,22 @@ import React from 'react'; import { observer } from 'mobx-react-lite'; import { Text, StaticUrl } from '@deriv/components'; +import { ContentFlag } from '@deriv/shared'; +import { useStore } from '@deriv/stores'; import { Localize, localize } from '@deriv/translations'; import ListingContainer from 'Components/containers/listing-container'; -import { BrandConfig } from 'Constants/platform-config'; -import TradingAppCard from 'Components/containers/trading-app-card'; -import { useStores } from 'Stores/index'; -import { isMobile, ContentFlag } from '@deriv/shared'; import PlatformLoader from 'Components/pre-loader/platform-loader'; +import TradingAppCard from 'Components/containers/trading-app-card'; +import { BrandConfig } from 'Constants/platform-config'; import { getHasDivider } from 'Constants/utils'; -const OptionsAndMultipliersListing = () => { - const { traders_hub, client, ui } = useStores(); +const OptionsAndMultipliersListing = observer(() => { + const { traders_hub, client, ui } = useStore(); const { available_platforms, is_eu_user, is_real, no_MF_account, no_CR_account, is_demo, content_flag } = traders_hub; const { is_landing_company_loaded, is_eu, has_maltainvest_account, real_account_creation_unlock_date } = client; - const { setShouldShowCooldownModal, openRealAccountSignup } = ui; + const { setShouldShowCooldownModal, openRealAccountSignup, is_mobile } = ui; const low_risk_cr_non_eu = content_flag === ContentFlag.LOW_RISK_CR_NON_EU; @@ -27,15 +27,16 @@ const OptionsAndMultipliersListing = () => { const cr_demo = content_flag === ContentFlag.CR_DEMO; const OptionsTitle = () => { - if ((low_risk_cr_non_eu || high_risk_cr || cr_demo) && !isMobile()) { + if (is_mobile) return null; + if (low_risk_cr_non_eu || high_risk_cr || cr_demo) { return ( - + ); - } else if ((low_risk_cr_eu || is_eu) && !isMobile()) { + } else if (low_risk_cr_eu || is_eu) { return ( - + ); @@ -113,6 +114,6 @@ const OptionsAndMultipliersListing = () => { )} ); -}; +}); -export default observer(OptionsAndMultipliersListing); +export default OptionsAndMultipliersListing; diff --git a/packages/appstore/src/components/routes/routes-wrapper.tsx b/packages/appstore/src/components/routes/routes-wrapper.tsx deleted file mode 100644 index 8092cd5b2913..000000000000 --- a/packages/appstore/src/components/routes/routes-wrapper.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import * as React from 'react'; -import { BrowserRouter as Router } from 'react-router-dom'; - -const RoutesWrapper: React.FC = ({ has_router, children }) => { - if (has_router) { - return {children}; - } - - return {children}; -}; - -type TRoutesWrapperProps = React.PropsWithChildren<{ - has_router: boolean; -}>; - -export default RoutesWrapper; diff --git a/packages/appstore/src/components/routes/routes.tsx b/packages/appstore/src/components/routes/routes.tsx index 8d08e7ca9db0..58a844e118be 100644 --- a/packages/appstore/src/components/routes/routes.tsx +++ b/packages/appstore/src/components/routes/routes.tsx @@ -1,17 +1,29 @@ import * as React from 'react'; -import { useFeatureFlags } from '@deriv/hooks'; -import { Localize } from '@deriv/translations'; +// import { Loading } from '@deriv/components'; +import { useFeatureFlags /*useWalletsList*/ } from '@deriv/hooks'; +import { observer } from '@deriv/stores'; +import { Localize, localize } from '@deriv/translations'; import Wallets from '@deriv/wallets'; -import getRoutesConfig from 'Constants/routes-config'; -import { useStores } from 'Stores'; -import { TRoute } from 'Types'; -import { observer } from 'mobx-react-lite'; +import Onboarding from 'Modules/onboarding'; +import TradersHub from 'Modules/traders-hub'; +// import { WalletsModule } from 'Modules/wallets'; import { Switch } from 'react-router-dom'; import RouteWithSubroutes from './route-with-sub-routes.jsx'; const Routes: React.FC = observer(() => { - const { config } = useStores(); - const { is_next_wallet_enabled } = useFeatureFlags(); + //TODO: Uncomment once useWalletList hook is optimized for production release. + const { /*is_wallet_enabled,*/ is_next_wallet_enabled } = useFeatureFlags(); + // const { has_wallet, isLoading } = useWalletsList(); + // const should_show_wallets = is_wallet_enabled && has_wallet; + + let content: React.FC = TradersHub; + if (is_next_wallet_enabled) { + content = Wallets; + } + // else if (should_show_wallets) { + // content = WalletsModule; + // } + // if (isLoading) return ; return ( { } > - {getRoutesConfig({ - consumer_routes: config.routes, - }).map((route: TRoute, idx: number) => { - // Temporary way to intercept the route to show the Wallets component. - let updated_route = route; - if (updated_route.path === '/appstore/traders-hub') { - updated_route = { - ...updated_route, - component: is_next_wallet_enabled ? Wallets : updated_route.component, - }; - } - - return ; - })} + localize("Trader's Hub")} + /> + localize('Onboarding')} + /> ); diff --git a/packages/appstore/src/components/transaction-list/index.ts b/packages/appstore/src/components/transaction-list/index.ts new file mode 100644 index 000000000000..eee3dd112e2f --- /dev/null +++ b/packages/appstore/src/components/transaction-list/index.ts @@ -0,0 +1,3 @@ +import TransactionList from './transaction-list'; + +export default TransactionList; diff --git a/packages/appstore/src/components/transaction-list/non-pending-transaction.tsx b/packages/appstore/src/components/transaction-list/non-pending-transaction.tsx new file mode 100644 index 000000000000..616230a162b7 --- /dev/null +++ b/packages/appstore/src/components/transaction-list/non-pending-transaction.tsx @@ -0,0 +1,116 @@ +import React from 'react'; +import { AppLinkedWithWalletIcon, Text, WalletIcon } from '@deriv/components'; +import { useWalletTransactions } from '@deriv/hooks'; +import { useStore } from '@deriv/stores'; +import { Localize } from '@deriv/translations'; + +type TNonPendingTransaction = { + transaction: ReturnType['transactions'][number]; +}; + +const NonPendingTransaction = ({ transaction }: TNonPendingTransaction) => { + const { + ui: { is_dark_mode_on, is_mobile }, + } = useStore(); + + const { + account_category, + account_currency, + account_name, + account_type, + action_type, + amount, + balance_after = 0, + gradient_class, + icon, + icon_type, + } = transaction; + + const formatAmount = (value: number) => value.toLocaleString(undefined, { minimumFractionDigits: 2 }); + + const formatActionType = (value: string) => value[0].toUpperCase() + value.substring(1).replace(/_/, ' '); + + const getAppIcon = () => { + switch (account_type) { + case 'standard': + return is_dark_mode_on ? 'IcWalletOptionsDark' : 'IcWalletOptionsLight'; + //TODO: add proper icon for mt5 + case 'mt5': + return 'IcMt5CfdPlatform'; + //TODO: add proper icon for dxtrade + case 'dxtrade': + return ''; + default: + return ''; + } + }; + + return ( +
+
+ {account_category === 'trading' ? ( + + ) : ( + + )} +
+ + {formatActionType(action_type)} + + + {account_name} + +
+
+
+ 0 ? 'profit-success' : 'loss-danger'} + weight='bold' + line_height={is_mobile ? 's' : 'm'} + > + {(amount > 0 ? '+' : '') + formatAmount(amount)} {account_currency} + + + + +
+
+ ); +}; + +export default NonPendingTransaction; diff --git a/packages/appstore/src/components/transaction-list/transaction-for-day.tsx b/packages/appstore/src/components/transaction-list/transaction-for-day.tsx new file mode 100644 index 000000000000..3d28a58231cf --- /dev/null +++ b/packages/appstore/src/components/transaction-list/transaction-for-day.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { Text } from '@deriv/components'; +import { useWalletTransactions } from '@deriv/hooks'; +import { observer, useStore } from '@deriv/stores'; +import NonPendingTransaction from './non-pending-transaction'; + +export const TransactionsForOneDay = observer( + ({ + day, + transaction_list, + }: { + day: string; + transaction_list: ReturnType['transactions']; + }) => { + const { + client: { loginid }, + ui: { is_mobile }, + } = useStore(); + + return ( +
+ + {day} + + {transaction_list.map(transaction => { + let display_transaction = transaction; + if ( + transaction?.action_type === 'transfer' && + transaction?.from?.loginid === loginid && + typeof transaction?.amount === 'number' + ) { + display_transaction = { ...transaction, amount: -transaction.amount }; + } + return ; + })} +
+ ); + } +); diff --git a/packages/appstore/src/components/transaction-list/transaction-list.scss b/packages/appstore/src/components/transaction-list/transaction-list.scss new file mode 100644 index 000000000000..0d98367bc8f5 --- /dev/null +++ b/packages/appstore/src/components/transaction-list/transaction-list.scss @@ -0,0 +1,99 @@ +.transaction-list { + display: flex; + flex-direction: column; + gap: 0.8rem; + + &__container { + display: flex; + flex-direction: column; + gap: 1.6rem; + margin: 0 auto; + width: 100%; + max-width: 800px; + } + + &__filter { + align-self: end; + + .dc-list { + width: 19.5rem; + } + + .dc-dropdown { + &__container { + height: 4rem; + width: 19.5rem; + } + + &__label { + transform: translate(1rem, -1rem) scale(0.75); + } + + &__display { + height: 4rem; + } + + &__display-text { + padding-left: 4rem; + } + } + + .suffix-icon { + height: 1.2rem; + width: 1.2rem; + } + + @include mobile { + align-self: auto; + margin: 0; + width: 100%; + max-width: 100%; + + .dc-dropdown__container, + .dc-list { + width: 100%; + } + } + } + + &__day { + display: flex; + flex-direction: column; + + &-header { + padding: 0.8rem 1.6rem; + } + } + + &__item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px; + box-shadow: inset 0 1px 0 var(--border-normal); + + &__left { + display: flex; + gap: 0.8rem; + + &__title { + display: flex; + flex-direction: column; + align-items: start; + justify-content: center; + gap: 0; + + @include mobile { + max-width: 10.4rem; + } + } + } + + &__right { + display: flex; + flex-direction: column; + align-items: end; + gap: 0.4rem; + } + } +} diff --git a/packages/appstore/src/components/transaction-list/transaction-list.tsx b/packages/appstore/src/components/transaction-list/transaction-list.tsx new file mode 100644 index 000000000000..1b24267e68b8 --- /dev/null +++ b/packages/appstore/src/components/transaction-list/transaction-list.tsx @@ -0,0 +1,77 @@ +import React, { useState } from 'react'; +import { Dropdown } from '@deriv/components'; +import { useActiveWallet, useWalletTransactions } from '@deriv/hooks'; +import { localize } from '@deriv/translations'; +import { groupTransactionsByDay } from '@deriv/utils'; +import { TransactionsForOneDay } from './transaction-for-day'; +import './transaction-list.scss'; + +const TransactionList = () => { + const wallet = useActiveWallet(); + + const filter_options = [ + { + text: localize('All'), + value: '', + }, + ...(wallet?.is_virtual + ? ([ + { + text: localize('Reset balance'), + value: 'reset_balance', + }, + ] as const) + : ([ + { + text: localize('Deposit'), + value: 'deposit', + }, + { + text: localize('Withdrawal'), + value: 'withdrawal', + }, + ] as const)), + { + text: localize('Transfer'), + value: 'transfer', + }, + ] as const; + + const [filter, setFilter] = useState(''); + + const { transactions } = useWalletTransactions(filter); + + // @ts-expect-error reset_balance is not supported in the API yet + const grouped_transactions = groupTransactionsByDay(transactions); + + const onValueChange = (e: { target: { name: string; value: string } }) => { + setFilter(e.target.value as typeof filter); + }; + + return ( +
+
+ + {Object.entries(grouped_transactions).map(([day, transaction_list]) => ( + ['transaction_list'] + } + /> + ))} +
+
+ ); +}; + +export default TransactionList; diff --git a/packages/appstore/src/components/wallet-button/__tests__/wallet-button.spec.tsx b/packages/appstore/src/components/wallet-button/__tests__/wallet-button.spec.tsx new file mode 100644 index 000000000000..280c3c2756df --- /dev/null +++ b/packages/appstore/src/components/wallet-button/__tests__/wallet-button.spec.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { mockStore, StoreProvider } from '@deriv/stores'; +import WalletButton from '..'; + +const mockedRootStore = mockStore({}); + +describe('', () => { + const button = { + name: 'Transfer', + text: 'Transfer', + icon: 'IcAccountTransfer', + action: () => { + return true; + }, + } as const; + + it('Should render right text', () => { + render( + + + + ); + + expect(screen.getByText('Transfer')).toBeInTheDocument(); + }); + + it('Should render desktop class', () => { + const { container } = render( + + + + ); + + expect(container.childNodes[0]).toHaveClass('wallet-button__desktop-item'); + expect(container.childNodes[0]).not.toHaveClass('wallet-button__mobile-item'); + }); + + it('Should render mobile class', () => { + const { container } = render( + + + + ); + + expect(container.childNodes[0]).not.toHaveClass('wallet-button__desktop-item'); + expect(container.childNodes[0]).toHaveClass('wallet-button__mobile-item'); + }); + + it('Should add disabled class', () => { + const { container } = render( + + + + ); + + expect(container.childNodes[0]).not.toHaveClass('wallet-button__mobile-item'); + expect(container.childNodes[0]).toHaveClass('wallet-button__desktop-item'); + expect(container.childNodes[0]).toHaveClass('wallet-button__desktop-item-disabled'); + }); +}); diff --git a/packages/appstore/src/components/wallet-button/index.ts b/packages/appstore/src/components/wallet-button/index.ts new file mode 100644 index 000000000000..9197d979040f --- /dev/null +++ b/packages/appstore/src/components/wallet-button/index.ts @@ -0,0 +1,3 @@ +import WalletButton from './wallet-button'; + +export default WalletButton; diff --git a/packages/appstore/src/components/wallet-button/wallet-button.scss b/packages/appstore/src/components/wallet-button/wallet-button.scss new file mode 100644 index 000000000000..47478ceb300d --- /dev/null +++ b/packages/appstore/src/components/wallet-button/wallet-button.scss @@ -0,0 +1,79 @@ +.wallet-button { + &__mobile-item { + display: flex; + flex-direction: column; + align-items: center; + cursor: pointer; + min-width: 5.6rem; + + &-icon { + padding: 0.8rem; + border: 1px solid var(--border-normal); + border-radius: 50%; + } + + &-text { + margin-top: 0.4rem; + } + } + + &__desktop-item { + display: flex; + align-items: center; + cursor: pointer; + height: 3.2rem; + border-radius: $BORDER_RADIUS * 4; + padding: 0.6rem 1.6rem; + margin-right: 0.8rem; + border: 1px solid var(--border-hover); + background-color: var(--prominent); + + &:hover:not(&-disabled) { + background-color: var(--button-secondary-hover); + } + + &-disabled { + cursor: auto; + border: 1px solid var(--general-disabled); + } + + &-text { + margin-left: 0.8rem; + } + + &-transition { + &-enter { + transform: translateX(-1rem); + opacity: 0; + } + + &-enter-active { + transition: all 240ms ease-in-out; + transform: translateX(0); + position: relative; + opacity: 1; + } + + &-enter-done { + transform: translateX(0); + opacity: 1; + } + + &-exit { + transform: translateX(0); + opacity: 1; + } + + &-exit-active { + transition: all 240ms ease-in-out; + transform: translateX(-1rem); + position: relative; + opacity: 0; + } + + &-exit-done { + opacity: 0; + } + } + } +} diff --git a/packages/appstore/src/components/wallet-button/wallet-button.tsx b/packages/appstore/src/components/wallet-button/wallet-button.tsx new file mode 100644 index 000000000000..277025b4dff1 --- /dev/null +++ b/packages/appstore/src/components/wallet-button/wallet-button.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { CSSTransition } from 'react-transition-group'; +import classNames from 'classnames'; +import { Icon, Text } from '@deriv/components'; +import { getWalletHeaderButtons } from 'Constants/utils'; +import './wallet-button.scss'; + +type TProps = { + button: ReturnType[number]; + is_desktop_wallet?: boolean; + is_disabled?: boolean; + is_open?: boolean; +}; + +const WalletButton = ({ button, is_desktop_wallet, is_disabled, is_open }: TProps) => { + const { name, text, icon, action } = button; + return is_desktop_wallet ? ( +
+ + + + {text} + + +
+ ) : ( +
+
+ +
+ + {text} + +
+ ); +}; + +export default WalletButton; diff --git a/packages/appstore/src/components/wallet-cards-carousel/__tests__/wallet-cards-carousel.spec.tsx b/packages/appstore/src/components/wallet-cards-carousel/__tests__/wallet-cards-carousel.spec.tsx new file mode 100644 index 000000000000..f37493775614 --- /dev/null +++ b/packages/appstore/src/components/wallet-cards-carousel/__tests__/wallet-cards-carousel.spec.tsx @@ -0,0 +1,165 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { APIProvider, useFetch } from '@deriv/api'; +import { mockStore, StoreProvider } from '@deriv/stores'; +import WalletCardsCarousel from '..'; + +jest.mock('@deriv/api', () => ({ + ...jest.requireActual('@deriv/api'), + useFetch: jest.fn((name: string) => { + if (name === 'authorize') { + return { + data: { + authorize: { + account_list: [ + { + account_category: 'wallet', + currency: 'USD', + is_virtual: 0, + loginid: 'CRW10001', + }, + { + account_category: 'trading', + currency: 'USD', + is_virtual: 0, + loginid: 'CRW10002', + }, + { + account_category: 'wallet', + currency: 'UST', + is_virtual: 0, + loginid: 'CRW10003', + }, + { + account_category: 'wallet', + currency: 'BTC', + is_virtual: 1, + loginid: 'VRW10001', + }, + { + account_category: 'wallet', + currency: 'AUD', + is_virtual: 0, + loginid: 'CRW10004', + }, + { + account_category: 'wallet', + currency: 'ETH', + is_virtual: 0, + loginid: 'CRW10005', + }, + ], + }, + }, + }; + } else if (name === 'balance') { + return { + data: { + balance: { + accounts: { + CRW909900: { + balance: 0, + }, + }, + }, + }, + }; + } else if (name === 'website_status') { + return { + data: { + website_status: { + currencies_config: { + AUD: { type: 'fiat' }, + BTC: { type: 'crypto' }, + ETH: { type: 'crypto' }, + UST: { type: 'crypto' }, + USD: { type: 'fiat' }, + }, + }, + }, + }; + } else if (name === 'crypto_config') { + return { + data: { + crypto_config: { + currencies_config: { + BTC: {}, + }, + }, + }, + }; + } + + return undefined; + }), +})); + +jest.mock('./../cards-slider-swiper', () => jest.fn(() =>
slider
)); +const mockUseFetch = useFetch as jest.MockedFunction>; + +describe('', () => { + const wrapper = (mock: ReturnType) => { + const Component = ({ children }: { children: JSX.Element }) => ( + + {children} + + ); + return Component; + }; + it('Should render slider', () => { + const mock = mockStore({ client: { accounts: { CRW909900: { token: '12345' } }, loginid: 'CRW909900' } }); + + render(, { wrapper: wrapper(mock) }); + const slider = screen.queryByText('slider'); + + expect(slider).toBeInTheDocument(); + }); + + it('Should render buttons for REAL', () => { + const mock = mockStore({ client: { accounts: { CRW909900: { token: '12345' } }, loginid: 'CRW909900' } }); + + render(, { wrapper: wrapper(mock) }); + + const btn1 = screen.getByRole('button', { name: /Deposit/i }); + const btn2 = screen.getByRole('button', { name: /Withdraw/i }); + const btn3 = screen.getByRole('button', { name: /Transfer/i }); + const btn4 = screen.getByRole('button', { name: /Transactions/i }); + + expect(btn1).toBeInTheDocument(); + expect(btn2).toBeInTheDocument(); + expect(btn3).toBeInTheDocument(); + expect(btn4).toBeInTheDocument(); + }); + + it('Should render buttons for DEMO', () => { + const mock = mockStore({ client: { accounts: { VRW10001: { token: '12345' } }, loginid: 'VRW10001' } }); + + mockUseFetch.mockReturnValue({ + data: { + authorize: { + account_list: [ + { + account_category: 'wallet', + account_type: 'doughflow', + currency: 'USD', + is_virtual: 1, + loginid: 'VRW10001', + }, + ], + loginid: 'VRW10001', + }, + }, + } as unknown as ReturnType); + + render(, { wrapper: wrapper(mock) }); + + const btn1 = screen.getByRole('button', { name: /Transfer/i }); + const btn2 = screen.getByRole('button', { name: /Transactions/i }); + const btn3 = screen.getByRole('button', { name: /Reset balance/i }); + + expect(btn1).toBeInTheDocument(); + expect(btn2).toBeInTheDocument(); + expect(btn3).toBeInTheDocument(); + }); +}); diff --git a/packages/appstore/src/components/wallet-cards-carousel/cards-slider-swiper.tsx b/packages/appstore/src/components/wallet-cards-carousel/cards-slider-swiper.tsx new file mode 100644 index 000000000000..dfa33194257d --- /dev/null +++ b/packages/appstore/src/components/wallet-cards-carousel/cards-slider-swiper.tsx @@ -0,0 +1,86 @@ +import React, { useEffect, useState } from 'react'; +import useEmblaCarousel from 'embla-carousel-react'; +import { WalletCard, ProgressBarTracker } from '@deriv/components'; +import { useWalletsList } from '@deriv/hooks'; +import { formatMoney } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; +import { getAccountName } from 'Constants/utils'; +import { TWalletAccount } from 'Types'; +import './wallet-cards-carousel.scss'; + +const CardsSliderSwiper = observer(() => { + const { client } = useStore(); + const { switchAccount } = client; + const { data } = useWalletsList(); + + const active_wallet_index = data.findIndex(item => item?.is_selected) || 0; + + const [active_index, setActiveIndex] = useState(active_wallet_index); + const [emblaRef, emblaApi] = useEmblaCarousel({ skipSnaps: true, containScroll: false }); + + const steps = data.map((_, idx) => idx.toString()); + + useEffect(() => { + emblaApi?.on('select', () => { + const index = emblaApi?.selectedScrollSnap() || 0; + setActiveIndex(index + 1); + }); + }, [emblaApi]); + + useEffect(() => { + emblaApi?.scrollTo(active_index - 1); + }, [active_index, emblaApi]); + + useEffect(() => { + const timeout_id = setTimeout(() => { + if (!data[active_index - 1]?.is_selected) switchAccount(data[active_index - 1]?.loginid); + }, 1000); + + return () => clearTimeout(timeout_id); + }, [active_index, data, switchAccount]); + + useEffect(() => { + setActiveIndex(active_wallet_index + 1); + }, [active_wallet_index]); + + const slider = React.useMemo( + () => + data?.map((item: TWalletAccount) => { + const { loginid, icon, currency_config, balance, currency, landing_company_name, gradient_card_class } = + item; + return ( +
+ +
+ ); + }), + [data?.length] + ); + + return ( + +
+
{slider}
+
+
+ +
+
+ ); +}); + +export default CardsSliderSwiper; diff --git a/packages/appstore/src/components/wallet-cards-carousel/index.ts b/packages/appstore/src/components/wallet-cards-carousel/index.ts new file mode 100644 index 000000000000..7a4b6f3d0f73 --- /dev/null +++ b/packages/appstore/src/components/wallet-cards-carousel/index.ts @@ -0,0 +1,3 @@ +import WalletCardsCarousel from './wallet-cards-carousel'; + +export default WalletCardsCarousel; diff --git a/packages/appstore/src/components/wallet-cards-carousel/wallet-cards-carousel.scss b/packages/appstore/src/components/wallet-cards-carousel/wallet-cards-carousel.scss new file mode 100644 index 000000000000..9279aafa264b --- /dev/null +++ b/packages/appstore/src/components/wallet-cards-carousel/wallet-cards-carousel.scss @@ -0,0 +1,36 @@ +.wallet-cards-carousel { + margin: -1.6rem -1.6rem -1.4rem; + padding: 2.4rem 0 1.6rem; + + &__viewport { + overflow: hidden; + } + &__container { + height: 100%; + display: flex; + align-items: center; + justify-content: flex-start; + gap: 2.4rem; + } + &__pagination { + margin-block: 1.6rem; + } + + &__buttons { + display: flex; + justify-content: center; + gap: 0.8rem; + } +} + +.wallet-carousel-content-container { + display: flex; + padding: 1.6rem; + flex-direction: column; + align-items: center; + background-color: var(--general-main-1); + + &-demo { + background-color: var(--wallet-demo-bg-color); + } +} diff --git a/packages/appstore/src/components/wallet-cards-carousel/wallet-cards-carousel.tsx b/packages/appstore/src/components/wallet-cards-carousel/wallet-cards-carousel.tsx new file mode 100644 index 000000000000..5473bdf3bf45 --- /dev/null +++ b/packages/appstore/src/components/wallet-cards-carousel/wallet-cards-carousel.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { useActiveWallet } from '@deriv/hooks'; +import { observer, useStore } from '@deriv/stores'; +import WalletButton from 'Components/wallet-button'; +import { getWalletHeaderButtons } from 'Constants/utils'; +import CardsSliderSwiper from './cards-slider-swiper'; +import './wallet-cards-carousel.scss'; + +const WalletCardsCarousel = observer(() => { + const { ui, traders_hub } = useStore(); + const { setIsWalletModalVisible } = ui; + const { setWalletModalActiveWalletID, setWalletModalActiveTab } = traders_hub; + const active_wallet = useActiveWallet(); + + const wallet_buttons = getWalletHeaderButtons(active_wallet?.is_demo || false); + + return ( +
+ +
+ {wallet_buttons.map(button => { + button.action = () => { + setWalletModalActiveTab(button.name); + setIsWalletModalVisible(true); + setWalletModalActiveWalletID(active_wallet?.loginid); + }; + + return ; + })} +
+
+ ); +}); + +export default WalletCardsCarousel; diff --git a/packages/appstore/src/components/wallet-content/__tests__/wallet-content-divider.spec.tsx b/packages/appstore/src/components/wallet-content/__tests__/wallet-content-divider.spec.tsx new file mode 100644 index 000000000000..bddb0ec95bcc --- /dev/null +++ b/packages/appstore/src/components/wallet-content/__tests__/wallet-content-divider.spec.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import WalletContentDivider from '../wallet-content-divider'; + +describe('', () => { + it('Check classname for NOT demo', () => { + const { container } = render(); + + expect(container.childNodes[0]).toHaveClass('wallet-content__divider'); + expect(container.childNodes[0]).not.toHaveClass('wallet-content__divider-demo'); + }); + + it('Check classname for demo', () => { + const { container } = render(); + + expect(container.childNodes[0]).toHaveClass('wallet-content__divider'); + expect(container.childNodes[0]).toHaveClass('wallet-content__divider-demo'); + }); +}); diff --git a/packages/appstore/src/components/wallet-content/__tests__/wallet-content.spec.tsx b/packages/appstore/src/components/wallet-content/__tests__/wallet-content.spec.tsx new file mode 100644 index 000000000000..925c2bd480c1 --- /dev/null +++ b/packages/appstore/src/components/wallet-content/__tests__/wallet-content.spec.tsx @@ -0,0 +1,82 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { mockStore, StoreProvider } from '@deriv/stores'; +import WalletContent from '../wallet-content'; + +const mockedRootStore = mockStore({ + modules: { + cfd: { + toggleCompareAccountsModal: jest.fn(), + }, + }, +}); + +jest.mock('./../../containers/currency-switcher-container', () => jest.fn(({ children }) =>
{children}
)); + +jest.mock('@deriv/hooks', () => ({ + ...jest.requireActual('@deriv/hooks'), + useActiveWallet: jest.fn(), +})); + +describe('', () => { + it('Check class', () => { + render( + + + + ); + + const wrapper = screen.queryByTestId('dt_wallet-content'); + expect(wrapper).toHaveClass('wallet-content'); + expect(wrapper).not.toHaveClass('wallet-content__demo'); + }); + + it('Check class for demo', () => { + render( + + + + ); + + const wrapper = screen.queryByTestId('dt_wallet-content'); + expect(wrapper).toHaveClass('wallet-content'); + expect(wrapper).toHaveClass('wallet-content__demo'); + }); + + it('Check there is NOT disclaimer for demo', () => { + render( + + + + ); + + const disclaimer = screen.queryByTestId('dt_disclaimer_wrapper'); + + expect(disclaimer).not.toBeInTheDocument(); + }); + + it('Check there is NOT disclaimer for Non-EU', () => { + render( + + + + ); + + const disclaimer = screen.queryByTestId('dt_disclaimer_wrapper'); + + expect(disclaimer).not.toBeInTheDocument(); + }); + + it('Check there is disclaimer for EU and not demo', () => { + render( + + + + ); + + const disclaimer = screen.queryByTestId('dt_disclaimer_wrapper'); + + expect(disclaimer).toBeInTheDocument(); + }); +}); diff --git a/packages/appstore/src/components/wallet-content/__tests__/wallet-transfer-block.spec.tsx b/packages/appstore/src/components/wallet-content/__tests__/wallet-transfer-block.spec.tsx new file mode 100644 index 000000000000..71b3d47ae566 --- /dev/null +++ b/packages/appstore/src/components/wallet-content/__tests__/wallet-transfer-block.spec.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import WalletTransferBlock from '../wallet-transfer-block'; +import { TWalletAccount } from 'Types'; +import { StoreProvider, mockStore } from '@deriv/stores'; + +const wallet_account: TWalletAccount = { + name: 'USD', + currency: 'USD', + icon: '', + balance: 10415.24, + icon_type: 'fiat', + landing_company_name: 'svg', + is_disabled: false, + is_virtual: false, + loginid: 'CRW10001', +}; + +jest.mock('./../../containers/currency-switcher-container', () => jest.fn(({ children }) =>
{children}
)); + +const mockedRootStore = mockStore({ + modules: { + cfd: { + toggleCompareAccountsModal: jest.fn(), + }, + }, +}); + +describe('', () => { + it('Check balance', () => { + render( + + + + ); + const { currency } = wallet_account; + + const balance_title = screen.getByText(`10,415.24 ${currency}`); + + expect(balance_title).toBeInTheDocument(); + }); + + it('Check loginid', () => { + render( + + + + ); + const { loginid } = wallet_account; + + const loginid_title = screen.getByText(String(loginid)); + + expect(loginid_title).toBeInTheDocument(); + }); +}); diff --git a/packages/appstore/src/components/wallet-content/index.ts b/packages/appstore/src/components/wallet-content/index.ts new file mode 100644 index 000000000000..fee1bf581d8b --- /dev/null +++ b/packages/appstore/src/components/wallet-content/index.ts @@ -0,0 +1,3 @@ +import WalletContent from './wallet-content'; + +export default WalletContent; diff --git a/packages/appstore/src/components/wallet-content/wallet-cfds-listing.tsx b/packages/appstore/src/components/wallet-content/wallet-cfds-listing.tsx new file mode 100644 index 000000000000..bfb0a014da40 --- /dev/null +++ b/packages/appstore/src/components/wallet-content/wallet-cfds-listing.tsx @@ -0,0 +1,253 @@ +import React from 'react'; +import { Text, StaticUrl, Button } from '@deriv/components'; +import { useActiveWallet, useCFDCanGetMoreMT5Accounts } from '@deriv/hooks'; +import { formatMoney, isCryptocurrency } from '@deriv/shared'; +import { localize, Localize } from '@deriv/translations'; +import ListingContainer from 'Components/containers/listing-container'; +import TradingAppCard from 'Components/containers/trading-app-card'; +import PlatformLoader from 'Components/pre-loader/platform-loader'; +import { getHasDivider } from 'Constants/utils'; +import { useStore, observer } from '@deriv/stores'; +import GetMoreAccounts from 'Components/get-more-accounts'; +import { TDetailsOfEachMT5Loginid } from 'Types'; +import './wallet-content.scss'; + +type TProps = { + fiat_wallet_currency?: string; +}; + +const CryptoCFDs = observer(({ fiat_wallet_currency }: TProps) => { + const { traders_hub, ui } = useStore(); + const { setWalletModalActiveWalletID, setWalletModalActiveTab } = traders_hub; + + const { is_mobile, setIsWalletModalVisible } = ui; + + const wallet_account = useActiveWallet(); + if (!wallet_account) return null; + + return ( +
+ + + + +
+ ); +}); + +const FiatCFDs = observer(() => { + const { traders_hub } = useStore(); + const { + selected_region, + getExistingAccounts, + selected_account_type, + available_dxtrade_accounts, + combined_cfd_mt5_accounts, + toggleAccountTypeModalVisibility, + } = traders_hub; + + const can_get_more_cfd_mt5_accounts = useCFDCanGetMoreMT5Accounts(); + + const wallet_account = useActiveWallet(); + if (!wallet_account) return null; + + const getMT5AccountAuthStatus = (current_acc_status: string) => { + if (current_acc_status === 'proof_failed') { + return 'failed'; + } + if (current_acc_status === 'verification_pending') { + return 'pending'; + } + return null; + }; + + return ( + +
+ + + +
+ {combined_cfd_mt5_accounts.map((existing_account, index) => { + const { + action_type, + description, + icon, + key, + landing_company_short, + market_type, + name, + platform, + status, + sub_title, + } = existing_account; + const list_size = combined_cfd_mt5_accounts.length; + const mt5_account_status = status ? getMT5AccountAuthStatus(status) : null; + return ( + + ); + })} + {can_get_more_cfd_mt5_accounts && ( + + )} + {available_dxtrade_accounts?.length > 0 && ( +
+ + + +
+ )} + {available_dxtrade_accounts?.map(account => { + const existing_accounts = getExistingAccounts(account.platform ?? '', account.market_type ?? ''); + const has_existing_accounts = existing_accounts.length > 0; + return has_existing_accounts ? ( + existing_accounts.map((existing_account: TDetailsOfEachMT5Loginid) => ( + + )) + ) : ( + + ); + })} +
+ ); +}); + +const WalletCFDsListing = observer(({ fiat_wallet_currency = 'USD' }: TProps) => { + const { + client, + modules: { cfd }, + ui, + } = useStore(); + + const { toggleCompareAccountsModal } = cfd; + const { is_landing_company_loaded, is_logging_in, is_switching } = client; + const { is_mobile } = ui; + + const wallet_account = useActiveWallet(); + + if (!wallet_account || !is_landing_company_loaded || is_switching || is_logging_in) + return ( +
+ +
+ ); + + const { currency, landing_company_name, is_virtual } = wallet_account; + const accounts_sub_text = + landing_company_name === 'svg' || is_virtual ? ( + + ) : ( + + ); + + const is_fiat = !isCryptocurrency(currency) && currency !== 'USDT'; + + return ( + + + + +
+ + {accounts_sub_text} + +
+
+ ) + } + description={ + + Learn more' + } + components={[]} + /> + + } + is_outside_grid_container={!is_fiat} + > + {is_mobile && ( +
+ + {accounts_sub_text} + +
+ )} + {is_fiat ? : } + + ); +}); + +export default WalletCFDsListing; diff --git a/packages/appstore/src/components/wallet-content/wallet-content-divider.tsx b/packages/appstore/src/components/wallet-content/wallet-content-divider.tsx new file mode 100644 index 000000000000..442c7c4ad486 --- /dev/null +++ b/packages/appstore/src/components/wallet-content/wallet-content-divider.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import classNames from 'classnames'; + +const WalletContentDivider = ({ is_demo_divider }: { is_demo_divider?: boolean }) => ( +
+); + +export default WalletContentDivider; diff --git a/packages/appstore/src/components/wallet-content/wallet-content.scss b/packages/appstore/src/components/wallet-content/wallet-content.scss new file mode 100644 index 000000000000..dca6d6f6931d --- /dev/null +++ b/packages/appstore/src/components/wallet-content/wallet-content.scss @@ -0,0 +1,75 @@ +.wallet-content { + &__loader { + padding: 2.4rem; + + @include mobile { + padding: 1.6rem 0; + } + } + + &__divider { + border: 1px solid var(--general-section-1); + margin-inline: 2.4rem; + + &-demo { + border-color: var(--wallet-demo-divider-color); + } + } + + &__border-reset { + border: 0; + } + + &__disclaimer { + width: 100%; + height: 7rem; + display: flex; + align-items: center; + backface-visibility: hidden; + background-color: var(--wallet-eu-disclaimer); + border-radius: 0 0 $BORDER_RADIUS * 4 $BORDER_RADIUS * 4; + + @include mobile { + height: 8rem; + border: 1px solid var(--wallet-eu-disclaimer); + } + + &-text { + padding: 0 4rem; + + @include mobile { + padding: 0 1.5rem; + } + } + } + + &__cfd { + & .listing-container__content { + padding-block-start: 0; + } + + & .cfd-accounts__compare-table-title { + padding-left: 0.8rem; + } + + &-crypto { + display: flex; + flex-direction: column; + align-items: center; + padding: 2.4rem; + + @include mobile { + padding: 0; + } + + &-title { + padding: 2.4rem; + + @include mobile { + padding: 2.4rem 1.6rem; + text-align: center; + } + } + } + } +} diff --git a/packages/appstore/src/components/wallet-content/wallet-content.tsx b/packages/appstore/src/components/wallet-content/wallet-content.tsx new file mode 100644 index 000000000000..195b87c978b9 --- /dev/null +++ b/packages/appstore/src/components/wallet-content/wallet-content.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import classNames from 'classnames'; +import ContentDivider from './wallet-content-divider'; +import WalletCfdsListing from './wallet-cfds-listing'; +import WalletOptionsAndMultipliersListing from './wallet-option-multipliers-listing'; +import EUDisclaimer from 'Components/eu-disclaimer'; +import './wallet-content.scss'; + +type TProps = { + is_demo: boolean; + is_malta_wallet: boolean; +}; + +const WalletContent = ({ is_demo, is_malta_wallet }: TProps) => { + return ( +
+ + + + + {is_malta_wallet && !is_demo && ( + + )} +
+ ); +}; + +export default WalletContent; diff --git a/packages/appstore/src/components/wallet-content/wallet-option-multipliers-listing.tsx b/packages/appstore/src/components/wallet-content/wallet-option-multipliers-listing.tsx new file mode 100644 index 000000000000..a3f39f67a5cb --- /dev/null +++ b/packages/appstore/src/components/wallet-content/wallet-option-multipliers-listing.tsx @@ -0,0 +1,131 @@ +import React from 'react'; +import { Text, StaticUrl } from '@deriv/components'; +import { Localize, localize } from '@deriv/translations'; +import ListingContainer from 'Components/containers/listing-container'; +import TradingAppCard from 'Components/containers/trading-app-card'; +import PlatformLoader from 'Components/pre-loader/platform-loader'; +import { getHasDivider } from 'Constants/utils'; +import { Jurisdiction } from '@deriv/shared'; +import { useStore, observer } from '@deriv/stores'; +import { useActiveWallet } from '@deriv/hooks'; +import './wallet-content.scss'; + +type TProps = { + landing_company_name: string | undefined; +}; + +const OptionsTitle = observer(({ landing_company_name }: TProps) => { + const { + ui: { is_mobile }, + } = useStore(); + + const is_svg_wallet = landing_company_name === 'svg'; + + if (is_svg_wallet && !is_mobile) { + return ( + + + + ); + } else if (!is_svg_wallet && !is_mobile) { + return ( + + + + ); + } + return null; +}); + +const ListingContainerDescription = ({ landing_company_name }: TProps) => + landing_company_name === 'svg' ? ( + + , + , + ]} + /> + + ) : ( + + ]} + /> + + ); + +const WalletOptionsAndMultipliersListing = observer(() => { + const { traders_hub, client, ui } = useStore(); + const { setShouldShowCooldownModal, openRealAccountSignup } = ui; + const { + is_landing_company_loaded, + has_maltainvest_account, + real_account_creation_unlock_date, + is_logging_in, + is_switching, + } = client; + const { available_platforms, is_eu_user, is_real, no_MF_account, no_CR_account, is_demo } = traders_hub; + + const wallet_account = useActiveWallet(); + + if (!wallet_account || is_switching || is_logging_in || !is_landing_company_loaded) { + return ( +
+ +
+ ); + } + + const platforms_action_type = + is_demo || (!no_CR_account && !is_eu_user) || (has_maltainvest_account && is_eu_user) ? 'trade' : 'none'; + + const derivAccountAction = () => { + if (no_MF_account) { + if (real_account_creation_unlock_date) { + setShouldShowCooldownModal(true); + } else { + openRealAccountSignup(Jurisdiction.MALTA_INVEST); + } + } else { + openRealAccountSignup(Jurisdiction.SVG); + } + }; + + return ( + } + description={} + is_deriv_platform + > + {is_real && (no_CR_account || no_MF_account) && ( +
+ +
+ )} + {available_platforms.map((available_platform, index) => ( + + ))} +
+ ); +}); + +export default WalletOptionsAndMultipliersListing; diff --git a/packages/appstore/src/components/wallet-content/wallet-transfer-block.tsx b/packages/appstore/src/components/wallet-content/wallet-transfer-block.tsx new file mode 100644 index 000000000000..b2e04796583d --- /dev/null +++ b/packages/appstore/src/components/wallet-content/wallet-transfer-block.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { Button, Text } from '@deriv/components'; +import { formatMoney } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; +import { Localize } from '@deriv/translations'; +import CurrencySwitcherContainer from 'Components/containers/currency-switcher-container'; +import { TWalletAccount } from 'Types'; + +type TProps = { + wallet_account: TWalletAccount; +}; + +const WalletTransferBlock = observer(({ wallet_account }: TProps) => { + const { traders_hub, ui } = useStore(); + const { setIsWalletModalVisible } = ui; + const { setWalletModalActiveWalletID, setWalletModalActiveTab } = traders_hub; + + const { currency, balance, loginid } = wallet_account; + + return ( + { + setWalletModalActiveTab('Transfer'); + setIsWalletModalVisible(true); + setWalletModalActiveWalletID(loginid); + }} + secondary + className='currency-switcher__button' + > + + + } + has_interaction + show_dropdown={false} + > + + + {formatMoney(currency, balance, true)} {currency} + + + {loginid} + + + + ); +}); +export default WalletTransferBlock; diff --git a/packages/appstore/src/components/wallet-deposit/index.ts b/packages/appstore/src/components/wallet-deposit/index.ts new file mode 100644 index 000000000000..7a81ec16480e --- /dev/null +++ b/packages/appstore/src/components/wallet-deposit/index.ts @@ -0,0 +1,3 @@ +import WalletDeposit from './wallet-deposit'; + +export default WalletDeposit; diff --git a/packages/appstore/src/components/wallet-deposit/wallet-deposit.scss b/packages/appstore/src/components/wallet-deposit/wallet-deposit.scss new file mode 100644 index 000000000000..2daf382ce975 --- /dev/null +++ b/packages/appstore/src/components/wallet-deposit/wallet-deposit.scss @@ -0,0 +1,7 @@ +.wallet-deposit { + &__fiat-container { + display: flex; + max-width: 58.8rem; + margin: 0 auto; + } +} diff --git a/packages/appstore/src/components/wallet-deposit/wallet-deposit.tsx b/packages/appstore/src/components/wallet-deposit/wallet-deposit.tsx new file mode 100644 index 000000000000..4e26439f5c3f --- /dev/null +++ b/packages/appstore/src/components/wallet-deposit/wallet-deposit.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { useCurrencyConfig } from '@deriv/hooks'; +import { useStore, observer } from '@deriv/stores'; +import { Div100vhContainer } from '@deriv/components'; +import DepositFiatIframe from '@deriv/cashier/src/modules/deposit-fiat/components/deposit-fiat-iframe/deposit-fiat-iframe'; +import './wallet-deposit.scss'; + +const WalletDeposit = observer(() => { + const { client, ui } = useStore(); + const { is_mobile } = ui; + const { currency, loginid } = client; + + const { getConfig } = useCurrencyConfig(); + const currency_config = getConfig(currency); + const is_crypto = currency_config?.is_crypto; + + //TODO: remove when selected wallet will be provided to WalletDeposit props + const real_fiat_wallet = loginid?.startsWith('CRW') && !is_crypto; + + return real_fiat_wallet ? ( + + + + ) : ( +
Deposit Development Is In Progress
+ ); +}); + +export default WalletDeposit; diff --git a/packages/appstore/src/components/wallet-header/__tests__/wallet-header.spec.tsx b/packages/appstore/src/components/wallet-header/__tests__/wallet-header.spec.tsx new file mode 100644 index 000000000000..b506d6c39f44 --- /dev/null +++ b/packages/appstore/src/components/wallet-header/__tests__/wallet-header.spec.tsx @@ -0,0 +1,326 @@ +import React from 'react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import '@testing-library/jest-dom'; +import { getStatusBadgeConfig } from '@deriv/account'; +import { mockStore, StoreProvider } from '@deriv/stores'; +import { TWalletAccount } from 'Types'; +import WalletHeader from '..'; + +const mockedRootStore = mockStore({}); + +jest.mock('@deriv/account', () => ({ + ...jest.requireActual('@deriv/account'), + getStatusBadgeConfig: jest.fn(() => ({ icon: '', text: '' })), +})); + +jest.mock('@deriv/hooks', () => ({ + ...jest.requireActual('@deriv/hooks'), + useWalletModalActionHandler: jest.fn(() => ({ setWalletModalActiveTabIndex: jest.fn(), handleAction: jest.fn() })), +})); + +describe('', () => { + const default_mocked_props: TWalletAccount = { + is_demo: false, + currency: 'USD', + landing_company_name: 'svg', + balance: 10000, + loginid: 'CRW123123', + is_malta_wallet: false, + is_selected: true, + gradient_header_class: 'wallet-header__usd-bg', + gradient_card_class: 'wallet-card__usd-bg', + wallet_currency_type: '', + currency_config: undefined, + icon: '', + }; + + describe('Check currency card', () => { + it('Should render right currency card for DEMO', () => { + const mocked_props = { ...default_mocked_props, is_demo: true }; + render( + + + + ); + + expect(screen.queryByTestId(`dt_demo`)).toBeInTheDocument(); + }); + + it('Should render right currency card for REAL SVG fiat', () => { + const mocked_props = { ...default_mocked_props, currency: 'AUD' }; + render( + + + + ); + + expect(screen.queryByTestId(`dt_${mocked_props.currency.toLowerCase()}`)).toBeInTheDocument(); + }); + + it('Should render right currency card for REAL SVG crypto', () => { + const mocked_props = { ...default_mocked_props, currency: 'ETH' }; + render( + + + + ); + + expect(screen.queryByTestId(`dt_${mocked_props.currency.toLowerCase()}`)).toBeInTheDocument(); + }); + + it('Should render right currency card for REAL MALTA fiat', () => { + const mocked_props = { ...default_mocked_props, currency: 'ETH', landing_company_name: 'malta' }; + render( + + + + ); + + expect(screen.queryByTestId(`dt_${mocked_props.currency.toLowerCase()}`)).toBeInTheDocument(); + }); + }); + + describe('Check balance', () => { + it('Should render right balance with balance as props', () => { + const mocked_props = { + ...default_mocked_props, + currency: 'EUR', + landing_company_name: 'malta', + balance: 2345.56, + }; + render( + + + + ); + + expect(screen.getByText('2,345.56 EUR')).toBeInTheDocument(); + }); + + it('Should render balance === 0.00', () => { + const mocked_props = { + ...default_mocked_props, + currency: 'EUR', + landing_company_name: 'malta', + balance: 0, + }; + render( + + + + ); + + expect(screen.queryByText(`0.00 ${mocked_props.currency}`)).toBeInTheDocument(); + }); + + it('Should render badge Pending verification', () => { + getStatusBadgeConfig.mockReturnValue({ icon: '', text: 'Pending verification' }); + + const mocked_props = { + ...default_mocked_props, + currency: 'EUR', + landing_company_name: 'malta', + balance: 0, + }; + + const mocked_store = mockStore({ + client: { + loginid: 'MFW1231', + }, + traders_hub: { multipliers_account_status: 'pending', is_eu_user: true }, + }); + + render( + + + + ); + + expect(screen.queryByText(/Pending verification/i)).toBeInTheDocument(); + expect(screen.queryByText(/balance/i)).not.toBeInTheDocument(); + }); + + it('Should render badge Verification failed', () => { + getStatusBadgeConfig.mockReturnValue({ icon: '', text: 'Verification failed' }); + + const mocked_props = { + ...default_mocked_props, + currency: 'EUR', + landing_company_name: 'malta', + balance: 0, + }; + + const mocked_store = mockStore({ + client: { + loginid: 'MFW1231', + }, + traders_hub: { multipliers_account_status: 'failed', is_eu_user: true }, + }); + + render( + + + + ); + + expect(screen.queryByText(/Verification failed/i)).toBeInTheDocument(); + expect(screen.queryByText(/balance/i)).not.toBeInTheDocument(); + }); + + it('Should render badge Need verification', () => { + getStatusBadgeConfig.mockReturnValue({ icon: '', text: 'Need verification' }); + + const mocked_props = { + ...default_mocked_props, + currency: 'EUR', + landing_company_name: 'malta', + balance: 0, + }; + + const mocked_store = mockStore({ + client: { + loginid: 'MFW1231', + }, + traders_hub: { multipliers_account_status: 'need_verification', is_eu_user: true }, + }); + + render( + + + + ); + + expect(screen.queryByText(/Need verification/i)).toBeInTheDocument(); + expect(screen.queryByText(/balance/i)).not.toBeInTheDocument(); + }); + }); + + describe('Check buttons', () => { + it('Buttons collapsed', () => { + const mocked_props = { + ...default_mocked_props, + currency: 'EUR', + balance: 0, + is_selected: false, + is_demo: true, + }; + + render( + + + + ); + + expect(screen.queryByRole('button', { name: /Transfer/i })).not.toBeInTheDocument(); + }); + + it('Buttons uncollapsed', () => { + const mocked_props = { + ...default_mocked_props, + currency: 'EUR', + balance: 0, + is_selected: true, + is_demo: true, + }; + + const mocked_store = mockStore({ + client: { + loginid: 'CRW1231', + }, + }); + + render( + + + + ); + + expect(screen.getByRole('button', { name: /Transfer/i })).toBeInTheDocument(); + }); + + it('Arrow button click and switchAccount should be called', async () => { + const mocked_props = { + ...default_mocked_props, + balance: 0, + is_selected: false, + is_demo: true, + loginid: 'CRW1231', + }; + + render( + + + + ); + + const arrow_btn = screen.getByTestId('dt_arrow'); + userEvent.click(arrow_btn); + + await waitFor(() => { + expect(mockedRootStore.client.switchAccount).toBeCalledTimes(1); + }); + }); + + it('Check buttons for demo', () => { + const mocked_props = { + ...default_mocked_props, + balance: 0, + currency: 'EUR', + is_demo: true, + loginid: 'VRW123123', + }; + + const mocked_store = mockStore({ + client: { + loginid: 'VRW123123', + }, + }); + + render( + + + + ); + + const transfer_btn = screen.getByRole('button', { name: /Transfer/i }); + const transactions_btn = screen.getByRole('button', { name: /Transactions/i }); + const reset_btn = screen.getByRole('button', { name: /Reset balance/i }); + + expect(transfer_btn).toBeInTheDocument(); + expect(transactions_btn).toBeInTheDocument(); + expect(reset_btn).toBeInTheDocument(); + }); + + it('Check buttons for real', () => { + const mocked_props = { + ...default_mocked_props, + balance: 1230, + currency: 'EUR', + loginid: 'CRW123123', + }; + + const mocked_store = mockStore({ + client: { + loginid: 'CRW123123', + }, + }); + + render( + + + + ); + + const deposit_btn = screen.getByRole('button', { name: /Deposit/i }); + const withdraw_btn = screen.getByRole('button', { name: /Withdraw/i }); + const transfer_btn = screen.getByRole('button', { name: /Transfer/i }); + const transactions_btn = screen.getByRole('button', { name: /Transactions/i }); + + expect(deposit_btn).toBeInTheDocument(); + expect(withdraw_btn).toBeInTheDocument(); + expect(transfer_btn).toBeInTheDocument(); + expect(transactions_btn).toBeInTheDocument(); + }); + }); +}); diff --git a/packages/appstore/src/components/wallet-header/index.ts b/packages/appstore/src/components/wallet-header/index.ts new file mode 100644 index 000000000000..ed176c4b242f --- /dev/null +++ b/packages/appstore/src/components/wallet-header/index.ts @@ -0,0 +1,3 @@ +import WalletHeader from './wallet-header'; + +export default WalletHeader; diff --git a/packages/appstore/src/components/wallet-header/wallet-currency-card.tsx b/packages/appstore/src/components/wallet-header/wallet-currency-card.tsx new file mode 100644 index 000000000000..967614865c52 --- /dev/null +++ b/packages/appstore/src/components/wallet-header/wallet-currency-card.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { WalletIcon } from '@deriv/components'; +import { TWalletAccount } from 'Types'; + +type TWalletCurrencyCard = Pick & { + gradient_class?: string; + icon_type?: string; +}; + +const WalletCurrencyCard = ({ is_demo, currency, icon, icon_type, gradient_class }: TWalletCurrencyCard) => { + return ( +
+ +
+ ); +}; + +export default WalletCurrencyCard; diff --git a/packages/appstore/src/components/wallet-header/wallet-header-balance.tsx b/packages/appstore/src/components/wallet-header/wallet-header-balance.tsx new file mode 100644 index 000000000000..d985a9b0cd06 --- /dev/null +++ b/packages/appstore/src/components/wallet-header/wallet-header-balance.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { Text, StatusBadge } from '@deriv/components'; +import { getStatusBadgeConfig } from '@deriv/account'; +import { useStore, observer } from '@deriv/stores'; +import { Localize } from '@deriv/translations'; +import { TWalletAccount } from 'Types'; +import { formatMoney } from '@deriv/shared'; + +type TWalletHeaderBalance = Pick; + +const WalletHeaderBalance = observer(({ balance, currency }: TWalletHeaderBalance) => { + const { + traders_hub: { openFailedVerificationModal, multipliers_account_status, is_eu_user }, + } = useStore(); + + const balance_amount = ( + + + + ); + + // TODO: just for test use empty object. When BE will be ready it will be fixed + const { text: badge_text, icon: badge_icon } = getStatusBadgeConfig( + multipliers_account_status, + openFailedVerificationModal, + { + platform: '', + category: '', + type: '', + jurisdiction: '', + } + ); + + return ( +
+ {multipliers_account_status && is_eu_user ? ( + + ) : ( + + + + + {balance_amount} + + )} +
+ ); +}); +export default WalletHeaderBalance; diff --git a/packages/appstore/src/components/wallet-header/wallet-header-buttons.tsx b/packages/appstore/src/components/wallet-header/wallet-header-buttons.tsx new file mode 100644 index 000000000000..2a13cde524cd --- /dev/null +++ b/packages/appstore/src/components/wallet-header/wallet-header-buttons.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { observer, useStore } from '@deriv/stores'; +import { TWalletAccount, TWalletButton } from 'Types'; +import WalletButton from 'Components/wallet-button'; + +type TWalletHeaderButtons = { + is_disabled: boolean; + is_open: boolean; + buttons: TWalletButton[]; + wallet_account: TWalletAccount; +}; + +const WalletHeaderButtons = observer(({ is_disabled, is_open, buttons, wallet_account }: TWalletHeaderButtons) => { + const { ui, traders_hub } = useStore(); + const { setIsWalletModalVisible } = ui; + const { setWalletModalActiveWalletID, setWalletModalActiveTab } = traders_hub; + + return ( +
+ {buttons.map(button => { + button.action = () => { + setWalletModalActiveTab(button.name); + setIsWalletModalVisible(true); + setWalletModalActiveWalletID(wallet_account.loginid); + }; + + return ( + + ); + })} +
+ ); +}); +export default WalletHeaderButtons; diff --git a/packages/appstore/src/components/wallet-header/wallet-header-title.tsx b/packages/appstore/src/components/wallet-header/wallet-header-title.tsx new file mode 100644 index 000000000000..ad88f61d55f5 --- /dev/null +++ b/packages/appstore/src/components/wallet-header/wallet-header-title.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { Text, Badge } from '@deriv/components'; +import { Localize } from '@deriv/translations'; +import { TWalletAccount } from 'Types'; + +type TWalletHeaderTitle = Pick; + +const WalletHeaderTitle = ({ is_demo, currency, landing_company_name }: TWalletHeaderTitle) => { + return ( +
+ + {is_demo ? ( + + ) : ( + + )} + + {!is_demo && ( + + )} +
+ ); +}; + +export default WalletHeaderTitle; diff --git a/packages/appstore/src/components/wallet-header/wallet-header.scss b/packages/appstore/src/components/wallet-header/wallet-header.scss new file mode 100644 index 000000000000..adbd6d34988c --- /dev/null +++ b/packages/appstore/src/components/wallet-header/wallet-header.scss @@ -0,0 +1,91 @@ +.wallet-header { + padding: 2.4rem; + background-color: var(--general-main-1); + border-radius: $BORDER_RADIUS * 4; + height: 12.8rem; + + &__demo { + position: relative; + background-color: var(--wallet-demo-bg-color); + } + &__demo:before { + content: ' '; + display: block; + position: absolute; + inset: 0; + opacity: 0.1; + background-repeat: repeat; + background-position: 3% 30%; + border-radius: $BORDER_RADIUS * 4; + } + + .theme--light &__demo:before { + background-image: url('./../../public/images/wallet-header-demo-bg.svg'); + } + + .theme--dark &__demo:before { + background-image: url('./../../public/images/wallet-header-demo-bg-dark.svg'); + } + + &__container { + position: relative; + display: flex; + height: 8rem; + } + + &__currency { + display: flex; + justify-content: center; + align-items: center; + width: 12.8rem; + margin-right: 2.4rem; + border-radius: $BORDER_RADIUS * 2; + } + + &__description { + display: flex; + flex-direction: column; + justify-content: space-between; + padding-block: 0.5rem; + + &-title { + display: flex; + align-items: center; + } + + &-badge { + margin-left: 0.8rem; + } + + &-buttons { + display: flex; + } + } + + &__balance { + display: flex; + align-self: center; + padding-block: 1.3rem; + margin-left: auto; + + &-title-amount { + display: flex; + flex-direction: column; + padding-inline: 2.4rem; + + &-title { + align-self: flex-end; + } + } + + &-arrow-icon { + cursor: pointer; + transition: transform 0.3s ease; + transform: rotate(0deg); + align-self: center; + } + &-arrow-icon-active { + transform: rotate(180deg); + } + } +} diff --git a/packages/appstore/src/components/wallet-header/wallet-header.tsx b/packages/appstore/src/components/wallet-header/wallet-header.tsx new file mode 100644 index 000000000000..a8d7f9957247 --- /dev/null +++ b/packages/appstore/src/components/wallet-header/wallet-header.tsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { Icon } from '@deriv/components'; +import classNames from 'classnames'; +import WalletCurrencyCard from './wallet-currency-card'; +import WalletHeaderButtons from './wallet-header-buttons'; +import WalletHeaderTitle from './wallet-header-title'; +import WalletHeaderBalance from './wallet-header-balance'; +import { TWalletAccount } from 'Types'; +import { getWalletHeaderButtons } from 'Constants/utils'; +import { observer, useStore } from '@deriv/stores'; +import './wallet-header.scss'; + +type TWalletHeader = { + wallet_account: TWalletAccount; +}; + +const WalletHeader = observer(({ wallet_account }: TWalletHeader) => { + const { client, traders_hub } = useStore(); + const { switchAccount, loginid } = client; + const is_active = wallet_account.is_selected; + // const [is_loading, setIsLoading] = useState(false); + const { multipliers_account_status } = traders_hub; + + const { is_demo, currency, gradient_card_class, currency_config, icon, balance, landing_company_name } = + wallet_account; + + const wallet_buttons = getWalletHeaderButtons(wallet_account.is_demo); + + const onArrowClickHandler = async () => { + // setIsLoading(true); + if (loginid !== wallet_account.loginid) await switchAccount(wallet_account.loginid); + // setIsLoading(false); + }; + + /** @todo: uncomment this when we have a skeleton loader for wallet header*/ + // useEffect(() => { + // if (is_authorize) { + // setIsLoading(false); + // } + // }, [is_authorize]);} + + return ( +
+
+ +
+ + +
+
+ + +
+
+
+ ); +}); + +export default WalletHeader; diff --git a/packages/appstore/src/components/wallet-jurisdiction-badge/__tests__/wallet-jurisdiction-badge.spec.tsx b/packages/appstore/src/components/wallet-jurisdiction-badge/__tests__/wallet-jurisdiction-badge.spec.tsx new file mode 100644 index 000000000000..8c036a66244c --- /dev/null +++ b/packages/appstore/src/components/wallet-jurisdiction-badge/__tests__/wallet-jurisdiction-badge.spec.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import WalletJurisdictionBadge from '../wallet-jurisdiction-badge'; +import { render, screen } from '@testing-library/react'; + +describe('WalletJurisdictionBadge', () => { + it('Should render demo badge', () => { + render(); + + expect(screen.getByText('Demo')).toBeInTheDocument(); + }); + + it('Should render svg badge', () => { + render(); + + expect(screen.getByText('SVG')).toBeInTheDocument(); + }); + + it('Should render malta badge', () => { + render(); + + expect(screen.getByText('MALTA')).toBeInTheDocument(); + }); +}); diff --git a/packages/appstore/src/components/wallet-jurisdiction-badge/index.ts b/packages/appstore/src/components/wallet-jurisdiction-badge/index.ts new file mode 100644 index 000000000000..eb311bb13e05 --- /dev/null +++ b/packages/appstore/src/components/wallet-jurisdiction-badge/index.ts @@ -0,0 +1,3 @@ +import WalletJurisdictionBadge from './wallet-jurisdiction-badge'; + +export { WalletJurisdictionBadge }; diff --git a/packages/appstore/src/components/wallet-jurisdiction-badge/wallet-jurisdiction-badge.tsx b/packages/appstore/src/components/wallet-jurisdiction-badge/wallet-jurisdiction-badge.tsx new file mode 100644 index 000000000000..3447b05ea32d --- /dev/null +++ b/packages/appstore/src/components/wallet-jurisdiction-badge/wallet-jurisdiction-badge.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { Badge } from '@deriv/components'; +import { localize } from '@deriv/translations'; + +type TWalletJurisdictionBadge = { + is_demo: boolean; + shortcode?: string; +}; + +const WalletJurisdictionBadge = ({ is_demo, shortcode }: TWalletJurisdictionBadge) => { + return is_demo ? ( + + ) : ( + + ); +}; + +export default WalletJurisdictionBadge; diff --git a/packages/appstore/src/components/wallet-transfer/__tests__/wallet-transfer.spec.tsx b/packages/appstore/src/components/wallet-transfer/__tests__/wallet-transfer.spec.tsx new file mode 100644 index 000000000000..888267235c12 --- /dev/null +++ b/packages/appstore/src/components/wallet-transfer/__tests__/wallet-transfer.spec.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import WalletTransfer from '../wallet-transfer'; +import { APIProvider } from '@deriv/api'; +import { mockStore, StoreProvider } from '@deriv/stores'; +import { render, screen } from '@testing-library/react'; + +jest.mock('../transfer-account-selector', () => jest.fn(() =>
TransferAccountSelector
)); + +jest.mock('@deriv/components', () => ({ + ...jest.requireActual('@deriv/components'), + AmountInput: () =>
AmountInput
, +})); + +jest.mock('@deriv/api', () => ({ + ...jest.requireActual('@deriv/api'), + useFetch: jest.fn(() => ({ data: undefined })), +})); + +describe('WalletTransfer', () => { + const mock = mockStore({ + client: { + loginid: 'CRW1030', + accounts: { + CRW1030: { + token: 'token', + }, + }, + }, + }); + + it('Should render two amount inputs and two transfer account selectors', () => { + render( + + + + + + ); + + expect(screen.getAllByText('AmountInput')).toHaveLength(2); + expect(screen.getAllByText('TransferAccountSelector')).toHaveLength(2); + }); +}); diff --git a/packages/appstore/src/components/wallet-transfer/index.ts b/packages/appstore/src/components/wallet-transfer/index.ts new file mode 100644 index 000000000000..488f1606ef01 --- /dev/null +++ b/packages/appstore/src/components/wallet-transfer/index.ts @@ -0,0 +1,3 @@ +import WalletTransfer from './wallet-transfer'; + +export default WalletTransfer; diff --git a/packages/appstore/src/components/wallet-transfer/transfer-account-selector/__tests__/transfer-account-list.spec.tsx b/packages/appstore/src/components/wallet-transfer/transfer-account-selector/__tests__/transfer-account-list.spec.tsx new file mode 100644 index 000000000000..93c945e7c9ee --- /dev/null +++ b/packages/appstore/src/components/wallet-transfer/transfer-account-selector/__tests__/transfer-account-list.spec.tsx @@ -0,0 +1,98 @@ +import React from 'react'; +import TransferAccountList from '../transfer-account-list'; +import { render, screen } from '@testing-library/react'; + +jest.mock('../../wallet-transfer-tile/wallet-transfer-tile', () => jest.fn(() =>
WalletTransferTile
)); + +describe('TransferAccountList', () => { + let mocked_props: React.ComponentProps; + + beforeEach(() => { + mocked_props = { + is_mobile: false, + selected_account: { + account_type: 'wallet', + balance: 100, + currency: 'USD', + display_currency_code: 'USD', + gradient_class: 'wallet-card__usd-bg', + icon: 'Icon', + is_demo: false, + loginid: 'CRW1000', + shortcode: 'svg', + type: 'fiat', + active_wallet_icon: 'Wallet Icon', + }, + setIsListModalOpen: jest.fn(), + setSelectedAccount: jest.fn(), + transfer_accounts: { + trading_accounts: { + CR1000: { + account_type: 'trading', + balance: 10, + currency: 'USD', + display_currency_code: 'USD', + gradient_class: 'wallet-card__usd-bg', + icon: 'Icon', + is_demo: false, + loginid: '1', + shortcode: 'svg', + type: 'fiat', + active_wallet_icon: 'IcCurrencyUsd', + }, + MTR2000: { + account_type: 'mt5', + balance: 10, + currency: 'USD', + display_currency_code: 'USD', + gradient_class: 'wallet-card__usd-bg', + icon: 'Icon', + is_demo: false, + loginid: '2', + shortcode: 'svg', + type: 'fiat', + active_wallet_icon: 'IcCurrencyUsd', + }, + }, + + wallet_accounts: { + CRW1000: { + account_type: 'wallet', + balance: 10000, + currency: 'USD', + display_currency_code: 'USD', + gradient_class: 'wallet-card__usd-bg', + icon: 'Icon', + is_demo: false, + loginid: '3', + shortcode: 'svg', + type: 'fiat', + active_wallet_icon: 'IcCurrencyUsd', + }, + }, + }, + transfer_hint: 'Transfer hint', + wallet_name: 'USD Wallet', + }; + }); + + it('Should render proper titles of transfer accounts', () => { + render(); + + expect(screen.getByText('Trading accounts linked with USD Wallet')).toBeInTheDocument(); + expect(screen.getByText('Wallets')).toBeInTheDocument(); + }); + + it('Should render proper amount of transfer accounts', () => { + render(); + + expect(screen.getAllByText('WalletTransferTile')).toHaveLength(3); + }); + + it('Should render transfer hint for Wallets account list', () => { + mocked_props.transfer_accounts = { ...mocked_props.transfer_accounts, trading_accounts: {} }; + render(); + + expect(screen.getByText('Transfer hint')).toBeInTheDocument(); + }); +}); diff --git a/packages/appstore/src/components/wallet-transfer/transfer-account-selector/__tests__/transfer-account-selector.spec.tsx b/packages/appstore/src/components/wallet-transfer/transfer-account-selector/__tests__/transfer-account-selector.spec.tsx new file mode 100644 index 000000000000..8784109c5814 --- /dev/null +++ b/packages/appstore/src/components/wallet-transfer/transfer-account-selector/__tests__/transfer-account-selector.spec.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import TransferAccountSelector from '../transfer-account-selector'; +import userEvent from '@testing-library/user-event'; +import { render, screen } from '@testing-library/react'; + +jest.mock('../transfer-account-list', () => jest.fn(() =>
TransferAccountList
)); +jest.mock('../../wallet-transfer-tile', () => jest.fn(() =>
WalletTransferTile
)); + +describe('TransferAccountSelector', () => { + let modal_root_el: HTMLDivElement, mocked_props: React.ComponentProps; + + beforeAll(() => { + modal_root_el = document.createElement('div'); + modal_root_el.setAttribute('id', 'modal_root'); + document.body.appendChild(modal_root_el); + }); + + beforeEach(() => { + mocked_props = { + is_mobile: false, + is_wallet_name_visible: false, + label: 'Transfer from', + onSelectAccount: jest.fn(), + placeholder: 'Placeholder', + portal_id: 'modal_root', + setIsWalletNameVisible: jest.fn(), + transfer_accounts: { + trading_accounts: {}, + wallet_accounts: {}, + }, + transfer_hint: 'Transfer hint', + value: undefined, + wallet_name: 'USD Wallet', + }; + }); + + it('Should render placeholder, if there is no selected account', () => { + render(); + + expect(screen.getByText('Transfer from')).toBeInTheDocument(); + expect(screen.getByText('Placeholder')).toBeInTheDocument(); + expect(screen.getByTestId('dt_chevron_icon')).toBeInTheDocument(); + }); + + it('Should render WalletTransferTile if the account was selected', () => { + mocked_props.value = { + active_wallet_icon: 'Icon', + display_currency_code: 'USD', + account_type: 'wallet', + balance: 100, + currency: 'USD', + gradient_class: 'wallet-card__usd-bg', + is_demo: false, + loginid: '12345678', + shortcode: 'svg', + type: 'fiat', + icon: 'Wallet Icon', + }; + render(); + + expect(screen.getByText('Transfer from')).toBeInTheDocument(); + expect(screen.getByText('WalletTransferTile')).toBeInTheDocument(); + expect(screen.getByTestId('dt_chevron_icon')).toBeInTheDocument(); + }); + + it('Should render account selector transfer tile with default values default', () => { + render(); + + expect(screen.getByText('Transfer from')).toBeInTheDocument(); + expect(screen.getByText('Placeholder')).toBeInTheDocument(); + expect(screen.getByTestId('dt_chevron_icon')).toBeInTheDocument(); + }); + + it('Should render TransferAccountList when the user is clicking on Transfer selector', () => { + render(); + + const el_transfer_tile = screen.getByTestId('dt_transfer_account_selector'); + userEvent.click(el_transfer_tile); + + expect(screen.getByText('TransferAccountList')).toBeInTheDocument(); + }); + + it('Should render proper label', () => { + render(); + + expect(screen.getByText('Transfer from')).toBeInTheDocument(); + }); +}); diff --git a/packages/appstore/src/components/wallet-transfer/transfer-account-selector/index.ts b/packages/appstore/src/components/wallet-transfer/transfer-account-selector/index.ts new file mode 100644 index 000000000000..50a91c417029 --- /dev/null +++ b/packages/appstore/src/components/wallet-transfer/transfer-account-selector/index.ts @@ -0,0 +1,3 @@ +import TransferAccountSelector from './transfer-account-selector'; + +export default TransferAccountSelector; diff --git a/packages/appstore/src/components/wallet-transfer/transfer-account-selector/transfer-account-list.tsx b/packages/appstore/src/components/wallet-transfer/transfer-account-selector/transfer-account-list.tsx new file mode 100644 index 000000000000..e7239d33d9db --- /dev/null +++ b/packages/appstore/src/components/wallet-transfer/transfer-account-selector/transfer-account-list.tsx @@ -0,0 +1,102 @@ +import React from 'react'; +import classNames from 'classnames'; +import { Text } from '@deriv/components'; +import { Localize } from '@deriv/translations'; +import WalletTransferTile from '../wallet-transfer-tile'; +import type { TTransferAccount } from 'Types'; + +type TTransferAccountList = { + is_mobile?: boolean; + onSelectAccount?: (account: TTransferAccount) => void; + selected_account?: TTransferAccount; + setIsListModalOpen: (value: boolean) => void; + setSelectedAccount: React.Dispatch>; + transfer_accounts: Record<'trading_accounts' | 'wallet_accounts', Record>; + transfer_hint?: string | JSX.Element; + wallet_name?: string; +}; + +const TitleLine = () =>
; + +const TransferAccountList = ({ + is_mobile, + onSelectAccount, + selected_account, + setIsListModalOpen, + setSelectedAccount, + transfer_accounts, + transfer_hint, + wallet_name, +}: TTransferAccountList) => { + const is_single_list = React.useMemo( + () => + Object.keys(transfer_accounts).filter( + key => Object.keys(transfer_accounts[key as 'trading_accounts' | 'wallet_accounts']).length > 0 + ).length === 1, + [transfer_accounts] + ); + + return ( +
+ {Object.keys(transfer_accounts).map((key, idx) => { + if (Object.values(transfer_accounts[key as 'trading_accounts' | 'wallet_accounts']).length === 0) + return null; + + return ( + +
+
+ + {key === 'trading_accounts' ? ( + + ) : ( + + )} + + +
+
+ {Object.values(transfer_accounts[key as 'trading_accounts' | 'wallet_accounts']).map( + account => ( + { + setSelectedAccount(account); + if (account) onSelectAccount?.(account); + setIsListModalOpen(false); + }} + /> + ) + )} +
+
+ {transfer_hint && ( + + {transfer_hint} + + )} +
+ ); + })} +
+ ); +}; +export default React.memo(TransferAccountList); diff --git a/packages/appstore/src/components/wallet-transfer/transfer-account-selector/transfer-account-selector.scss b/packages/appstore/src/components/wallet-transfer/transfer-account-selector/transfer-account-selector.scss new file mode 100644 index 000000000000..acbaf273e617 --- /dev/null +++ b/packages/appstore/src/components/wallet-transfer/transfer-account-selector/transfer-account-selector.scss @@ -0,0 +1,173 @@ +.transfer-account-selector { + display: flex; + align-items: center; + width: 100%; + background-color: var(--general-main-1); + padding: 0.8rem; + cursor: pointer; + + &__value { + padding: 0; + } + + &__chevron-icon { + display: flex; + margin-left: 1rem; + + @include mobile { + margin-left: auto; + } + } + + &__heading { + display: flex; + margin-bottom: 0.4rem; + } + + &__heading-with-chevron { + display: flex; + } + + &__content { + display: flex; + flex-direction: column; + align-items: flex-start; + margin-right: auto; + width: 100%; + + @include mobile { + align-items: unset; + } + } + + &__list { + border-bottom: 4px solid $color-grey-2; + + &__container { + @include mobile { + padding: 0 1.6rem 1.6rem; + } + + .transfer-hint { + margin: 1.6rem 0; + padding: 0 0.8rem; + + @include mobile { + margin: 0.8rem 0; + padding: 0; + } + } + } + + &--is-last, + &--is-single, + &--is-mobile { + border-bottom: none; + } + + &--is-last:is(&--is-mobile) { + margin-bottom: 0.8rem; + } + + &-items { + margin-bottom: 1.6rem; + padding: 0 0.8rem; + + @include mobile { + margin-bottom: 0; + padding: 0; + } + } + + &-header { + display: flex; + padding: 1.6rem 2.4rem 0.8rem; + + @include mobile { + padding: 0.8rem 0.8rem 0.4rem; + + &__title-line { + flex-grow: 1; + border-bottom: 1px solid $color-grey-2; + margin: 0 0 0.65rem 1rem; + } + } + } + + &-tile { + padding: 1rem 2rem; + flex-direction: row; + align-items: center; + cursor: pointer; + + @include mobile { + border-radius: unset; + padding: 0.8rem; + } + + .wallet-transfer-tile__icon { + margin-right: 1.6rem; + + @include mobile { + margin-right: 0.8rem; + } + } + } + } +} + +// Overwrite modal style +.dc-modal__container_transfer-account-selector__modal-header { + max-width: 40rem; + max-height: 52.8rem !important; + + @include mobile { + max-width: unset; + max-height: unset !important; + } +} + +.dc-modal-header--transfer-account-selector__modal-header { + border-bottom: 2px solid $color-grey-2; +} + +#mobile_list_modal_root { + position: absolute; + inset: 0; + z-index: 2; + display: none; + opacity: 0; + + &:not(:empty) { + display: flex; + opacity: 1; + } + + .dc-modal { + width: 100vw; + + &__container { + max-width: unset !important; + border-radius: unset; + box-shadow: unset; + } + + &-header { + border: none; + padding: 1.6rem 2.4rem 0rem; + + &__title { + font-size: var(--text-size-xxs); + padding: 0; + margin-bottom: 0; + } + + &__close { + padding: 0; + margin: 0; + height: auto; + width: auto; + } + } + } +} diff --git a/packages/appstore/src/components/wallet-transfer/transfer-account-selector/transfer-account-selector.tsx b/packages/appstore/src/components/wallet-transfer/transfer-account-selector/transfer-account-selector.tsx new file mode 100644 index 000000000000..b6bed30949af --- /dev/null +++ b/packages/appstore/src/components/wallet-transfer/transfer-account-selector/transfer-account-selector.tsx @@ -0,0 +1,160 @@ +import React from 'react'; +import { Div100vhContainer, Icon, Modal, Text, ThemedScrollbars } from '@deriv/components'; +import TransferAccountList from './transfer-account-list'; +import WalletTransferTile from '../wallet-transfer-tile'; +import { WalletJurisdictionBadge } from 'Components/wallet-jurisdiction-badge'; +import type { TTransferAccount } from 'Types'; +import './transfer-account-selector.scss'; + +type TTransferAccountSelectorProps = { + contentScrollHandler?: React.UIEventHandler; + is_mobile?: boolean; + is_wallet_name_visible?: boolean; + label?: string; + onSelectAccount?: (account: TTransferAccount) => void; + placeholder?: string; + portal_id?: string; + setIsWalletNameVisible?: (value: boolean) => void; + transfer_accounts: Record<'trading_accounts' | 'wallet_accounts', Record>; + transfer_hint?: string | JSX.Element; + value?: TTransferAccount; + wallet_name?: string; +}; + +type TAccountSelectorTransferTileProps = { + is_mobile?: boolean; + label?: string; + selected_account?: TTransferAccount; + placeholder?: string; +}; + +const ChevronIcon = () => { + return ( +
+ +
+ ); +}; + +const AccountSelectorTransferTile = ({ + is_mobile, + label, + placeholder, + selected_account, +}: TAccountSelectorTransferTileProps) => { + return ( + +
+
+
+ {label} +
+ + {is_mobile && } +
+ + {selected_account ? ( + + ) : ( + + {placeholder} + + )} +
+ + {!is_mobile && ( + + + + + )} +
+ ); +}; + +const TransferAccountSelector = ({ + contentScrollHandler, + is_mobile, + is_wallet_name_visible, + label, + onSelectAccount, + placeholder, + portal_id, + setIsWalletNameVisible, + transfer_accounts = { trading_accounts: {}, wallet_accounts: {} }, + transfer_hint, + value, + wallet_name, +}: TTransferAccountSelectorProps) => { + const [is_list_modal_open, setIsListModalOpen] = React.useState(false); + const [selected_account, setSelectedAccount] = React.useState(value); + + React.useEffect(() => { + setSelectedAccount(value); + }, [value]); + + const openAccountsList = () => { + setIsListModalOpen(true); + }; + + const getHeightOffset = React.useCallback(() => { + const header_height = '16.2rem'; + const collapsed_header_height = '12.2rem'; + return is_wallet_name_visible ? header_height : collapsed_header_height; + }, [is_wallet_name_visible]); + + return ( +
+ + +
+ + setIsWalletNameVisible?.(true)} + portalId={portal_id} + transition_timeout={is_mobile ? { enter: 250, exit: 0 } : 250} + title={label} + toggleModal={() => setIsListModalOpen(old => !old)} + > + + + + + + +
+ ); +}; + +export default TransferAccountSelector; diff --git a/packages/appstore/src/components/wallet-transfer/wallet-transfer-tile/__tests__/wallet-transfer-tile.spec.tsx b/packages/appstore/src/components/wallet-transfer/wallet-transfer-tile/__tests__/wallet-transfer-tile.spec.tsx new file mode 100644 index 000000000000..c7719d5f6b7d --- /dev/null +++ b/packages/appstore/src/components/wallet-transfer/wallet-transfer-tile/__tests__/wallet-transfer-tile.spec.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import WalletTransferTile from '../wallet-transfer-tile'; +import userEvent from '@testing-library/user-event'; +import { render, screen } from '@testing-library/react'; + +jest.mock('@deriv/components', () => ({ + ...jest.requireActual('@deriv/components'), + AppLinkedWithWalletIcon: jest.fn(() =>
AppLinkedWithWalletIcon
), + WalletIcon: jest.fn(() =>
WalletIcon
), +})); + +describe('WalletTransferTile', () => { + let mocked_props: Required>; + + beforeEach(() => { + mocked_props = { + account: { + account_type: 'trading', + balance: 100, + currency: 'USD', + display_currency_code: 'USD', + gradient_class: 'wallet-card__usd-bg', + icon: 'Icon', + is_demo: false, + shortcode: 'svg', + loginid: '12345678', + type: 'fiat', + active_wallet_icon: 'Wallet Icon', + }, + className: 'classname', + has_hover: false, + icon_size: 'small', + is_active: false, + is_list_item: false, + is_mobile: false, + onClick: jest.fn(), + }; + }); + + it('Should render merged icon (App with Wallet)', () => { + render(); + + expect(screen.getByText('AppLinkedWithWalletIcon')).toBeInTheDocument(); + }); + + it('Should render single wallet icon, if there is wallet account type', () => { + mocked_props.account = { ...mocked_props.account, account_type: 'wallet' }; + render(); + + expect(screen.getByText('WalletIcon')).toBeInTheDocument(); + }); + + it('Should render jurisdiction in mobile view', () => { + mocked_props.is_list_item = false; + mocked_props.is_mobile = true; + render(); + + expect(screen.getByText('SVG')).toBeInTheDocument(); + }); + + it('Should render jurisdiction in desktop view', () => { + mocked_props.is_list_item = true; + render(); + + expect(screen.getByText('SVG')).toBeInTheDocument(); + }); + + it('Should render proper account label', () => { + render(); + + expect(screen.getByText('Deriv Apps')).toBeInTheDocument(); + }); + + it('Should render proper account balance', () => { + render(); + + expect(screen.getByText('Balance: 100.00 USD')).toBeInTheDocument(); + }); + + it('Should trigger onClick callback when the user is clicking on Wallet tile', () => { + render(); + + const el_wallet_tile = screen.getByTestId('dt_wallet_transfer_tile'); + userEvent.click(el_wallet_tile); + + expect(mocked_props.onClick).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/appstore/src/components/wallet-transfer/wallet-transfer-tile/index.ts b/packages/appstore/src/components/wallet-transfer/wallet-transfer-tile/index.ts new file mode 100644 index 000000000000..e5851511bba4 --- /dev/null +++ b/packages/appstore/src/components/wallet-transfer/wallet-transfer-tile/index.ts @@ -0,0 +1,3 @@ +import WalletTransferTile from './wallet-transfer-tile'; + +export default WalletTransferTile; diff --git a/packages/appstore/src/components/wallet-transfer/wallet-transfer-tile/wallet-transfer-tile.scss b/packages/appstore/src/components/wallet-transfer/wallet-transfer-tile/wallet-transfer-tile.scss new file mode 100644 index 000000000000..606c6b4f0890 --- /dev/null +++ b/packages/appstore/src/components/wallet-transfer/wallet-transfer-tile/wallet-transfer-tile.scss @@ -0,0 +1,48 @@ +.wallet-transfer-tile { + display: flex; + align-items: center; + justify-content: flex-start; + background-color: var(--general-main-1); + border-radius: $BORDER_RADIUS; + flex-grow: 1; + + @include mobile { + flex-direction: column; + align-items: flex-start; + + &__icon-with-badge { + display: flex; + align-items: end; + margin-bottom: 0.4rem; + } + } + + &--hover { + &:hover { + background-color: var(--general-hover); + } + } + + &--active { + background-color: var(--state-active); + } + + &--list-item-background { + background-color: var(--general-main-2); + } + + &__icon { + margin-right: 0.8rem; + min-width: 4rem; + + @include mobile { + margin-right: 0.4rem; + } + } + + &__content { + display: flex; + flex-direction: column; + flex-grow: 1; + } +} diff --git a/packages/appstore/src/components/wallet-transfer/wallet-transfer-tile/wallet-transfer-tile.tsx b/packages/appstore/src/components/wallet-transfer/wallet-transfer-tile/wallet-transfer-tile.tsx new file mode 100644 index 000000000000..510eebfbc27c --- /dev/null +++ b/packages/appstore/src/components/wallet-transfer/wallet-transfer-tile/wallet-transfer-tile.tsx @@ -0,0 +1,123 @@ +import React from 'react'; +import classNames from 'classnames'; +import { Text, AppLinkedWithWalletIcon, WalletIcon } from '@deriv/components'; +import { formatMoney } from '@deriv/shared'; +import { Localize } from '@deriv/translations'; +import { getAccountName } from 'Constants/utils'; +import { WalletJurisdictionBadge } from 'Components/wallet-jurisdiction-badge'; +import type { TTransferAccount } from 'Types'; +import './wallet-transfer-tile.scss'; + +type TIconSize = + | React.ComponentProps['size'] + | React.ComponentProps['size']; + +type TWalletTileProps = { + account?: TTransferAccount; + className?: string; + has_hover?: boolean; + icon_size?: TIconSize; + is_active?: boolean; + is_list_item?: boolean; + is_mobile?: boolean; + onClick?: () => void; +}; + +const IconComponent = ({ account, icon_size }: TWalletTileProps) => { + if (account?.account_type === 'wallet') { + return account?.icon ? ( + ['size']} + type={account?.type} + /> + ) : null; + } + + return account?.icon && account?.active_wallet_icon ? ( + ['size']} + type={account?.type} + wallet_icon={account?.active_wallet_icon} + /> + ) : null; +}; + +const Balance = ({ account, is_list_item, is_mobile }: TWalletTileProps) => { + if (account?.balance !== undefined) { + let size; + if (is_list_item) size = is_mobile ? 'xxxs' : 'xxs'; + else size = is_mobile ? 'xxxxs' : 'xxxs'; + + return ( + + + + ); + } + + return null; +}; + +const Label = ({ account, is_list_item, is_mobile }: TWalletTileProps) => { + let size; + if (is_list_item) size = is_mobile ? 'xxs' : 'xs'; + else size = is_mobile ? 'xxxxs' : 'xxxs'; + + return ( + + {getAccountName({ ...account })} + + ); +}; + +const WalletTransferTile = ({ + account, + className, + has_hover, + icon_size = 'small', + is_active, + is_list_item, + is_mobile, + onClick, +}: TWalletTileProps) => { + return ( +
onClick?.()} + > +
+
+ +
+ + {!is_list_item && is_mobile && ( + + )} +
+ +
+
+ + {is_list_item && } +
+ ); +}; + +export default React.memo(WalletTransferTile); diff --git a/packages/appstore/src/components/wallet-transfer/wallet-transfer.scss b/packages/appstore/src/components/wallet-transfer/wallet-transfer.scss new file mode 100644 index 000000000000..120976f2dc40 --- /dev/null +++ b/packages/appstore/src/components/wallet-transfer/wallet-transfer.scss @@ -0,0 +1,52 @@ +.wallet-transfer { + display: flex; + flex-direction: column; + max-width: 65rem; + width: 100%; + margin: 9.6rem auto 4.8rem; + + @include mobile { + margin: 4.8rem auto; + } + + &__tiles-container { + display: flex; + flex-direction: column; + } + + &__divider { + border: 0.5px solid var(--border-normal); + margin: 0.8rem 0; + } + + &__tile { + display: flex; + border: 1px solid var(--border-normal); + border-radius: $BORDER_RADIUS; + + .amount-input-wrapper { + border-radius: $BORDER_RADIUS; + flex-basis: 50%; + justify-content: space-between; + + @include mobile { + flex-basis: 60%; + } + } + + .transfer-account-selector { + border-radius: $BORDER_RADIUS; + flex-basis: 50%; + + @include mobile { + flex-basis: 40%; + } + } + } + + &__transfer-button { + margin-top: 4.8rem; + display: flex; + justify-content: flex-end; + } +} diff --git a/packages/appstore/src/components/wallet-transfer/wallet-transfer.tsx b/packages/appstore/src/components/wallet-transfer/wallet-transfer.tsx new file mode 100644 index 000000000000..f77d89aca091 --- /dev/null +++ b/packages/appstore/src/components/wallet-transfer/wallet-transfer.tsx @@ -0,0 +1,280 @@ +import React, { useEffect } from 'react'; +import classNames from 'classnames'; +import { Field, FieldProps, Formik, Form, FormikHelpers } from 'formik'; +import { AmountInput, Button, Loading, MessageList } from '@deriv/components'; +import { useWalletTransfer, useCurrencyConfig } from '@deriv/hooks'; +import { validNumber } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; +import { localize, Localize } from '@deriv/translations'; +import TransferAccountSelector from './transfer-account-selector'; +import { getAccountName } from 'Constants/utils'; +import type { TMessageItem } from 'Types'; +import './wallet-transfer.scss'; + +type TWalletTransferProps = { + contentScrollHandler: React.UIEventHandler; + is_wallet_name_visible: boolean; + setIsWalletNameVisible: (value: boolean) => void; +}; + +const Divider = () =>
; + +const initial_demo_balance = 10000.0; + +const ERROR_CODES = { + is_demo: { + between_min_max: 'BetweenMinMax', + insufficient_fund: 'InsufficientFund', + }, +}; + +const WalletTransfer = observer(({ is_wallet_name_visible, setIsWalletNameVisible }: TWalletTransferProps) => { + const { client, ui, traders_hub } = useStore(); + const { setWalletModalActiveTab } = traders_hub; + const { is_switching } = client; + const { is_mobile } = ui; + + const { getConfig } = useCurrencyConfig(); + + const { + active_wallet, + is_accounts_loading, + from_account, + to_account, + to_account_list, + transfer_accounts, + setFromAccount, + setToAccount, + } = useWalletTransfer(); + + useEffect(() => { + if (!from_account?.loginid) { + setFromAccount(active_wallet); + } + }, [active_wallet, from_account, setFromAccount]); + + const portal_id = is_mobile ? 'mobile_list_modal_root' : 'modal_root'; + + const is_amount_to_input_disabled = !to_account; + + const active_wallet_name = getAccountName({ ...active_wallet }); + + const transfer_to_hint = React.useMemo(() => { + return to_account?.loginid === active_wallet?.loginid ? ( + + ) : ( + '' + ); + }, [active_wallet?.loginid, active_wallet_name, from_account, to_account?.loginid]); + + const [message_list, setMessageList] = React.useState([]); + + const clearErrorMessages = React.useCallback( + () => setMessageList(list => list.filter(el => el.type !== 'error')), + [] + ); + + const appendMessage = (error_code: string, message: TMessageItem) => { + setMessageList(list => { + if (list.some(el => el.key === error_code)) return list; + return [...list, message]; + }); + }; + + const validateAmount = (amount: number) => { + clearErrorMessages(); + + if (!amount || is_amount_to_input_disabled || !active_wallet?.is_demo) return; + + const { is_ok, message } = validNumber(amount.toString(), { + type: 'float', + decimals: getConfig(from_account?.currency ?? '')?.fractional_digits, + min: 1, + max: from_account?.balance, + }); + + const should_reset_balance = + active_wallet?.balance !== undefined && + amount > active_wallet?.balance && + active_wallet?.balance < initial_demo_balance; + + if (from_account?.loginid === active_wallet.loginid && should_reset_balance) { + appendMessage(ERROR_CODES.is_demo.insufficient_fund, { + variant: 'with-action-button', + key: ERROR_CODES.is_demo.insufficient_fund, + button_label: localize('Reset balance'), + onClickHandler: () => setWalletModalActiveTab('Deposit'), + message: localize( + 'You have insufficient fund in the selected wallet, please reset your virtual balance' + ), + type: 'error', + }); + } else if (!is_ok) { + //else if not wallet loginid and not is_ok message + appendMessage(ERROR_CODES.is_demo.between_min_max, { + variant: 'base', + key: ERROR_CODES.is_demo.between_min_max, + message: `${message} ${from_account?.display_currency_code}`, + type: 'error', + }); + } + }; + + const onSelectFromAccount = React.useCallback( + ( + account: typeof from_account, + resetForm: FormikHelpers<{ + to_amount: number; + from_amount: number; + }>['resetForm'] + ) => { + if (account?.loginid === from_account?.loginid) return; + setFromAccount(account); + if (account?.loginid === active_wallet?.loginid) { + setToAccount(undefined); + } else { + setToAccount(active_wallet); + } + clearErrorMessages(); + resetForm(); + }, + [active_wallet, clearErrorMessages, from_account?.loginid, setFromAccount, setToAccount] + ); + + const onSelectToAccount = React.useCallback( + ( + account: typeof to_account, + resetForm: FormikHelpers<{ + to_amount: number; + from_amount: number; + }>['resetForm'] + ) => { + if (account?.loginid === to_account?.loginid) return; + setToAccount(account); + clearErrorMessages(); + resetForm(); + }, + [clearErrorMessages, setToAccount, to_account?.loginid] + ); + + if (is_accounts_loading || is_switching) { + return ; + } + + return ( +
+ undefined} + validateOnBlur={false} + > + {({ setValues, values, resetForm }) => ( +
+
+
0, + })} + > + + {({ field }: FieldProps) => ( + el.type === 'error')} + initial_value={field.value} + label={localize('Amount you send')} + onChange={(value: number) => { + setValues({ + from_amount: value, + to_amount: is_amount_to_input_disabled ? 0 : value, + }); + }} + /> + )} + + + onSelectFromAccount(account, resetForm)} + placeholder={localize('Select a trading account or a Wallet')} + portal_id={portal_id} + setIsWalletNameVisible={setIsWalletNameVisible} + transfer_accounts={transfer_accounts} + wallet_name={active_wallet_name} + value={from_account} + /> +
+ +
+ + {({ field }: FieldProps) => ( + el.type === 'error')} + initial_value={field.value} + label={localize('Amount you receive')} + onChange={(value: number) => { + setValues({ from_amount: value, to_amount: value }); + }} + /> + )} + + + onSelectToAccount(account, resetForm)} + placeholder={!to_account ? localize('Select a trading account or a Wallet') : ''} + portal_id={portal_id} + setIsWalletNameVisible={setIsWalletNameVisible} + transfer_accounts={to_account_list} + transfer_hint={transfer_to_hint} + wallet_name={active_wallet_name} + value={to_account} + /> +
+
+
+ +
+
+ )} +
+
+ ); +}); + +export default WalletTransfer; diff --git a/packages/appstore/src/components/wallet-withdrawal/__tests__/wallet-withdrawal.test.tsx b/packages/appstore/src/components/wallet-withdrawal/__tests__/wallet-withdrawal.test.tsx new file mode 100644 index 000000000000..a6e9ec4f4b46 --- /dev/null +++ b/packages/appstore/src/components/wallet-withdrawal/__tests__/wallet-withdrawal.test.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import WalletWithdrawal from '../wallet-withdrawal'; +import { mockStore } from '@deriv/stores'; +import CashierProviders from '@deriv/cashier/src/cashier-providers'; +import { useRequest } from '@deriv/api'; + +jest.mock('@deriv/api', () => ({ + ...jest.requireActual('@deriv/api'), + useRequest: jest.fn(), +})); + +// @ts-expect-error ignore this until find a way to make arguments as partial +const mockUseRequest = useRequest as jest.MockedFunction>; + +const mock_store = mockStore({ + client: { + email: 'john@company.com', + }, + modules: { cashier: { transaction_history: { onMount: jest.fn() } } }, +}); + +describe('WalletWithdrawal', () => { + test('should render the component', () => { + // @ts-expect-error ignore this until find a way to make arguments as partial + mockUseRequest.mockReturnValue({}); + + render(, { + wrapper: ({ children }) => {children}, + }); + + expect(screen.queryByTestId('dt_empty_state_title')).toBeInTheDocument(); + expect(screen.queryByTestId('dt_empty_state_description')).toBeInTheDocument(); + expect(screen.queryByTestId('dt_empty_state_action')).toHaveTextContent('Send email'); + }); +}); diff --git a/packages/appstore/src/components/wallet-withdrawal/index.ts b/packages/appstore/src/components/wallet-withdrawal/index.ts new file mode 100644 index 000000000000..bab74c8c7b9b --- /dev/null +++ b/packages/appstore/src/components/wallet-withdrawal/index.ts @@ -0,0 +1,3 @@ +import WalletWithdrawal from './wallet-withdrawal'; + +export default WalletWithdrawal; diff --git a/packages/appstore/src/components/wallet-withdrawal/wallet-withdrawal.tsx b/packages/appstore/src/components/wallet-withdrawal/wallet-withdrawal.tsx new file mode 100644 index 000000000000..0344dfd24c92 --- /dev/null +++ b/packages/appstore/src/components/wallet-withdrawal/wallet-withdrawal.tsx @@ -0,0 +1,8 @@ +import React from 'react'; +import WithdrawalVerificationEmail from '@deriv/cashier/src/pages/withdrawal/withdrawal-verification-email'; + +const WalletWithdrawal = () => { + return ; +}; + +export default WalletWithdrawal; diff --git a/packages/appstore/src/components/wallets-banner/__tests__/wallets-banner.spec.tsx b/packages/appstore/src/components/wallets-banner/__tests__/wallets-banner.spec.tsx new file mode 100644 index 000000000000..ccc8eb85e9ee --- /dev/null +++ b/packages/appstore/src/components/wallets-banner/__tests__/wallets-banner.spec.tsx @@ -0,0 +1,222 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import '@testing-library/jest-dom'; +import { mockStore, StoreProvider } from '@deriv/stores'; +import { TImageTestID } from 'Assets/svgs/wallets/image-types'; +import WalletsBannerUpgrade from '../wallets-banner-upgrade'; +import WalletsBannerUpgrading from '../wallets-banner-upgrading'; +import WalletsBannerReady from '../wallets-banner-ready'; + +describe('', () => { + const mockRootStore = mockStore({ + traders_hub: { + toggleWalletsUpgrade: true, + }, + }); + + describe('Should render properly with right banner if status is eligible: ', () => { + const desktop: TImageTestID = 'dt_upgrade_desktop'; + const mobile: TImageTestID = 'dt_upgrade_mobile'; + + it('Should render upgrade now button', async () => { + render(, { + wrapper: ({ children }) => {children}, + }); + const btn = screen.getByRole('button', { name: /Upgrade now/i }); + expect(btn).toBeInTheDocument(); + }); + + it('Should render image properly for desktop', () => { + mockRootStore.ui.is_mobile = false; + render(, { + wrapper: ({ children }) => {children}, + }); + const desktop_image = screen.queryByTestId(desktop); + const mobile_image = screen.queryByTestId(mobile); + + expect(desktop_image).toBeInTheDocument(); + expect(mobile_image).not.toBeInTheDocument(); + }); + + it('Should render image properly for mobile', () => { + mockRootStore.ui.is_mobile = true; + render(, { + wrapper: ({ children }) => {children}, + }); + const desktop_image = screen.queryByTestId(desktop); + const mobile_image = screen.queryByTestId(mobile); + + expect(mobile_image).toBeInTheDocument(); + expect(desktop_image).not.toBeInTheDocument(); + }); + }); + + describe('Should render properly with right banner if status is in_progress: ', () => { + const desktop: TImageTestID = 'dt_upgrading_desktop'; + const mobile: TImageTestID = 'dt_upgrading_mobile'; + const desktop_eu: TImageTestID = 'dt_upgrading_desktop_eu'; + const mobile_eu: TImageTestID = 'dt_upgrading_mobile_eu'; + + it('Should render right title', () => { + render(, { + wrapper: ({ children }) => {children}, + }); + const title = screen.queryByText(/We're setting up your Wallets/i); + + expect(title).toBeInTheDocument(); + }); + + it('Should render loading dots', () => { + render(, { + wrapper: ({ children }) => {children}, + }); + const loading_dots = screen.queryByTestId('dt_wallets_loading_dots'); + + expect(loading_dots).toBeInTheDocument(); + }); + + it('Should render image properly for desktop for Non-EU', () => { + mockRootStore.ui.is_mobile = false; + render(, { + wrapper: ({ children }) => {children}, + }); + const desktop_image = screen.queryByTestId(desktop); + const mobile_image = screen.queryByTestId(mobile); + + expect(desktop_image).toBeInTheDocument(); + expect(mobile_image).not.toBeInTheDocument(); + }); + + it('Should render image properly for mobile for Non-EU', () => { + mockRootStore.ui.is_mobile = true; + render(, { + wrapper: ({ children }) => {children}, + }); + const desktop_image = screen.queryByTestId(desktop); + const mobile_image = screen.queryByTestId(mobile); + + expect(mobile_image).toBeInTheDocument(); + expect(desktop_image).not.toBeInTheDocument(); + }); + + it('Should render image properly for desktop for EU', () => { + mockRootStore.ui.is_mobile = false; + render(, { + wrapper: ({ children }) => {children}, + }); + const desktop_image = screen.queryByTestId(desktop_eu); + const mobile_image = screen.queryByTestId(mobile_eu); + + expect(desktop_image).toBeInTheDocument(); + expect(mobile_image).not.toBeInTheDocument(); + }); + + it('Should render image properly for mobile for EU', () => { + mockRootStore.ui.is_mobile = true; + render(, { + wrapper: ({ children }) => {children}, + }); + const desktop_image = screen.queryByTestId(desktop_eu); + const mobile_image = screen.queryByTestId(mobile_eu); + + expect(mobile_image).toBeInTheDocument(); + expect(desktop_image).not.toBeInTheDocument(); + }); + }); + + describe('Should render properly with right banner if status is migrated: ', () => { + const desktop: TImageTestID = 'dt_ready_desktop'; + const mobile: TImageTestID = 'dt_ready_mobile'; + const desktop_eu: TImageTestID = 'dt_ready_desktop_eu'; + const mobile_eu: TImageTestID = 'dt_ready_mobile_eu'; + + it('Should render right title', () => { + render(, { + wrapper: ({ children }) => {children}, + }); + const title = screen.queryByText(/Your Wallets are ready/i); + + expect(title).toBeInTheDocument(); + }); + + it('Should render tick', () => { + render(, { + wrapper: ({ children }) => {children}, + }); + const tick = screen.queryByTestId('dt_wallets_ready_tick'); + + expect(tick).toBeInTheDocument(); + }); + + it('Should render right button', () => { + render(, { + wrapper: ({ children }) => {children}, + }); + const btn = screen.queryByText('Log out'); + + expect(btn).toBeInTheDocument(); + }); + + it('Should render image properly for desktop for Non-EU', () => { + mockRootStore.ui.is_mobile = false; + render(, { + wrapper: ({ children }) => {children}, + }); + const desktop_image = screen.queryByTestId(desktop); + const mobile_image = screen.queryByTestId(mobile); + + expect(desktop_image).toBeInTheDocument(); + expect(mobile_image).not.toBeInTheDocument(); + }); + + it('Should render image properly for mobile for Non-EU', () => { + mockRootStore.ui.is_mobile = true; + render(, { + wrapper: ({ children }) => {children}, + }); + const desktop_image = screen.queryByTestId(desktop); + const mobile_image = screen.queryByTestId(mobile); + + expect(mobile_image).toBeInTheDocument(); + expect(desktop_image).not.toBeInTheDocument(); + }); + + it('Should render image properly for desktop for EU', () => { + mockRootStore.ui.is_mobile = false; + render(, { + wrapper: ({ children }) => {children}, + }); + const desktop_image = screen.queryByTestId(desktop_eu); + const mobile_image = screen.queryByTestId(mobile_eu); + + expect(desktop_image).toBeInTheDocument(); + expect(mobile_image).not.toBeInTheDocument(); + }); + + it('Should render image properly for mobile for EU', () => { + mockRootStore.ui.is_mobile = true; + render(, { + wrapper: ({ children }) => {children}, + }); + const desktop_image = screen.queryByTestId(desktop_eu); + const mobile_image = screen.queryByTestId(mobile_eu); + + expect(mobile_image).toBeInTheDocument(); + expect(desktop_image).not.toBeInTheDocument(); + }); + + it('Should call logout function when click on button', async () => { + render(, { + wrapper: ({ children }) => {children}, + }); + + const btn = screen.getByText('Log out'); + + await userEvent.click(btn); + + expect(btn).toBeInTheDocument(); + expect(mockRootStore.client.logout).toBeCalledTimes(1); + }); + }); +}); diff --git a/packages/appstore/src/components/wallets-banner/index.ts b/packages/appstore/src/components/wallets-banner/index.ts new file mode 100644 index 000000000000..5309a3973964 --- /dev/null +++ b/packages/appstore/src/components/wallets-banner/index.ts @@ -0,0 +1,4 @@ +import WalletsBanner from './wallets-banner'; +import './wallets-banner.scss'; + +export default WalletsBanner; diff --git a/packages/appstore/src/components/wallets-banner/wallets-banner-ready.tsx b/packages/appstore/src/components/wallets-banner/wallets-banner-ready.tsx new file mode 100644 index 000000000000..0501b929c73c --- /dev/null +++ b/packages/appstore/src/components/wallets-banner/wallets-banner-ready.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import WalletsImage from 'Assets/svgs/wallets'; +import { Button, Icon, Text } from '@deriv/components'; +import { Localize, localize } from '@deriv/translations'; +import { TWalletsImagesListKey } from 'Assets/svgs/wallets/image-types'; +import { observer, useStore } from '@deriv/stores'; + +// just for now for testing purpose, in the future 'is_eu' value will be taken from the store +type TWalletsBannerReady = { + is_eu?: boolean; +}; + +const WalletsBannerReady = observer(({ is_eu }: TWalletsBannerReady) => { + const { + client: { logout }, + ui: { is_mobile }, + } = useStore(); + + const mobile_class = is_eu ? 'ready_mobile_eu' : 'ready_mobile'; + const desktop_class = is_eu ? 'ready_desktop_eu' : 'ready_desktop'; + + const image: TWalletsImagesListKey = is_mobile ? mobile_class : desktop_class; + + const title_size = is_mobile ? 'xs' : 'sm'; + const description_size = is_mobile ? 'xxxs' : 'xs'; + const tick_size = is_mobile ? 16 : 24; + + const onButtonClickHandler = async () => { + await logout(); + }; + + return ( +
+
+
+ +
+ ]} + /> + ]} + /> +
+ +
+ ); +}); + +export default WalletsBannerReady; diff --git a/packages/appstore/src/components/wallets-banner/wallets-banner-upgrade.tsx b/packages/appstore/src/components/wallets-banner/wallets-banner-upgrade.tsx new file mode 100644 index 000000000000..825891d1ce52 --- /dev/null +++ b/packages/appstore/src/components/wallets-banner/wallets-banner-upgrade.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import WalletsImage from 'Assets/svgs/wallets'; +import { Button, Text } from '@deriv/components'; +import { Localize, localize } from '@deriv/translations'; +import { TWalletsImagesListKey } from 'Assets/svgs/wallets/image-types'; +import { observer, useStore } from '@deriv/stores'; + +const WalletsBannerUpgrade = observer(() => { + const { traders_hub, ui } = useStore(); + const { toggleWalletsUpgrade } = traders_hub; + const { is_mobile } = ui; + + const image: TWalletsImagesListKey = is_mobile ? 'upgrade_mobile' : 'upgrade_desktop'; + const size: string = is_mobile ? 'xs' : 'm'; + + return ( +
+
+
+ , ]} + /> +
+
+ +
+ ); +}); + +export default WalletsBannerUpgrade; diff --git a/packages/appstore/src/components/wallets-banner/wallets-banner-upgrading.tsx b/packages/appstore/src/components/wallets-banner/wallets-banner-upgrading.tsx new file mode 100644 index 000000000000..0158acc3f4fe --- /dev/null +++ b/packages/appstore/src/components/wallets-banner/wallets-banner-upgrading.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import WalletsImage from 'Assets/svgs/wallets'; +import { Text } from '@deriv/components'; +import { Localize } from '@deriv/translations'; +import { TWalletsImagesListKey } from 'Assets/svgs/wallets/image-types'; +import { observer, useStore } from '@deriv/stores'; + +// just for now for testing purpose, in the future 'is_eu' value will be taken from the store +type TWalletsBannerUpgrading = { + is_eu?: boolean; +}; + +const WalletsBannerUpgrading = observer(({ is_eu }: TWalletsBannerUpgrading) => { + const { ui } = useStore(); + const { is_mobile } = ui; + + const mobile_class = is_eu ? 'upgrading_mobile_eu' : 'upgrading_mobile'; + const desktop_class = is_eu ? 'upgrading_desktop_eu' : 'upgrading_desktop'; + const image: TWalletsImagesListKey = is_mobile ? mobile_class : desktop_class; + + const title_size = is_mobile ? 'xs' : 'sm'; + const description_size = is_mobile ? 'xxxs' : 'xs'; + + return ( +
+
+
+ + + +
+ ]} + /> + ]} + /> +
+ +
+ ); +}); + +export default WalletsBannerUpgrading; diff --git a/packages/appstore/src/components/wallets-banner/wallets-banner.scss b/packages/appstore/src/components/wallets-banner/wallets-banner.scss new file mode 100644 index 000000000000..588446d13f80 --- /dev/null +++ b/packages/appstore/src/components/wallets-banner/wallets-banner.scss @@ -0,0 +1,188 @@ +.wallets-banner { + &__container { + position: relative; + padding-inline: 4.8rem; + margin-bottom: 2.4rem; + overflow: hidden; + + // TODO: remove this after the whole flow is finalized + background-color: var(--general-main-1); + + border: 1px solid var(--wallets-banner-border-color); + box-shadow: var(--wallet-box-shadow); + border-radius: $BORDER_RADIUS * 4; + + @include mobile { + padding: 1.4rem 1.6rem; + border-radius: $BORDER_RADIUS * 2; + } + } + + &__image { + position: absolute; + right: 0; + bottom: 0; + border-top-right-radius: $BORDER_RADIUS * 4; + border-bottom-right-radius: $BORDER_RADIUS * 4; + + @include mobile { + border-top-right-radius: $BORDER_RADIUS * 2; + border-bottom-right-radius: $BORDER_RADIUS * 2; + } + } + + &__upgrade-banner { + height: 18rem; + + @include mobile { + height: 10.8rem; + } + + &-description { + width: 45%; + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: baseline; + + @include mobile { + width: 70%; + } + } + + &-button { + margin-top: 0.8rem; + padding-inline: 6.7rem; + + @include mobile { + padding-inline: 1.6rem; + } + } + } + + &__upgrading-banner { + display: flex; + align-items: center; + height: 18.2rem; + + @include mobile { + height: 14.2rem; + } + + &-description { + width: 45%; + height: 80%; + display: flex; + flex-direction: column; + justify-content: space-around; + align-items: baseline; + + @include mobile { + width: 70%; + } + } + + &-loading { + span { + animation-name: loading; + animation-duration: 1.5s; + animation-iteration-count: infinite; + animation-timing-function: cubic-bezier(1, 0, 0, 1); + } + + span:nth-child(2) { + animation-delay: 0.5s; + } + + span:nth-child(3) { + animation-delay: 1s; + } + } + + &-dot { + height: 0.8rem; + width: 0.8rem; + background-color: var(--wallets-banner-dot-color); + border-radius: 50%; + display: inline-block; + margin-right: 0.6rem; + + @include mobile { + height: 0.5rem; + width: 0.5rem; + } + } + + &-image { + opacity: 50%; + height: 100%; + } + } + + &__ready-banner { + display: flex; + align-items: center; + height: 23rem; + background-color: var(--wallets-banner-ready-bg-color); + + @include mobile { + height: 19rem; + } + + &-tick { + display: flex; + justify-content: center; + align-items: center; + background-color: var(--wallets-banner-ready-tick-bg-color); + border-radius: 50%; + width: 4.8rem; + height: 4.8rem; + + @include mobile { + width: 3.2rem; + height: 3.2rem; + } + } + + &-description { + width: 45%; + height: 80%; + display: flex; + flex-direction: column; + justify-content: space-around; + align-items: baseline; + + @include mobile { + width: 70%; + } + } + + &-button { + margin-top: 0.8rem; + padding-inline: 8.6rem; + + @include mobile { + padding-inline: 5rem; + } + } + + &-image { + opacity: 90%; + height: 100%; + + @include mobile { + opacity: 100%; + } + } + } +} + +@keyframes loading { + 0% { + background-color: var(--wallets-banner-active-dot-color); + } + 100% { + opacity: var(--wallets-banner-dot-color); + } +} diff --git a/packages/appstore/src/components/wallets-banner/wallets-banner.tsx b/packages/appstore/src/components/wallets-banner/wallets-banner.tsx new file mode 100644 index 000000000000..62561f8c0a84 --- /dev/null +++ b/packages/appstore/src/components/wallets-banner/wallets-banner.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import WalletsBannerUpgrade from './wallets-banner-upgrade'; +import WalletsBannerUpgrading from './wallets-banner-upgrading'; +import WalletsBannerReady from './wallets-banner-ready'; +import { useWalletMigration } from '@deriv/hooks'; +import { observer, useStore } from '@deriv/stores'; + +const WalletsBanner = observer(() => { + const { is_eligible, is_failed, is_in_progress, is_migrated } = useWalletMigration(); + const { traders_hub } = useStore(); + const { is_eu_user } = traders_hub; + + if (is_migrated) return ; + + if (is_eligible || is_failed) return ; + + if (is_in_progress) return ; + + return null; +}); + +export default WalletsBanner; diff --git a/packages/appstore/src/constants/mock_wallet_migration_response.ts b/packages/appstore/src/constants/mock_wallet_migration_response.ts new file mode 100644 index 000000000000..b557ff1be44b --- /dev/null +++ b/packages/appstore/src/constants/mock_wallet_migration_response.ts @@ -0,0 +1,213 @@ +import { localize } from '@deriv/translations'; +import { getWalletCurrencyIcon } from './utils'; + +const getMockWalletMigrationResponse = () => { + return [ + { + title: localize('Non-EU USD accounts'), + wallets: [ + { + wallet_details: { + balance: 0, + currency: 'USD', + icon: getWalletCurrencyIcon('USD', false), + icon_type: 'fiat', + jurisdiction_title: 'SVG', + name: 'USD', + gradient_class: 'wallet-card__usd-bg', + }, + account_list: [ + { + balance: 1000, + currency: 'USD', + account_name: 'US Dollar', + icon: 'IcCurrencyUsd', + platform: 'deriv', + }, + { + balance: 100, + currency: 'USD', + account_name: 'MT5 Derived SVG', + icon: 'IcRebrandingMt5DerivedDashboard', + platform: 'mt5', + sub_account_type: 'Derived', + landing_company_name: 'SVG', + }, + { + balance: 123, + currency: 'USD', + account_name: 'MT5 Derived BVI', + icon: 'IcRebrandingMt5DerivedDashboard', + platform: 'mt5', + sub_account_type: 'Derived', + landing_company_name: 'BVI', + }, + { + balance: 20, + currency: 'USD', + account_name: 'MT5 Derived Vanuata', + icon: 'IcRebrandingMt5DerivedDashboard', + platform: 'mt5', + sub_account_type: 'Derived', + landing_company_name: 'Vanuatu', + }, + { + balance: 100, + currency: 'USD', + account_name: 'MT5 Financial SVG', + icon: 'IcRebrandingMt5FinancialDashboard', + platform: 'mt5', + sub_account_type: 'Financial', + landing_company_name: 'SVG', + }, + { + balance: 100, + currency: 'USD', + account_name: 'MT5 Financial BVI', + icon: 'IcRebrandingMt5FinancialDashboard', + platform: 'mt5', + sub_account_type: 'Financial', + landing_company_name: 'SVG', + }, + { + balance: 100, + currency: 'USD', + account_name: 'MT5 Financial Vanuatu', + icon: 'IcRebrandingMt5FinancialDashboard', + platform: 'mt5', + sub_account_type: 'Financial', + landing_company_name: 'SVG', + }, + { + balance: 100, + currency: 'USD', + account_name: 'MT5 Financial Labuan', + icon: 'IcRebrandingMt5FinancialDashboard', + platform: 'mt5', + sub_account_type: 'Financial', + landing_company_name: 'SVG', + }, + { + balance: 150, + currency: 'USD', + account_name: 'MT5 Swap-free', + icon: 'IcRebrandingMt5SwapFree', + platform: 'mt5', + sub_account_type: 'Swap-Free', + }, + { + balance: 100, + currency: 'USD', + account_name: 'Deriv X', + icon: 'IcRebrandingDerivx', + platform: 'derivx', + }, + { + balance: 100, + currency: 'USD', + account_name: 'Deriv EZ', + icon: 'IcDerivez', + platform: 'derivez', + }, + ], + }, + ], + }, + { + title: localize('EU-regulated USD accounts'), + wallets: [ + { + wallet_details: { + balance: 0, + currency: 'USD', + icon: getWalletCurrencyIcon('USD', false), + icon_type: 'fiat', + jurisdiction_title: 'MALTA', + name: 'USD', + gradient_class: 'wallet-card__usd-bg', + }, + account_list: [ + { + balance: 1000, + currency: 'USD', + account_name: 'US Dollar', + icon: 'IcCurrencyUsd', + platform: 'deriv', + }, + { + balance: 234, + currency: 'USD', + account_name: 'MT5 CFDs', + icon: 'IcRebrandingMt5Cfds', + platform: 'mt5', + }, + ], + }, + ], + }, + { + title: localize('Cryptocurrency accounts'), + wallets: [ + { + wallet_details: { + balance: 0, + currency: 'BTC', + icon: getWalletCurrencyIcon('BTC', false), + icon_type: 'crypto', + jurisdiction_title: 'SVG', + name: 'Bitcoin', + gradient_class: 'wallet-card__btc-bg', + }, + account_list: [ + { + balance: 0.00212012, + currency: 'BTC', + account_name: 'Bitcoin', + icon: 'IcCurrencyBtc', + }, + ], + }, + { + wallet_details: { + balance: 0, + currency: 'ETH', + icon: getWalletCurrencyIcon('ETH', false), + icon_type: 'crypto', + jurisdiction_title: 'SVG', + name: 'Ethereum', + gradient_class: 'wallet-card__eth-bg', + }, + account_list: [ + { + balance: 0.00212012, + currency: 'ETH', + account_name: 'Ethereum', + icon: 'IcCurrencyEth', + }, + ], + }, + { + wallet_details: { + balance: 0, + currency: 'USDC', + icon: getWalletCurrencyIcon('USDC', false), + icon_type: 'crypto', + jurisdiction_title: 'SVG', + name: 'USD Coin', + gradient_class: 'wallet-card__usdc-bg', + }, + account_list: [ + { + balance: 0.00212012, + currency: 'USDC', + account_name: 'USD Coin', + icon: 'IcCurrencyUsdc', + }, + ], + }, + ], + }, + ]; +}; + +export default getMockWalletMigrationResponse; diff --git a/packages/appstore/src/constants/routes-config.ts b/packages/appstore/src/constants/routes-config.ts deleted file mode 100644 index 42109b352a64..000000000000 --- a/packages/appstore/src/constants/routes-config.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { localize } from '@deriv/translations'; -import TradersHub from 'Modules/traders-hub'; -import ConfigStore from 'Stores/config-store'; -import { TRoute } from 'Types'; -import Onboarding from 'Modules/onboarding'; -import CFDCompareAccounts from '@deriv/cfd/src/Containers/cfd-compare-accounts'; - -type TRoutesConfig = { - consumer_routes: ConfigStore['routes']; -}; - -// 1. Order matters! Put more specific consumer_routes at the top. -// 2. Don't use `Localize` component since native html tag like `option` cannot render them -const initRoutesConfig = ({ consumer_routes }: TRoutesConfig): TRoute[] => [ - { - path: consumer_routes.traders_hub, - component: TradersHub, - getTitle: () => localize("Trader's Hub"), - }, - { - path: consumer_routes.onboarding, - component: Onboarding, - getTitle: () => localize('Onboarding'), - }, - { - path: consumer_routes.compare_cfds, - component: CFDCompareAccounts, - getTitle: () => localize('CFDCompareAccounts'), - }, -]; - -let routes_config: Array; - -const getRoutesConfig = ({ consumer_routes }: TRoutesConfig): TRoute[] => { - // For default page route if page/path is not found, must be kept at the end of routes_config array. - if (!routes_config) { - const route_default = { getTitle: () => localize('Error 404') }; - - routes_config = initRoutesConfig({ consumer_routes }); - routes_config.push(route_default); - } - - return routes_config; -}; -export default getRoutesConfig; diff --git a/packages/appstore/src/constants/upgrade-info-lists-config.tsx b/packages/appstore/src/constants/upgrade-info-lists-config.tsx new file mode 100644 index 000000000000..468529bdd6d5 --- /dev/null +++ b/packages/appstore/src/constants/upgrade-info-lists-config.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { Text } from '@deriv/components'; +import { Localize } from '@deriv/translations'; + +type TUpgradeInformationList = { + is_eu: boolean; + text_info_size: string; + form_line_height: string; +}; + +const getUpgradeInformationList = ({ is_eu, text_info_size, form_line_height }: TUpgradeInformationList) => [ + { + name: 'upgrade_info', + visibility: true, + content: ( + + ), + }, + { + name: 'open_positions', + visibility: true, + content: , + }, + { + name: 'deriv_p2p', + visibility: !is_eu, + content: ( + } + /> + ), + }, + { + name: 'payment_agents', + visibility: !is_eu, + content: ( + } + /> + ), + }, +]; + +export default getUpgradeInformationList; diff --git a/packages/appstore/src/constants/utils.ts b/packages/appstore/src/constants/utils.ts index db86a3d13da9..d089409d2c29 100644 --- a/packages/appstore/src/constants/utils.ts +++ b/packages/appstore/src/constants/utils.ts @@ -1,4 +1,6 @@ import { isMobile } from '@deriv/shared'; +import { localize } from '@deriv/translations'; +import { TTransferAccount, TWalletButton } from 'Types'; /** * This function checks whether the current item should have a border at the bottom 'aka "divider" '. @@ -20,3 +22,125 @@ export const getHasDivider = (current_item_index: number, list_size: number, ava (list_size % available_grid_columns === 0 ? available_grid_columns : list_size % available_grid_columns) ); }; + +// TODO: Moved to shared package! Delete it later, right now it uses for cashier wallet modals +// TODO: Refactor using data transformation layer pattern when we will have API for wallets (e.g. wallet.icon) +export const getWalletCurrencyIcon = (currency: string, is_dark_mode_on: boolean, is_modal = false) => { + switch (currency) { + case 'demo': + if (is_modal) return 'IcWalletDerivDemoLight'; + return is_dark_mode_on ? 'IcWalletDerivDemoDark' : 'IcWalletDerivDemoLight'; + case 'USD': + return 'IcWalletCurrencyUsd'; + case 'EUR': + return 'IcWalletCurrencyEur'; + case 'AUD': + return 'IcWalletCurrencyAud'; + case 'GBP': + return 'IcWalletCurrencyGbp'; + case 'BTC': + return is_dark_mode_on ? 'IcWalletBitcoinDark' : 'IcWalletBitcoinLight'; + case 'ETH': + return is_dark_mode_on ? 'IcWalletEtheriumDark' : 'IcWalletEtheriumLight'; + case 'USDT': + case 'eUSDT': + case 'tUSDT': + case 'UST': + if (is_modal) { + return is_dark_mode_on ? 'IcWalletModalTetherDark' : 'IcWalletModalTetherLight'; + } + return is_dark_mode_on ? 'IcWalletTetherDark' : 'IcWalletTetherLight'; + case 'LTC': + return is_dark_mode_on ? 'IcWalletLiteCoinDark' : 'IcWalletLiteCoinLight'; + case 'USDC': + return is_dark_mode_on ? 'IcWalletUsdCoinDark' : 'IcWalletUsdCoinLight'; + default: + return 'Unknown'; + } +}; + +export const getWalletHeaderButtons = (is_demo: boolean, handleAction?: () => void): TWalletButton[] => { + return is_demo + ? [ + { + name: 'Transfer', + text: localize('Transfer'), + icon: 'IcAccountTransfer', + action: () => handleAction?.(), + }, + { + name: 'Transactions', + text: localize('Transactions'), + icon: 'IcStatement', + action: () => handleAction?.(), + }, + { + name: 'Deposit', + text: localize('Reset balance'), + icon: 'IcCashierAdd', + action: () => handleAction?.(), + }, + ] + : [ + { + name: 'Deposit', + text: localize('Deposit'), + icon: 'IcCashierAdd', + action: () => handleAction?.(), + }, + { + name: 'Withdraw', + text: localize('Withdraw'), + icon: 'IcCashierMinus', + action: () => handleAction?.(), + }, + { + name: 'Transfer', + text: localize('Transfer'), + icon: 'IcAccountTransfer', + action: () => handleAction?.(), + }, + { + name: 'Transactions', + text: localize('Transactions'), + icon: 'IcStatement', + action: () => handleAction?.(), + }, + ]; +}; + +export const getAccountName = ({ + account_type, + mt5_market_type, + display_currency_code, +}: Partial>): string => { + switch (account_type) { + case 'trading': + return localize('Deriv Apps'); + case 'mt5': { + switch (mt5_market_type) { + case 'financial': + return localize('MT5 Financial'); + case 'synthetic': + return localize('MT5 Derived'); + case 'all': + return localize('MT5 Swap-free'); + default: + return ''; + } + } + case 'derivez': + return localize('Deriv EZ'); + case 'dxtrade': + return localize('Deriv X'); + // @ts-expect-error Need to update @deriv/api-types to fix the TS error + case 'ctrader': + return localize('Deriv cTrader'); + case 'wallet': + return localize('{{display_currency_code}} Wallet', { + display_currency_code, + }); + default: + return ''; + } +}; diff --git a/packages/appstore/src/constants/wallet-mocked-response.ts b/packages/appstore/src/constants/wallet-mocked-response.ts new file mode 100644 index 000000000000..ba8ab7d627d7 --- /dev/null +++ b/packages/appstore/src/constants/wallet-mocked-response.ts @@ -0,0 +1,43 @@ +// TODO: Remove this file once we have the real API response +const wallets = [ + { + name: 'USD Wallet', + currency: 'usd', + icon: 'IcCurrencyUsd', + balance: 100000, + icon_type: 'fiat', + state: 'default', + jurisdiction_title: 'svg', + }, + { + name: 'USD Wallet', + currency: 'usd', + icon: 'IcCurrencyUsd', + balance: 100000, + icon_type: 'fiat', + state: 'default', + jurisdiction_title: 'svg', + }, + { + name: 'MT5 Derived Demo', + currency: 'usd', + icon: 'IcRebrandingMt5Logo', + wallet_icon: 'IcWalletDerivDemoLight', + balance: 879, + icon_type: 'app', + app: 'mt5', + state: 'default', + is_demo: true, + }, + { + name: 'Bitcoin Wallet', + currency: 'btc', + icon: 'IcCashierBitcoinLight', + balance: 0.003546, + icon_type: 'crypto', + state: 'default', + jurisdiction_title: 'svg', + }, +]; + +export default wallets; diff --git a/packages/appstore/src/constants/wallet_description_mapper.ts b/packages/appstore/src/constants/wallet_description_mapper.ts new file mode 100644 index 000000000000..914c2d1a63af --- /dev/null +++ b/packages/appstore/src/constants/wallet_description_mapper.ts @@ -0,0 +1,27 @@ +import { localize } from '@deriv/translations'; + +type TWalletDescriptionMapper = { + [key: string]: string; +}; + +const wallet_description_mapper: TWalletDescriptionMapper = { + AUD: localize('Deposit and withdraw Australian dollars using credit or debit cards, e-wallets, or bank wires.'), + EUR: localize( + 'Deposit and withdraw euros into your accounts regulated by MFSA using credit or debit cards and e-wallets.' + ), + USD: localize('Deposit and withdraw US dollars using credit or debit cards, e-wallets, or bank wires.'), + BTC: localize( + "Deposit and withdraw Bitcoin, the world's most popular cryptocurrency, hosted on the Bitcoin blockchain." + ), + ETH: localize('Deposit and withdraw Ether, the fastest growing cryptocurrency, hosted on the Ethereum blockchain.'), + LTC: localize( + 'Deposit and withdraw Litecoin, the cryptocurrency with low transaction fees, hosted on the Litecoin blockchain.' + ), + USDC: localize('Deposit and withdraw USD Coin, hosted on the Ethereum blockchain.'), + eUSDT: localize('Deposit and withdraw Tether ERC20, a version of Tether hosted on the Ethereum blockchain.'), + tUSDT: localize('Deposit and withdraw Tether TRC20, a version of Tether hosted on the TRON blockchain.'), + UST: localize('Deposit and withdraw Tether Omni, hosted on the Bitcoin blockchain.'), + PaymentAgent: localize('Deposit and withdraw funds via authorised, independent payment agents.'), +}; + +export default wallet_description_mapper; diff --git a/packages/appstore/src/constants/wallets-intro-content-config.tsx b/packages/appstore/src/constants/wallets-intro-content-config.tsx new file mode 100644 index 000000000000..9cc822735c3c --- /dev/null +++ b/packages/appstore/src/constants/wallets-intro-content-config.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import WalletsImage from 'Assets/svgs/wallets'; +import { localize, Localize } from '@deriv/translations'; + +const getWalletsIntroContent = (is_eu: boolean) => [ + { + image: , + title: localize('Introducing Wallets'), + description: is_eu ? localize('A new way to manage your funds') : localize('A better way to manage your funds'), + bullets: [ + is_eu ? localize('One Wallet for all your transactions') : localize('One Wallet, one currency'), + is_eu + ? localize('Keep track of your trading funds in one place') + : localize('A Wallet for each currency to focus your funds'), + !is_eu && ( + + ), + ], + }, + { + image: , + title: localize('How it works'), + description: is_eu ? localize('Simply add your funds and trade') : localize('Get a Wallet, add funds, trade'), + bullets: [ + !is_eu && localize('Get a Wallet for the currency you want'), + localize('Add funds to your Wallet via your favourite payment method'), + localize('Move funds to your trading account to start trading'), + ], + }, + { + image: , + title: localize('What happens to my trading accounts'), + description: localize("We'll link them"), + bullets: [ + is_eu + ? localize("We'll connect your existing USD trading account(s) to your new USD Wallet ") + : localize("We'll connect your existing trading accounts of the same currency to your new Wallet"), + !is_eu && localize('For example, all your USD trading account(s) will be linked to your USD Wallet'), + ], + }, +]; + +export default getWalletsIntroContent; 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 fd2f3eddabd5..bbe3c8f1b4ad 100644 --- a/packages/appstore/src/modules/traders-hub/__tests__/index.spec.tsx +++ b/packages/appstore/src/modules/traders-hub/__tests__/index.spec.tsx @@ -2,17 +2,22 @@ import React from 'react'; import { ContentFlag } from '@deriv/shared'; import { StoreProvider, mockStore } from '@deriv/stores'; import { render, screen } from '@testing-library/react'; +import { APIProvider } from '@deriv/api'; import TradersHub from '..'; jest.mock('Components/modals/modal-manager', () => jest.fn(() => 'mockedModalManager')); +jest.mock('Components/main-title-bar', () => jest.fn(() => 'mockedMainTitleBar')); jest.mock('Components/cfds-listing', () => jest.fn(() => 'mockedCFDsListing')); jest.mock('Components/options-multipliers-listing', () => jest.fn(() => 'mocked')); +jest.mock('../../tour-guide/tour-guide', () => jest.fn(() => 'mocked')); describe('TradersHub', () => { const render_container = (mock_store_override = {}) => { const mock_store = mockStore(mock_store_override); const wrapper = ({ children }: { children: JSX.Element }) => ( - {children} + + {children} + ); return render(, { diff --git a/packages/appstore/src/modules/traders-hub/index.tsx b/packages/appstore/src/modules/traders-hub/index.tsx index ac99a42f880d..c18f45c1ac9a 100644 --- a/packages/appstore/src/modules/traders-hub/index.tsx +++ b/packages/appstore/src/modules/traders-hub/index.tsx @@ -12,7 +12,7 @@ import classNames from 'classnames'; import TourGuide from '../tour-guide/tour-guide'; import './traders-hub.scss'; -const TradersHub = () => { +const TradersHub = observer(() => { const { traders_hub, client, ui } = useStore(); const { notification_messages_ui: Notifications, is_mobile } = ui; const { is_landing_company_loaded, is_logged_in, is_switching, is_logging_in, is_account_setting_loaded } = client; @@ -123,6 +123,6 @@ const TradersHub = () => { )} ); -}; +}); -export default observer(TradersHub); +export default TradersHub; diff --git a/packages/appstore/src/modules/wallets/desktop-wallets-list.tsx b/packages/appstore/src/modules/wallets/desktop-wallets-list.tsx new file mode 100644 index 000000000000..7889ef84ddff --- /dev/null +++ b/packages/appstore/src/modules/wallets/desktop-wallets-list.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { useWalletsList } from '@deriv/hooks'; +import { observer } from '@deriv/stores'; +import Wallet from 'Components/containers/wallet'; + +const DesktopWalletsList = observer(() => { + const { data } = useWalletsList(); + + return ( + + {data?.map(wallet => ( + + ))} + + ); +}); + +export default DesktopWalletsList; diff --git a/packages/appstore/src/modules/wallets/index.ts b/packages/appstore/src/modules/wallets/index.ts new file mode 100644 index 000000000000..b8cb10bdc8c3 --- /dev/null +++ b/packages/appstore/src/modules/wallets/index.ts @@ -0,0 +1 @@ +export { default as WalletsModule } from './wallets'; diff --git a/packages/appstore/src/modules/wallets/mobile-wallets-carousel.tsx b/packages/appstore/src/modules/wallets/mobile-wallets-carousel.tsx new file mode 100644 index 000000000000..df31999b94e8 --- /dev/null +++ b/packages/appstore/src/modules/wallets/mobile-wallets-carousel.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { ButtonToggle } from '@deriv/components'; +import { useActiveWallet, useContentFlag } from '@deriv/hooks'; +import { observer, useStore } from '@deriv/stores'; +import ButtonToggleLoader from 'Components/pre-loader/button-toggle-loader'; +import WalletCardsCarousel from 'Components/wallet-cards-carousel'; +import WalletCFDsListing from 'Components/wallet-content/wallet-cfds-listing'; +import WalletOptionsAndMultipliersListing from 'Components/wallet-content/wallet-option-multipliers-listing'; +import classNames from 'classnames'; + +const MobileWalletsCarousel = observer(() => { + const { client, traders_hub } = useStore(); + const { is_landing_company_loaded } = client; + const { selected_platform_type, setTogglePlatformType, is_eu_user } = traders_hub; + const { is_eu_demo, is_eu_real } = useContentFlag(); + const eu_title = is_eu_demo || is_eu_real || is_eu_user; + const active_wallet = useActiveWallet(); + + const platform_toggle_options = [ + { text: 'CFDs', value: 'cfd' }, + { text: eu_title ? 'Multipliers' : 'Options & Multipliers', value: 'options' }, + ]; + + const platformTypeChange = (event: { target: { value: string; name: string } }) => { + setTogglePlatformType(event.target.value); + }; + + return ( + <> + +
+ {is_landing_company_loaded ? ( + + ) : ( + + )} + {selected_platform_type === 'cfd' && } + {selected_platform_type === 'options' && } +
+ + ); +}); + +export default MobileWalletsCarousel; diff --git a/packages/appstore/src/modules/wallets/wallets.scss b/packages/appstore/src/modules/wallets/wallets.scss new file mode 100644 index 000000000000..a152a1057e9c --- /dev/null +++ b/packages/appstore/src/modules/wallets/wallets.scss @@ -0,0 +1,25 @@ +.wallets-module { + display: flex; + min-height: calc(100vh - 84px); + background-color: var(--general-section-1); + + @include mobile { + min-height: 100vh; + } + + &__content { + width: 100%; + max-width: 131.2rem; + margin: auto; + display: flex; + padding: 4rem; + flex-direction: column; + align-items: center; + gap: 2.4rem; + align-self: stretch; + + @include mobile { + padding: 2.4rem 0; + } + } +} diff --git a/packages/appstore/src/modules/wallets/wallets.tsx b/packages/appstore/src/modules/wallets/wallets.tsx new file mode 100644 index 000000000000..7704a25adaa6 --- /dev/null +++ b/packages/appstore/src/modules/wallets/wallets.tsx @@ -0,0 +1,37 @@ +import React, { useEffect } from 'react'; +import { ThemedScrollbars, Loading } from '@deriv/components'; +import { useActiveWallet, useWalletsList } from '@deriv/hooks'; +import { observer, useStore } from '@deriv/stores'; +import AddMoreWallets from 'Components/add-more-wallets'; +import ModalManager from 'Components/modals/modal-manager'; +import DesktopWalletsList from './desktop-wallets-list'; +import MobileWalletsCarousel from './mobile-wallets-carousel'; +import './wallets.scss'; + +const Wallets = observer(() => { + const { client, ui } = useStore(); + const { switchAccount, is_authorize } = client; + const { is_mobile } = ui; + const { data } = useWalletsList(); + const active_wallet = useActiveWallet(); + + useEffect(() => { + if (!active_wallet && data && data?.length) { + switchAccount(data[0].loginid); + } + }, [active_wallet, data, switchAccount]); + + if (!is_authorize) return ; + + return ( + +
+ {is_mobile ? : } + +
+ +
+ ); +}); + +export default Wallets; diff --git a/packages/appstore/src/public/images/wallet-header-demo-bg-dark.svg b/packages/appstore/src/public/images/wallet-header-demo-bg-dark.svg new file mode 100644 index 000000000000..0689dc76a1c5 --- /dev/null +++ b/packages/appstore/src/public/images/wallet-header-demo-bg-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/appstore/src/public/images/wallet-header-demo-bg.svg b/packages/appstore/src/public/images/wallet-header-demo-bg.svg new file mode 100644 index 000000000000..1625156e5725 --- /dev/null +++ b/packages/appstore/src/public/images/wallet-header-demo-bg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/appstore/src/services/websocket.ts b/packages/appstore/src/services/websocket.ts deleted file mode 100644 index b2ac6e22830e..000000000000 --- a/packages/appstore/src/services/websocket.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { DerivWS } from 'Types'; - -let ws: DerivWS = null; - -export const useWs = (): DerivWS => ws; - -export const initWs = (ws_instance: DerivWS): void => (ws = ws_instance); diff --git a/packages/appstore/src/stores/base-store.ts b/packages/appstore/src/stores/base-store.ts deleted file mode 100644 index b24a80ad456d..000000000000 --- a/packages/appstore/src/stores/base-store.ts +++ /dev/null @@ -1,9 +0,0 @@ -import RootStore from './root-store'; - -export default class BaseStore { - public root_store: RootStore; - - public constructor(root_store: RootStore) { - this.root_store = root_store; - } -} diff --git a/packages/appstore/src/stores/config-store.ts b/packages/appstore/src/stores/config-store.ts deleted file mode 100644 index 7b92e450171a..000000000000 --- a/packages/appstore/src/stores/config-store.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { TConfigProps } from 'Types'; -import BaseStore from './base-store'; - -export default class ConfigStore extends BaseStore { - public has_router = true; - public routes = { - traders_hub: '/appstore/traders-hub', - onboarding: '/appstore/onboarding', - compare_cfds: '/appstore/compare-accounts', - - my_apps: '/my-apps', - explore: '/explore', - about_us: '/about-us', - resources: '/resources', - - market_commodities: '/markets/commodities', - market_forex: '/markets/forex', - market_stock: '/markets/stock', - market_synthetic: '/markets/synthetic', - markets: '/markets', - - platform_binary_bot: '/platforms/binary-bot', - platform_dbot: '/platforms/dbot', - platform_dmt5: '/platforms/dmt5', - platform_dmt5_financial: '/platforms/dmt5-financial', - platform_dmt5_financial_stp: '/platforms/dmt5-financial-stp', - platform_dmt5_synthetic: '/platforms/dmt5-synthetic', - platform_dtrader: '/platforms/dtrader', - platform_smarttrader: '/platforms/smarttrader', - platforms: '/platforms', - - trade_type_cfds: '/trade-types/cfds', - trade_type_multipliers: '/trade-types/multipliers', - trade_type_options: '/trade-types/options', - trade_types: '/trade-types', - - wallet_bank_wire: '/wallets/bank-wire', - wallet_cards: '/wallets/cards', - wallet_crypto: '/wallets/crypto', - wallet_ewallet: '/wallets/ewallet', - wallets: '/wallets', - }; - - public setConfig(config: TConfigProps): void { - this.has_router = config.has_router; - this.routes = config.routes; - } -} diff --git a/packages/appstore/src/stores/index.ts b/packages/appstore/src/stores/index.ts index 7f15b07398b9..86259b611fcd 100644 --- a/packages/appstore/src/stores/index.ts +++ b/packages/appstore/src/stores/index.ts @@ -1,18 +1,4 @@ -import * as React from 'react'; -import { initWs } from 'Services/websocket'; -import { TRootStore } from 'Types'; -import RootStore from './root-store'; - -let stores_context: React.Context; - -export const initContext = (core_store: TRootStore, websocket: Record): void => { - if (!stores_context) { - const root_store = new RootStore(core_store); - stores_context = React.createContext(root_store); - - initWs(websocket); - } -}; +import { useStore } from '@deriv/stores'; /** @deprecated Use `useStore` from `@deriv/stores` package instead. */ -export const useStores = (): TRootStore => React.useContext(stores_context); +export const useStores: () => any = useStore; diff --git a/packages/appstore/src/stores/root-store.ts b/packages/appstore/src/stores/root-store.ts deleted file mode 100644 index 471185b72deb..000000000000 --- a/packages/appstore/src/stores/root-store.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { TRootStore } from 'Types'; -import ConfigStore from './config-store'; - -export default class RootStore { - public config: ConfigStore; - public ws: unknown; - public client: Record; - public common: Record; - public ui: Record; - public modules: Record; - public notifications: Record; - public traders_hub: Record; - - public constructor(core_store: TRootStore) { - this.config = new ConfigStore(this); - this.client = core_store.client; - this.common = core_store.common; - this.ui = core_store.ui; - this.modules = core_store.modules; - this.notifications = core_store.notifications; - this.traders_hub = core_store.traders_hub; - } -} diff --git a/packages/appstore/src/types/api.types.ts b/packages/appstore/src/types/api.types.ts deleted file mode 100644 index 90ebe40a82b9..000000000000 --- a/packages/appstore/src/types/api.types.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type TErrorResponse = - | { - message: string; - code: string; - } - | undefined; diff --git a/packages/appstore/src/types/common.types.ts b/packages/appstore/src/types/common.types.ts index 846ee2f212c4..7bd4ef4ece1b 100644 --- a/packages/appstore/src/types/common.types.ts +++ b/packages/appstore/src/types/common.types.ts @@ -1,4 +1,6 @@ import { DetailsOfEachMT5Loginid } from '@deriv/api-types'; +import { useWalletsList, useAvailableWallets } from '@deriv/hooks'; +import { useStore } from '@deriv/stores'; import { PlatformIcons } from 'Assets/svgs/trading-platform'; import { RegionAvailability } from 'Constants/platform-config'; @@ -134,6 +136,8 @@ export interface AvailableAccount { market_type?: 'all' | 'financial' | 'synthetic'; icon: keyof typeof PlatformIcons; availability: RegionAvailability; + short_code_and_region?: string; + login?: string; } export type Currency = @@ -167,3 +171,73 @@ export interface AccountListDetail { loginid: string; title: string; } + +export type TAccountStatus = 'pending' | 'failed' | 'need_verification' | ''; +export type TWalletCurrency = + | Extract + | 'USDT' + | 'eUSDT' + | 'tUSDT'; +export type TWalletShortcode = Extract; +export type TLinkedTo = { + loginid?: string; + platform?: string; + balance?: string; + currency?: string; +}; + +export type TWalletAccount = NonNullable['data']>[number]; +export type TWalletInfo = NonNullable['data']>[number]; + +export type TTransferAccount = { + active_wallet_icon: string | undefined; + account_type?: 'wallet' | 'trading' | 'dxtrade' | 'mt5' | 'derivez' | 'binary' | 'ctrader'; + balance: number; + currency?: string; + display_currency_code: string | undefined; + gradient_class?: `wallet-card__${string}`; + icon?: string; + is_demo: boolean; + loginid?: string; + mt5_market_type?: 'all' | 'financial' | 'synthetic'; + shortcode: string | undefined; + type: 'fiat' | 'crypto' | 'demo'; +}; + +export type TMessageItem = + | { + variant: 'base'; + key: string; + type: 'info' | 'error' | 'success'; + message: string | JSX.Element; + } + | { + variant: 'with-action-button'; + onClickHandler: VoidFunction; + button_label: string; + key: string; + type: 'info' | 'error' | 'success'; + message: string | JSX.Element; + }; + +export type TWalletButton = { + name: Parameters['traders_hub']['setWalletModalActiveTab']>[0]; + text: string; + icon: string; + action: () => void; +}; + +export type TWalletSteps = { + handleBack: () => void; + handleClose: () => void; + handleNext: () => void; + is_disabled: boolean; + toggleCheckbox: () => void; + upgradeToWallets: (value: boolean) => void; +}; + +export type TRealWalletsUpgradeSteps = { + wallet_upgrade_steps: TWalletSteps & { + current_step: number; + }; +}; diff --git a/packages/appstore/src/types/index.ts b/packages/appstore/src/types/index.ts index edc109feef38..237db8bfebe2 100644 --- a/packages/appstore/src/types/index.ts +++ b/packages/appstore/src/types/index.ts @@ -1,6 +1 @@ -export * from './props.types'; -export * from './stores.types'; -export * from './params.types'; -export * from './api.types'; -export * from './ws.types'; export * from './common.types'; diff --git a/packages/appstore/src/types/params.types.ts b/packages/appstore/src/types/params.types.ts deleted file mode 100644 index 182bbc23e79d..000000000000 --- a/packages/appstore/src/types/params.types.ts +++ /dev/null @@ -1,30 +0,0 @@ -export type TRoute = { - component?: any; - default?: boolean; - exact?: boolean; - getTitle?: () => string; - icon?: string; - is_authenticated?: boolean; - is_routed?: boolean; - is_modal?: boolean; - label?: string; - path?: string; - routes?: TRoute[]; - subroutes?: TRoute[]; - to?: string; - is_logged_in?: boolean; - is_logging_in?: boolean; -}; - -export type TRouteGroup = { - default?: boolean; - icon?: string; - getTitle?: () => string; - path?: string; - subitems?: number[]; -}; -export type TRouteConfig = TRoute & { - is_modal?: boolean; - is_authenticated?: boolean; - routes?: TRoute[]; -}; diff --git a/packages/appstore/src/types/props.types.ts b/packages/appstore/src/types/props.types.ts deleted file mode 100644 index ed94bb03b11d..000000000000 --- a/packages/appstore/src/types/props.types.ts +++ /dev/null @@ -1,6 +0,0 @@ -import ConfigStore from 'Stores/config-store'; - -export type TConfigProps = { - has_router: boolean; - routes: ConfigStore['routes']; -}; diff --git a/packages/appstore/src/types/stores.types.ts b/packages/appstore/src/types/stores.types.ts deleted file mode 100644 index 690863da4f7e..000000000000 --- a/packages/appstore/src/types/stores.types.ts +++ /dev/null @@ -1,11 +0,0 @@ -import ConfigStore from 'Stores/config-store'; - -export type TRootStore = { - ui: Record; - common: Record; - client: Record; - config: ConfigStore; - modules: Record; - notifications: Record; - traders_hub: Record; -}; diff --git a/packages/appstore/src/types/ws.types.ts b/packages/appstore/src/types/ws.types.ts deleted file mode 100644 index 8dc541ad5384..000000000000 --- a/packages/appstore/src/types/ws.types.ts +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type DerivWS = any; diff --git a/packages/appstore/webpack.config.js b/packages/appstore/webpack.config.js index 2326f6fd28df..ea6310164228 100644 --- a/packages/appstore/webpack.config.js +++ b/packages/appstore/webpack.config.js @@ -107,9 +107,6 @@ module.exports = function (env) { 'style-loader', { loader: 'css-loader', - options: { - url: (_, resourcePath) => resourcePath.includes('packages/wallets'), - }, }, { loader: 'postcss-loader', @@ -158,6 +155,15 @@ module.exports = function (env) { include: /public\//, use: svg_loaders, }, + { + test: /\.svg$/, + exclude: [/node_modules/, path.resolve('../', 'wallets')], + include: /public\//, + type: 'asset/resource', + generator: { + filename: 'appstore/public/[name].[contenthash][ext]', + }, + }, { test: /\.svg$/, exclude: [/node_modules|public\//], diff --git a/packages/bot-skeleton/package.json b/packages/bot-skeleton/package.json index f396b5a390ac..afcdb7aea322 100644 --- a/packages/bot-skeleton/package.json +++ b/packages/bot-skeleton/package.json @@ -35,7 +35,7 @@ "eslint-plugin-react-hooks": "^4.2.0" }, "dependencies": { - "@deriv/deriv-api": "^1.0.11", + "@deriv/deriv-api": "^1.0.13", "@deriv/indicators": "^1.0.0", "@deriv/js-interpreter": "^3.0.0", "@deriv/shared": "^1.0.0", diff --git a/packages/bot-skeleton/src/scratch/dbot.js b/packages/bot-skeleton/src/scratch/dbot.js index 0eadcfcc3f29..312aef9128ef 100644 --- a/packages/bot-skeleton/src/scratch/dbot.js +++ b/packages/bot-skeleton/src/scratch/dbot.js @@ -1,14 +1,16 @@ -import './blockly'; -import { isAllRequiredBlocksEnabled, updateDisabledBlocks, validateErrorOnBlockDelete } from './utils'; -import main_xml from './xml/main.xml'; -import DBotStore from './dbot-store'; import { save_types } from '../constants'; import { config } from '../constants/config'; -import { getSavedWorkspaces, saveWorkspaceToRecent } from '../utils/local-storage'; -import { observer as globalObserver, compareXml } from '../utils'; +import { api_base } from '../services/api/api-base'; import ApiHelpers from '../services/api/api-helpers'; import Interpreter from '../services/tradeEngine/utils/interpreter'; -import { api_base } from '../services/api/api-base'; +import { compareXml,observer as globalObserver } from '../utils'; +import { getSavedWorkspaces, saveWorkspaceToRecent } from '../utils/local-storage'; + +import main_xml from './xml/main.xml'; +import DBotStore from './dbot-store'; +import { isAllRequiredBlocksEnabled, updateDisabledBlocks, validateErrorOnBlockDelete } from './utils'; + +import './blockly'; class DBot { constructor() { diff --git a/packages/bot-web-ui/package-lock.json b/packages/bot-web-ui/package-lock.json index dd2b96df9265..d67f84ddb208 100644 --- a/packages/bot-web-ui/package-lock.json +++ b/packages/bot-web-ui/package-lock.json @@ -95,7 +95,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@deriv/deriv-api": "^1.0.11", + "@deriv/deriv-api": "^1.0.13", "@deriv/indicators": "^1.0.0", "@deriv/js-interpreter": "^3.0.0", "@deriv/shared": "^1.0.0", diff --git a/packages/bot-web-ui/src/components/dashboard/bot-builder/bot-builder.tsx b/packages/bot-web-ui/src/components/dashboard/bot-builder/bot-builder.tsx index a7bfbcde7dcd..3caffd8258e4 100644 --- a/packages/bot-web-ui/src/components/dashboard/bot-builder/bot-builder.tsx +++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/bot-builder.tsx @@ -1,11 +1,14 @@ import React from 'react'; import classNames from 'classnames'; + import { observer } from '@deriv/stores'; + import { useDBotStore } from '../../../stores/useDBotStore'; import LoadModal from '../../load-modal'; import SaveModal from '../dashboard-component/load-bot-preview/save-modal'; import BotBuilderTourHandler from '../dbot-tours/bot-builder-tour'; import QuickStrategy from '../quick-strategy'; + import WorkspaceWrapper from './workspace-wrapper'; const BotBuilder = observer(() => { diff --git a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/__tests__/toolbar.spec.tsx b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/__tests__/toolbar.spec.tsx index 4110c8d34bdf..3a4028460580 100644 --- a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/__tests__/toolbar.spec.tsx +++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/__tests__/toolbar.spec.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { isDesktop, isMobile } from '@deriv/shared'; +import { useStore } from '@deriv/stores'; +// eslint-disable-next-line import/no-extraneous-dependencies import { render, screen } from '@testing-library/react'; import { useDBotStore } from 'Stores/useDBotStore'; import Toolbar from '..'; @@ -33,10 +34,14 @@ jest.mock('Stores/useDBotStore', () => ({ useDBotStore: jest.fn(() => mockDbotStore), })); -jest.mock('@deriv/shared', () => ({ - ...jest.requireActual('@deriv/shared'), - isMobile: jest.fn(() => false), - isDesktop: jest.fn(() => true), +jest.mock('@deriv/stores', () => ({ + ...jest.requireActual('@deriv/stores'), + observer: jest.fn(x => x), + useStore: jest.fn(() => ({ + ui: { + is_mobile: false, + }, + })), })); describe('Toolbar component', () => { @@ -61,8 +66,11 @@ describe('Toolbar component', () => { }); it('Toolbar should renders a button, when it is mobile version', async () => { - (isDesktop as jest.Mock).mockReturnValue(false); - (isMobile as jest.Mock).mockReturnValue(true); + (useStore as jest.Mock).mockReturnValue({ + ui: { + is_mobile: true, + }, + }); render(); expect(await screen.findByRole('button')).toBeInTheDocument(); }); diff --git a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/toolbar.tsx b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/toolbar.tsx index 4b1fcd1b3141..e2eb70bbe3b6 100644 --- a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/toolbar.tsx +++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/toolbar.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { Dialog } from '@deriv/components'; -import { isMobile } from '@deriv/shared'; -import { observer } from '@deriv/stores'; +import { observer, useStore } from '@deriv/stores'; import { Localize, localize } from '@deriv/translations'; import { useDBotStore } from 'Stores/useDBotStore'; import ToolbarButton from './toolbar-button'; @@ -9,6 +8,9 @@ import WorkspaceGroup from './workspace-group'; const Toolbar = observer(() => { const { run_panel, save_modal, load_modal, toolbar, quick_strategy } = useDBotStore(); + const { + ui: { is_mobile }, + } = useStore(); const { has_redo_stack, has_undo_stack, @@ -32,7 +34,7 @@ const Toolbar = observer(() => {
- {isMobile() && ( + {is_mobile && ( { ]} /> ) : ( - localize('Any unsaved changes will be lost.') + )} diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/__tests__/dashboard-component.spec.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/__tests__/dashboard-component.spec.tsx index 9e904e5bfa07..7dac2d6e335e 100644 --- a/packages/bot-web-ui/src/components/dashboard/dashboard-component/__tests__/dashboard-component.spec.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/__tests__/dashboard-component.spec.tsx @@ -1,7 +1,9 @@ import React from 'react'; + import { isMobile } from '@deriv/shared'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; + import UserGuide from '../user-guide'; jest.mock('@deriv/components', () => { diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/dashboard-component.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/dashboard-component.tsx index 0786c435beb1..977df2d29739 100644 --- a/packages/bot-web-ui/src/components/dashboard/dashboard-component/dashboard-component.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/dashboard-component.tsx @@ -1,11 +1,15 @@ import React from 'react'; import classNames from 'classnames'; + import { DesktopWrapper, MobileWrapper, Text } from '@deriv/components'; import { isMobile } from '@deriv/shared'; import { observer } from '@deriv/stores'; import { localize } from '@deriv/translations'; + import { useDBotStore } from 'Stores/useDBotStore'; + import OnboardTourHandler from '../dbot-tours/onboarding-tour'; + import Local from './load-bot-preview/local'; import Cards from './cards'; import InfoPanel from './info-panel'; diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/info-panel.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/info-panel.tsx index 04bca7895733..06c29f16119a 100644 --- a/packages/bot-web-ui/src/components/dashboard/dashboard-component/info-panel.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/info-panel.tsx @@ -1,14 +1,18 @@ import React from 'react'; import classNames from 'classnames'; + import { DesktopWrapper, Icon, MobileWrapper, Modal, Text } from '@deriv/components'; -import { isMobile } from '@deriv/shared'; -import { observer } from '@deriv/stores'; +import { observer, useStore } from '@deriv/stores'; + import { DBOT_TABS } from 'Constants/bot-contents'; import { useDBotStore } from 'Stores/useDBotStore'; + import { SIDEBAR_INTRO } from './constants'; const InfoPanel = observer(() => { - const is_mobile = isMobile(); + const { + ui: { is_mobile }, + } = useStore(); const { dashboard } = useDBotStore(); const { active_tour, is_info_panel_visible, setActiveTab, setActiveTabTutorial, setInfoPanelVisibility } = dashboard; diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/recent-workspace.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/recent-workspace.tsx index 3380df771da9..b5efd9be7dc2 100644 --- a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/recent-workspace.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/recent-workspace.tsx @@ -1,15 +1,20 @@ import React from 'react'; import classnames from 'classnames'; + import { timeSince } from '@deriv/bot-skeleton'; import { save_types } from '@deriv/bot-skeleton/src/constants/save-type'; import { DesktopWrapper, Icon, MobileWrapper, Text } from '@deriv/components'; import { isDesktop, isMobile } from '@deriv/shared'; import { observer } from '@deriv/stores'; + import { DBOT_TABS } from 'Constants/bot-contents'; -import { waitForDomElement } from 'Utils/dom-observer'; import { useDBotStore } from 'Stores/useDBotStore'; +import { waitForDomElement } from 'Utils/dom-observer'; + import { useComponentVisibility } from '../../hooks/useComponentVisibility'; + import { CONTEXT_MENU_MOBILE, MENU_DESKTOP, STRATEGY } from './constants'; + import './index.scss'; type TRecentWorkspace = { diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard.tsx index 8990bb53c772..2fe93bc328d7 100644 --- a/packages/bot-web-ui/src/components/dashboard/dashboard.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dashboard.tsx @@ -1,5 +1,6 @@ import React, { useEffect } from 'react'; import classNames from 'classnames'; + import { updateWorkspaceName } from '@deriv/bot-skeleton'; import dbot from '@deriv/bot-skeleton/src/scratch/dbot'; import { initTrashCan } from '@deriv/bot-skeleton/src/scratch/hooks/trashcan'; @@ -8,10 +9,13 @@ import { DesktopWrapper, Dialog, MobileWrapper, Tabs } from '@deriv/components'; import { isMobile } from '@deriv/shared'; import { observer, useStore } from '@deriv/stores'; import { localize } from '@deriv/translations'; + import Chart from 'Components/chart'; import { DBOT_TABS, TAB_IDS } from 'Constants/bot-contents'; import { useDBotStore } from 'Stores/useDBotStore'; + import RunPanel from '../run-panel'; + import RunStrategy from './dashboard-component/run-strategy'; import { tour_list } from './dbot-tours/utils'; import DashboardComponent from './dashboard-component'; diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/bot-builder-tour/bot-builder-tour-desktop.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/bot-builder-tour/bot-builder-tour-desktop.tsx index 102d06d91b0c..9d0044dc21c7 100644 --- a/packages/bot-web-ui/src/components/dashboard/dbot-tours/bot-builder-tour/bot-builder-tour-desktop.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/bot-builder-tour/bot-builder-tour-desktop.tsx @@ -1,7 +1,10 @@ import React from 'react'; + import { observer } from '@deriv/stores'; -import { getSetting } from 'Utils/settings'; + import { useDBotStore } from 'Stores/useDBotStore'; +import { getSetting } from 'Utils/settings'; + import ReactJoyrideWrapper from '../common/react-joyride-wrapper'; import TourEndDialog from '../common/tour-end-dialog'; import TourStartDialog from '../common/tour-start-dialog'; diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/bot-builder-tour/bot-builder-tour-mobile.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/bot-builder-tour/bot-builder-tour-mobile.tsx index 1964f56f0b39..fb11296b7fe5 100644 --- a/packages/bot-web-ui/src/components/dashboard/dbot-tours/bot-builder-tour/bot-builder-tour-mobile.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/bot-builder-tour/bot-builder-tour-mobile.tsx @@ -1,9 +1,12 @@ import React from 'react'; + import { ProgressBarTracker } from '@deriv/components'; import { observer } from '@deriv/stores'; import { localize } from '@deriv/translations'; -import { getSetting } from 'Utils/settings'; + import { useDBotStore } from 'Stores/useDBotStore'; +import { getSetting } from 'Utils/settings'; + import Accordion from '../common/accordion'; import TourButton from '../common/tour-button'; import TourStartDialog from '../common/tour-start-dialog'; diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/bot-builder-tour/index.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/bot-builder-tour/index.tsx index d8208c9cba57..3b512e67d903 100644 --- a/packages/bot-web-ui/src/components/dashboard/dbot-tours/bot-builder-tour/index.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/bot-builder-tour/index.tsx @@ -1,6 +1,8 @@ import React from 'react'; import { observer } from 'mobx-react'; + import { isMobile } from '@deriv/shared'; + import BotBuilderTourDesktop from './bot-builder-tour-desktop'; import BotBuilderTourMobile from './bot-builder-tour-mobile'; diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/__tests__/accordion.spec.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/__tests__/accordion.spec.tsx index 2577b3b9fb2d..699f1850fc7e 100644 --- a/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/__tests__/accordion.spec.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/__tests__/accordion.spec.tsx @@ -1,5 +1,7 @@ import React from 'react'; + import { fireEvent, render, screen, waitFor } from '@testing-library/react'; + import Accordion from '../accordion'; const mocked_props = { diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/__tests__/react-joyride-wrapper.spec.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/__tests__/react-joyride-wrapper.spec.tsx index d7a58b2f8150..7e71f25c1fd2 100644 --- a/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/__tests__/react-joyride-wrapper.spec.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/__tests__/react-joyride-wrapper.spec.tsx @@ -1,9 +1,12 @@ import React from 'react'; + import { mockStore, StoreProvider } from '@deriv/stores'; // eslint-disable-next-line import/no-extraneous-dependencies import { render, screen } from '@testing-library/react'; -import { mock_ws } from 'Utils/mock'; + import { DBotStoreProvider, mockDBotStore } from 'Stores/useDBotStore'; +import { mock_ws } from 'Utils/mock'; + import ReactJoyrideWrapper from '../react-joyride-wrapper'; jest.mock('@deriv/bot-skeleton/src/scratch/blockly', () => jest.fn()); diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/__tests__/tour-button.spec.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/__tests__/tour-button.spec.tsx index 9cd8e41ccd4b..960e55f7bdf8 100644 --- a/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/__tests__/tour-button.spec.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/__tests__/tour-button.spec.tsx @@ -1,5 +1,7 @@ import React from 'react'; + import { fireEvent, render, screen } from '@testing-library/react'; + import TourButton from '../tour-button'; const mocked_props = { diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/accordion.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/accordion.tsx index 02da2ffa2940..797ba949058b 100644 --- a/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/accordion.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/accordion.tsx @@ -1,7 +1,9 @@ import React from 'react'; import classNames from 'classnames'; + import { Icon, Text } from '@deriv/components'; import { localize } from '@deriv/translations'; + import { TStepMobile } from '../config'; type TAccordion = { diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/react-joyride-wrapper.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/react-joyride-wrapper.tsx index 103226c2e470..a123f715050d 100644 --- a/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/react-joyride-wrapper.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/react-joyride-wrapper.tsx @@ -1,5 +1,6 @@ import React from 'react'; import ReactJoyride, { CallBackProps, Step, Styles } from 'react-joyride'; + import { localize } from '@deriv/translations'; const common_tour_button_properties = { diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/tour-button.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/tour-button.tsx index c1d92380adef..fca1280afd76 100644 --- a/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/tour-button.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/tour-button.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { Text } from '@deriv/components'; type TTourButton = { diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/tour-end-dialog.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/tour-end-dialog.tsx index f5c273613509..aeeae1c010db 100644 --- a/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/tour-end-dialog.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/tour-end-dialog.tsx @@ -1,8 +1,10 @@ import React from 'react'; + import { Dialog, Text } from '@deriv/components'; import { isMobile } from '@deriv/shared'; import { observer } from '@deriv/stores'; import { Localize, localize } from '@deriv/translations'; + import { useDBotStore } from '../../../../stores/useDBotStore'; const TourEndDialog = observer(() => { diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/tour-start-dialog.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/tour-start-dialog.tsx index 673936b6ba67..27983aebffbc 100644 --- a/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/tour-start-dialog.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/tour-start-dialog.tsx @@ -1,9 +1,12 @@ import React from 'react'; + import { Dialog, Text } from '@deriv/components'; import { isMobile } from '@deriv/shared'; import { observer } from '@deriv/stores'; import { Localize, localize } from '@deriv/translations'; + import { DBOT_TABS } from 'Constants/bot-contents'; + import { useDBotStore } from '../../../../stores/useDBotStore'; import { bot_builder_tour_header, onboarding_tour_header, tourDialogAction, tourDialogInfo } from '../config'; import { setTourSettings, tour_list } from '../utils'; diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/tour-steps.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/tour-steps.tsx index da7f22c27029..42dcd3da26bb 100644 --- a/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/tour-steps.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/tour-steps.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { getUUID } from '@deriv/bot-skeleton/src/services/tradeEngine/utils/helpers'; import { Text } from '@deriv/components'; import { observer } from '@deriv/stores'; diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/config/__tests__/config.spec.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/config/__tests__/config.spec.tsx index 6740ebc9fdd8..7981e387a27d 100644 --- a/packages/bot-web-ui/src/components/dashboard/dbot-tours/config/__tests__/config.spec.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/config/__tests__/config.spec.tsx @@ -1,9 +1,12 @@ import React from 'react'; + import { mockStore, StoreProvider } from '@deriv/stores'; import { render, screen } from '@testing-library/react'; -import { mock_ws } from 'Utils/mock'; + import RootStore from 'Stores/root-store'; import { DBotStoreProvider, mockDBotStore } from 'Stores/useDBotStore'; +import { mock_ws } from 'Utils/mock'; + import OnboardingTourMobile from '../../onboarding-tour/onboarding-tour-mobile'; import { DBOT_ONBOARDING_MOBILE } from '../index'; diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/config/index.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/config/index.tsx index 6dd3f126ef03..b80ed86103f2 100644 --- a/packages/bot-web-ui/src/components/dashboard/dbot-tours/config/index.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/config/index.tsx @@ -1,8 +1,10 @@ -import { getImageLocation } from '../../../../public-path'; import React from 'react'; + import { Icon, Text } from '@deriv/components'; import { getUrlBase, isMobile } from '@deriv/shared'; import { Localize, localize } from '@deriv/translations'; + +import { getImageLocation } from '../../../../public-path'; import TourSteps from '../common/tour-steps'; const is_mobile = isMobile(); diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/onboarding-tour/index.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/onboarding-tour/index.tsx index b27e470f5ba6..7818f82e5e23 100644 --- a/packages/bot-web-ui/src/components/dashboard/dbot-tours/onboarding-tour/index.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/onboarding-tour/index.tsx @@ -1,6 +1,8 @@ import React from 'react'; import { observer } from 'mobx-react'; + import { isMobile } from '@deriv/shared'; + import OnboardingTourDesktop from './onboarding-tour-desktop'; import OnboardingTourMobile from './onboarding-tour-mobile'; diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/onboarding-tour/onboarding-tour-desktop.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/onboarding-tour/onboarding-tour-desktop.tsx index c77788a34df6..528a84d0def7 100644 --- a/packages/bot-web-ui/src/components/dashboard/dbot-tours/onboarding-tour/onboarding-tour-desktop.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/onboarding-tour/onboarding-tour-desktop.tsx @@ -1,7 +1,10 @@ import React from 'react'; + import { observer } from '@deriv/stores'; -import { getSetting } from 'Utils/settings'; + import { useDBotStore } from 'Stores/useDBotStore'; +import { getSetting } from 'Utils/settings'; + import ReactJoyrideWrapper from '../common/react-joyride-wrapper'; import TourStartDialog from '../common/tour-start-dialog'; import { DBOT_ONBOARDING } from '../config'; diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/onboarding-tour/onboarding-tour-mobile.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/onboarding-tour/onboarding-tour-mobile.tsx index 2e04b3e49234..b9454cab4784 100644 --- a/packages/bot-web-ui/src/components/dashboard/dbot-tours/onboarding-tour/onboarding-tour-mobile.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/onboarding-tour/onboarding-tour-mobile.tsx @@ -1,10 +1,13 @@ import React from 'react'; import classNames from 'classnames'; + import { Icon, ProgressBarTracker, Text } from '@deriv/components'; import { observer } from '@deriv/stores'; import { localize } from '@deriv/translations'; -import { getSetting } from 'Utils/settings'; + import { useDBotStore } from 'Stores/useDBotStore'; +import { getSetting } from 'Utils/settings'; + import TourButton from '../common/tour-button'; import { DBOT_ONBOARDING_MOBILE, TMobileTourConfig } from '../config'; diff --git a/packages/bot-web-ui/src/components/load-modal/local.tsx b/packages/bot-web-ui/src/components/load-modal/local.tsx index 005fd0372a14..446ae77b5193 100644 --- a/packages/bot-web-ui/src/components/load-modal/local.tsx +++ b/packages/bot-web-ui/src/components/load-modal/local.tsx @@ -1,10 +1,13 @@ import React from 'react'; import classNames from 'classnames'; + import { Button, Icon } from '@deriv/components'; import { isMobile } from '@deriv/shared'; import { observer } from '@deriv/stores'; import { Localize, localize } from '@deriv/translations'; + import { useDBotStore } from 'Stores/useDBotStore'; + import LocalFooter from './local-footer'; import WorkspaceControl from './workspace-control'; diff --git a/packages/bot-web-ui/src/components/run-panel/run-panel.tsx b/packages/bot-web-ui/src/components/run-panel/run-panel.tsx index b0ef95e8231f..d49bb69d5efc 100644 --- a/packages/bot-web-ui/src/components/run-panel/run-panel.tsx +++ b/packages/bot-web-ui/src/components/run-panel/run-panel.tsx @@ -1,9 +1,10 @@ import React from 'react'; import classNames from 'classnames'; + import { Button, Drawer, Modal, Money, Tabs, Text, ThemedScrollbars } from '@deriv/components'; -import { isMobile } from '@deriv/shared'; import { observer, useStore } from '@deriv/stores'; import { Localize, localize } from '@deriv/translations'; + import Journal from 'Components/journal'; import SelfExclusion from 'Components/self-exclusion'; import Summary from 'Components/summary'; @@ -230,7 +231,10 @@ const StatisticsInfoModal = ({ const RunPanel = observer(() => { const { run_panel, dashboard } = useDBotStore(); - const { client } = useStore(); + const { + client, + ui: { is_mobile }, + } = useStore(); const { currency } = client; const { active_index, @@ -250,8 +254,6 @@ const RunPanel = observer(() => { const { total_payout, total_profit, total_stake, won_contracts, lost_contracts, number_of_runs } = statistics; const { BOT_BUILDER, CHART } = DBOT_TABS; - const is_mobile = isMobile(); - React.useEffect(() => { onMount(); return () => onUnmount(); diff --git a/packages/bot-web-ui/src/components/summary/summary-card.tsx b/packages/bot-web-ui/src/components/summary/summary-card.tsx index 4ef10938d3f4..c061587ea502 100644 --- a/packages/bot-web-ui/src/components/summary/summary-card.tsx +++ b/packages/bot-web-ui/src/components/summary/summary-card.tsx @@ -1,12 +1,15 @@ import React from 'react'; import classNames from 'classnames'; + import { ContractCard, Text } from '@deriv/components'; import { getCardLabels, isMobile } from '@deriv/shared'; import { observer, useStore } from '@deriv/stores'; import { localize } from '@deriv/translations'; + import ContractCardLoader from 'Components/contract-card-loading'; import { getContractTypeDisplay } from 'Constants/contract'; import { useDBotStore } from 'Stores/useDBotStore'; + import { TSummaryCardProps } from './summary-card.types'; const SummaryCard = observer(({ contract_info, is_contract_loading }: TSummaryCardProps) => { diff --git a/packages/bot-web-ui/src/components/transaction-details/__tests__/transaction-details.spec.tsx b/packages/bot-web-ui/src/components/transaction-details/__tests__/transaction-details.spec.tsx index c0c426728fc2..67102dcb8d90 100644 --- a/packages/bot-web-ui/src/components/transaction-details/__tests__/transaction-details.spec.tsx +++ b/packages/bot-web-ui/src/components/transaction-details/__tests__/transaction-details.spec.tsx @@ -1,17 +1,10 @@ import React from 'react'; -import { isMobile } from '@deriv/shared'; import { mockStore, StoreProvider } from '@deriv/stores'; // eslint-disable-next-line import/no-extraneous-dependencies import { render, screen, waitFor } from '@testing-library/react'; -import RootStore from 'Stores/index'; import { DBotStoreProvider, mockDBotStore } from 'Stores/useDBotStore'; import TransactionDetails from '../transaction-details'; -jest.mock('@deriv/shared', () => ({ - ...jest.requireActual('@deriv/shared'), - isMobile: jest.fn(), -})); - jest.mock('@deriv/bot-skeleton/src/scratch/blockly', () => jest.fn()); jest.mock('@deriv/bot-skeleton/src/scratch/dbot', () => ({ saveRecentWorkspace: jest.fn(), @@ -47,31 +40,37 @@ const mock_ws = { send: jest.fn(), }; describe('TransactionDetails', () => { - let wrapper: ({ children }: { children: JSX.Element }) => JSX.Element, mock_DBot_store: RootStore | undefined; - beforeEach(() => { jest.resetModules(); - const mock_store = mockStore({}); - mock_DBot_store = mockDBotStore(mock_store, mock_ws); + }); - wrapper = ({ children }: { children: JSX.Element }) => ( + const wrapper = (mock_store: ReturnType) => { + const mock_DBot_store = mockDBotStore(mock_store, mock_ws); + + const Component = ({ children }: { children: JSX.Element }) => ( {children} ); - }); + + return Component; + }; it('should render Desktop component based on Desktop', async () => { - (isMobile as jest.Mock).mockReturnValueOnce(false); - await waitFor(() => render(, { wrapper })); + const mock_store = mockStore({}); + await waitFor(() => render(, { wrapper: wrapper(mock_store) })); expect(screen.queryByText('Desktop Details')).toBeInTheDocument(); }); it('should render Mobile component on mobile', async () => { - (isMobile as jest.Mock).mockReturnValueOnce(true); - await waitFor(() => render(, { wrapper })); + const mock_store = mockStore({ + ui: { + is_mobile: true, + }, + }); + await waitFor(() => render(, { wrapper: wrapper(mock_store) })); expect(screen.getByText('Mobile Details')).toBeInTheDocument(); }); }); diff --git a/packages/bot-web-ui/src/components/transaction-details/transaction-details-mobile.tsx b/packages/bot-web-ui/src/components/transaction-details/transaction-details-mobile.tsx index b84c6ffc6bb0..6a14171ad93e 100644 --- a/packages/bot-web-ui/src/components/transaction-details/transaction-details-mobile.tsx +++ b/packages/bot-web-ui/src/components/transaction-details/transaction-details-mobile.tsx @@ -1,12 +1,16 @@ import React from 'react'; + import { MobileFullPageModal } from '@deriv/components'; import { observer, useStore } from '@deriv/stores'; import { localize } from '@deriv/translations'; + import { StatisticsSummary } from 'Components/run-panel/run-panel'; import { transaction_elements } from 'Constants/transactions'; import { useDBotStore } from 'Stores/useDBotStore'; + import MobileTransactionCards from './mobile-transaction-card'; import { TRunPanelStore, TTransactionStore } from './transaction-details.types'; + import './transaction-details-mobile.scss'; const TransactionDetailsMobile = observer(() => { diff --git a/packages/bot-web-ui/src/components/transaction-details/transaction-details.tsx b/packages/bot-web-ui/src/components/transaction-details/transaction-details.tsx index 432682da3fe1..dc809a71f8a4 100644 --- a/packages/bot-web-ui/src/components/transaction-details/transaction-details.tsx +++ b/packages/bot-web-ui/src/components/transaction-details/transaction-details.tsx @@ -1,14 +1,18 @@ import React, { Suspense } from 'react'; import { Loading } from '@deriv/components'; -import { isMobile } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; import TransactionDetailsDesktop from './transaction-details-desktop'; import TransactionDetailsMobile from './transaction-details-mobile'; -export default function TransactionDetails() { - const is_mobile = isMobile(); +export const TransactionDetails = observer(() => { + const { + ui: { is_mobile }, + } = useStore(); return ( }> {is_mobile ? : } ); -} +}); + +export default TransactionDetails; diff --git a/packages/bot-web-ui/src/constants/contract.js b/packages/bot-web-ui/src/constants/contract.js index 0efd631e0205..44a18458b511 100644 --- a/packages/bot-web-ui/src/constants/contract.js +++ b/packages/bot-web-ui/src/constants/contract.js @@ -1,5 +1,6 @@ import { getTotalProfit } from '@deriv/shared'; import { localize } from '@deriv/translations'; + import { getBuyPrice } from 'Utils/multiplier'; export const getSupportedContracts = is_high_low => ({ diff --git a/packages/bot-web-ui/src/stores/dashboard-store.ts b/packages/bot-web-ui/src/stores/dashboard-store.ts index b04a4f1b0fa5..3bf83b1e15e0 100644 --- a/packages/bot-web-ui/src/stores/dashboard-store.ts +++ b/packages/bot-web-ui/src/stores/dashboard-store.ts @@ -1,8 +1,12 @@ import { action, computed, makeObservable, observable, reaction } from 'mobx'; + import { setColors } from '@deriv/bot-skeleton'; import { isMobile } from '@deriv/shared'; + import { clearInjectionDiv } from 'Constants/load-modal'; + import { setTourSettings, tour_type, TTourType } from '../components/dashboard/dbot-tours/utils'; + import RootStore from './root-store'; export interface IDashboardStore { diff --git a/packages/bot-web-ui/src/stores/download-store.js b/packages/bot-web-ui/src/stores/download-store.js index 5c60ffc34ce5..582efa895531 100644 --- a/packages/bot-web-ui/src/stores/download-store.js +++ b/packages/bot-web-ui/src/stores/download-store.js @@ -1,4 +1,5 @@ import { action, makeObservable } from 'mobx'; + import { log_types } from '@deriv/bot-skeleton'; import { localize } from '@deriv/translations'; diff --git a/packages/bot-web-ui/src/stores/journal-store.js b/packages/bot-web-ui/src/stores/journal-store.js index 9522b41f735a..6f4d61b5599e 100644 --- a/packages/bot-web-ui/src/stores/journal-store.js +++ b/packages/bot-web-ui/src/stores/journal-store.js @@ -1,8 +1,10 @@ import { action, computed, makeObservable, observable, reaction, when } from 'mobx'; + import { log_types, message_types } from '@deriv/bot-skeleton'; import { config } from '@deriv/bot-skeleton/src/constants/config'; import { formatDate } from '@deriv/shared'; import { localize } from '@deriv/translations'; + import { isCustomJournalMessage } from '../utils/journal-notifications'; import { getStoredItemsByKey, getStoredItemsByUser, setStoredItemsByKey } from '../utils/session-storage'; import { getSetting, storeSetting } from '../utils/settings'; diff --git a/packages/bot-web-ui/src/stores/summary-card-store.js b/packages/bot-web-ui/src/stores/summary-card-store.js index 95a414834f61..69c28aa0c318 100644 --- a/packages/bot-web-ui/src/stores/summary-card-store.js +++ b/packages/bot-web-ui/src/stores/summary-card-store.js @@ -1,5 +1,7 @@ import { action, computed, makeObservable, observable, reaction } from 'mobx'; + import { getIndicativePrice, isEqualObject, isMultiplierContract, Validator } from '@deriv/shared'; + import { getValidationRules } from 'Constants/contract'; import { contract_stages } from 'Constants/contract-stage'; import { getContractUpdateConfig } from 'Utils/multiplier'; diff --git a/packages/cashier/package.json b/packages/cashier/package.json index 26cbec245694..b470ee74ddc9 100644 --- a/packages/cashier/package.json +++ b/packages/cashier/package.json @@ -38,7 +38,7 @@ "@deriv/api": "^1.0.0", "@deriv/api-types": "^1.0.118", "@deriv/components": "^1.0.0", - "@deriv/deriv-api": "^1.0.11", + "@deriv/deriv-api": "^1.0.13", "@deriv/hooks": "^1.0.0", "@deriv/p2p": "^0.7.3", "@deriv/shared": "^1.0.0", diff --git a/packages/cashier/src/components/crypto-fiat-converter/crypto-fiat-converter.scss b/packages/cashier/src/components/crypto-fiat-converter/crypto-fiat-converter.scss index 9da413fc79a2..305250d49e66 100644 --- a/packages/cashier/src/components/crypto-fiat-converter/crypto-fiat-converter.scss +++ b/packages/cashier/src/components/crypto-fiat-converter/crypto-fiat-converter.scss @@ -2,9 +2,11 @@ display: grid; grid-template-columns: 1fr auto 1fr; grid-gap: 0.8rem; + @include desktop { max-width: 36rem; - margin: 0 auto 2.4rem; + margin: 0 auto; + .dc-icon { margin-top: 1rem; } @@ -12,7 +14,12 @@ .dc-input { margin-bottom: unset; } + + .dc-input__hint { + margin: 0.1rem 0 0 1.3rem; + } } + @include mobile { display: flex; flex-direction: column; @@ -28,6 +35,7 @@ margin: 1.4rem; } } + .input-group { display: flex; @@ -36,6 +44,7 @@ margin-left: -4rem; } } + &__hint { grid-column: 3; margin-left: 0; diff --git a/packages/cashier/src/components/email-verification-empty-state/email-verification-empty-state.scss b/packages/cashier/src/components/email-verification-empty-state/email-verification-empty-state.scss index 775cb2a2689b..565ebcd856b7 100644 --- a/packages/cashier/src/components/email-verification-empty-state/email-verification-empty-state.scss +++ b/packages/cashier/src/components/email-verification-empty-state/email-verification-empty-state.scss @@ -1,8 +1,11 @@ .email-verification-empty-state { display: flex; - flex: 1; flex-direction: column; align-items: center; justify-content: center; - margin: 4rem auto 0; + gap: 4.6rem; + + @include mobile { + gap: 2.4rem; + } } diff --git a/packages/cashier/src/components/email-verification-empty-state/email-verification-empty-state.tsx b/packages/cashier/src/components/email-verification-empty-state/email-verification-empty-state.tsx index e73862177b55..f8c592a3db46 100644 --- a/packages/cashier/src/components/email-verification-empty-state/email-verification-empty-state.tsx +++ b/packages/cashier/src/components/email-verification-empty-state/email-verification-empty-state.tsx @@ -21,7 +21,7 @@ const EmailVerificationEmptyState = ({ type }: TEmailVerificationEmptyStateProps return (
{ - if (!isMobile() || (is_default_route && (is_loading || is_cashier_onboarding))) return localize('Cashier'); + if (!is_mobile || (is_default_route && (is_loading || is_cashier_onboarding))) return localize('Cashier'); return selected_route.getTitle?.(); - }, [is_cashier_onboarding, is_default_route, is_loading, selected_route]); + }, [is_cashier_onboarding, is_default_route, is_loading, selected_route, is_mobile]); const updateActiveTab = useCallback( (path?: string) => { @@ -205,7 +205,7 @@ const Cashier = observer(({ history, location, routes: routes_config }: TCashier }, [history, is_p2p_enabled, is_p2p_enabled_success]); if ( - ((!is_logged_in || isMobile()) && is_logging_in) || + ((!is_logged_in || is_mobile) && is_logging_in) || !is_account_setting_loaded || is_payment_agent_checking || is_p2p_enabled_loading diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-fiat-card/cashier-onboarding-fiat-card.tsx b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-fiat-card/cashier-onboarding-fiat-card.tsx index e42105c9e09c..e243d2ca0f3b 100644 --- a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-fiat-card/cashier-onboarding-fiat-card.tsx +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-fiat-card/cashier-onboarding-fiat-card.tsx @@ -8,7 +8,7 @@ import { CashierOnboardingCard } from '../cashier-onboarding-card'; import { CashierOnboardingIconMarquee } from '../cashier-onboarding-icon-marquee'; const icons: React.ComponentProps['icons'] = [ - 'IcWalletCreditDebit', + 'IcCashierCreditDebit', 'IcCashierInstantBankTransfer', 'IcCashierEwallet', 'IcCashierLocalPaymentMethods', diff --git a/packages/cashier/src/pages/account-transfer/account-transfer-form/account-transfer-form.scss b/packages/cashier/src/pages/account-transfer/account-transfer-form/account-transfer-form.scss index 6301c88c6b02..1e752c01ec31 100644 --- a/packages/cashier/src/pages/account-transfer/account-transfer-form/account-transfer-form.scss +++ b/packages/cashier/src/pages/account-transfer/account-transfer-form/account-transfer-form.scss @@ -6,6 +6,7 @@ &__hint { color: var(--text-general); margin-top: 0.5rem; + &__disabled { color: var(--text-less-prominent); } @@ -23,10 +24,12 @@ margin-right: 0.4rem; } } + &__crypto { &--disabled { pointer-events: none; } + &--percentage-selector { display: flex; flex-direction: column; @@ -34,6 +37,7 @@ margin-bottom: 2.4rem; } } + &__inline-warning-message { margin-bottom: 3rem; @@ -42,6 +46,7 @@ width: 100%; } } + &__drop-down-wrapper { @include mobile { margin-top: 1.2rem; @@ -51,6 +56,7 @@ } } } + &__drop-down { display: inline-block; min-width: 20.3rem; @@ -58,9 +64,11 @@ line-height: 1.43; text-align: left; margin: 0 auto 5.1rem; + @include desktop { max-width: 40rem; } + @include mobile { width: 100%; min-width: auto; @@ -71,21 +79,26 @@ @include desktop { margin: 0 auto 3.3rem; } + @include mobile { margin-bottom: 2.4rem; } } + & .dc-dropdown__display-text { width: 100%; align-items: center; } + & .dc-field--error { padding-top: 0.5rem; + .link { pointer-events: all; } } } + &__notes { @include mobile { padding: 1.6rem 0; @@ -99,6 +112,7 @@ margin-top: 0; } } + &__bullet { background-color: var(--text-general); border-radius: 100%; @@ -113,10 +127,12 @@ margin-bottom: 0.8rem; } } + &__wrapper { .cashier__form-submit { margin-top: 3rem; } + .account-transfer-form__input { min-width: 40rem; height: 6.5rem; @@ -125,12 +141,15 @@ .dc-input__hint { margin: 0.5rem 0 -1.9rem 1.3rem; } + .dc-field--error { margin-top: 0.5rem; } + &-fit-content { width: fit-content; } + @include mobile { width: 100%; min-width: auto; @@ -141,14 +160,17 @@ } } } + &__currency, &__balance { line-height: 1.43; } + &__currency { &-icon { align-self: center; } + &-wrapper { display: flex; flex-direction: column; @@ -158,13 +180,16 @@ margin-right: 8px; } } + &__icon { display: flex; justify-content: center; } + &__balance { margin-left: auto; } + &__form-submit { display: flex; flex-direction: column; @@ -175,6 +200,7 @@ > * { width: auto; } + &--align-end { align-items: flex-end; @@ -187,7 +213,7 @@ &__form-buttons { display: flex; flex-direction: row; - justify-content: end; + justify-content: flex-end; align-items: center; margin-top: 4rem; @@ -213,11 +239,13 @@ &-wrapper { display: flex; height: 100%; + @include mobile { height: initial; } } } + @include desktop { &__form-submit { min-width: 36rem; @@ -226,14 +254,17 @@ } } } + .dc-modal__container_account_transfer { &_switch_modal { transition: none; + .dc-modal { @include mobile() { &-header__close { margin: 2.4rem 1.6rem 0; } + &-body { font-size: 1.4rem; } @@ -242,9 +273,11 @@ &-header__title { padding: 2.4rem 2.4rem 0; } + &-body { padding: 2.4rem; } + &-footer { padding: 0 2.4rem 2.4rem; } diff --git a/packages/cashier/src/pages/payment-agent/payment-agent-list/payment-agent-list.scss b/packages/cashier/src/pages/payment-agent/payment-agent-list/payment-agent-list.scss index 39618819c1bc..221249d604c6 100644 --- a/packages/cashier/src/pages/payment-agent/payment-agent-list/payment-agent-list.scss +++ b/packages/cashier/src/pages/payment-agent/payment-agent-list/payment-agent-list.scss @@ -7,6 +7,9 @@ padding: 0; margin: 0; } + .email-verification-empty-state { + margin-top: 2.4rem; + } &__instructions { @include mobile { display: grid; diff --git a/packages/cashier/src/pages/withdrawal/crypto-withdraw-form/crypto-withdraw-form.tsx b/packages/cashier/src/pages/withdrawal/crypto-withdraw-form/crypto-withdraw-form.tsx index 1c0c6726b34b..cf2b1ca536f3 100644 --- a/packages/cashier/src/pages/withdrawal/crypto-withdraw-form/crypto-withdraw-form.tsx +++ b/packages/cashier/src/pages/withdrawal/crypto-withdraw-form/crypto-withdraw-form.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Button, Icon, Input, Loading, Text } from '@deriv/components'; import { useCurrentAccountDetails } from '@deriv/hooks'; -import { CryptoConfig, getCurrencyName, isMobile } from '@deriv/shared'; +import { CryptoConfig, getCurrencyName } from '@deriv/shared'; import { observer, useStore } from '@deriv/stores'; import { Localize, localize } from '@deriv/translations'; import classNames from 'classnames'; @@ -48,7 +48,8 @@ const Header = ({ currency }: THeaderProps) => { }; const CryptoWithdrawForm = observer(() => { - const { client } = useStore(); + const { client, ui } = useStore(); + const { is_mobile } = ui; const { balance, currency, @@ -101,11 +102,11 @@ const CryptoWithdrawForm = observer(() => { return (
- {!isMobile() &&
} -
- + {!is_mobile &&
} +
+
- {isMobile() &&
} + {is_mobile &&
} ({ + ...jest.requireActual('@deriv/api'), + useRequest: jest.fn(), +})); + +// @ts-expect-error ignore this until find a way to make arguments as partial +const mockUseRequest = useRequest as jest.MockedFunction>; + +const mock_store = mockStore({ + client: { + email: 'john@company.com', + }, + modules: { cashier: { transaction_history: { onMount: jest.fn() } } }, +}); + +describe('WithdrawalVerificationEmail', () => { + test('should render the component', () => { + // @ts-expect-error ignore this until find a way to make arguments as partial + mockUseRequest.mockReturnValue({}); + + render(, { + wrapper: ({ children }) => {children}, + }); + + expect(screen.queryByTestId('dt_empty_state_title')).toBeInTheDocument(); + expect(screen.queryByTestId('dt_empty_state_description')).toBeInTheDocument(); + expect(screen.queryByTestId('dt_empty_state_action')).toHaveTextContent('Send email'); + }); + + test('should show the error component when `error` is provided', () => { + // @ts-expect-error ignore this until find a way to make arguments as partial + mockUseRequest.mockReturnValue({ error: { code: 'CODE', message: 'foo' } }); + + render(, { + wrapper: ({ children }) => {children}, + }); + + expect(screen.getByText('foo')).toBeInTheDocument(); + }); + + test('should show the proper message when email has been sent.', () => { + // @ts-expect-error ignore this until find a way to make arguments as partial + mockUseRequest.mockReturnValue({ mutate: jest.fn() }); + + render(, { + wrapper: ({ children }) => {children}, + }); + + const send_button = screen.getByText('Send email'); + fireEvent.click(send_button); + + expect(screen.getByText("We've sent you an email.")).toBeInTheDocument(); + }); +}); diff --git a/packages/cashier/src/pages/withdrawal/withdrawal-verification-email/withdrawal-verification-email.tsx b/packages/cashier/src/pages/withdrawal/withdrawal-verification-email/withdrawal-verification-email.tsx index 4c27bcd72789..c71074f27156 100644 --- a/packages/cashier/src/pages/withdrawal/withdrawal-verification-email/withdrawal-verification-email.tsx +++ b/packages/cashier/src/pages/withdrawal/withdrawal-verification-email/withdrawal-verification-email.tsx @@ -16,11 +16,11 @@ const WithdrawalVerificationEmail = observer(() => { return ( - +

diff --git a/packages/cfd/package.json b/packages/cfd/package.json index df4d52d2ad50..ad79b8b8496f 100644 --- a/packages/cfd/package.json +++ b/packages/cfd/package.json @@ -85,7 +85,7 @@ "@deriv/account": "^1.0.0", "@deriv/api-types": "^1.0.118", "@deriv/components": "^1.0.0", - "@deriv/deriv-api": "^1.0.11", + "@deriv/deriv-api": "^1.0.13", "@deriv/hooks": "^1.0.0", "@deriv/shared": "^1.0.0", "@deriv/stores": "^1.0.0", diff --git a/packages/cfd/src/Components/cfd-account-card.tsx b/packages/cfd/src/Components/cfd-account-card.tsx index 3687a4086600..2a277397ae86 100644 --- a/packages/cfd/src/Components/cfd-account-card.tsx +++ b/packages/cfd/src/Components/cfd-account-card.tsx @@ -1,29 +1,32 @@ -import classNames from 'classnames'; import React from 'react'; import { CSSTransition } from 'react-transition-group'; -import { Icon, Money, Button, Text, DesktopWrapper, MobileWrapper, Popover } from '@deriv/components'; -import { isMobile, mobileOSDetect, getCFDPlatformLabel, CFD_PLATFORMS } from '@deriv/shared'; -import { localize, Localize } from '@deriv/translations'; -import { CFDAccountCopy } from './cfd-account-copy'; +import classNames from 'classnames'; +import { FormikValues } from 'formik'; + +import { DetailsOfEachMT5Loginid } from '@deriv/api-types'; +import { Button, DesktopWrapper, Icon, MobileWrapper, Money, Popover, Text } from '@deriv/components'; +import { CFD_PLATFORMS, getCFDPlatformLabel, isMobile, mobileOSDetect } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; +import { Localize, localize } from '@deriv/translations'; + import { - getDXTradeWebTerminalLink, - getPlatformDXTradeDownloadLink, getCTraderWebTerminalLink, getDerivEzWebTerminalLink, + getDXTradeWebTerminalLink, + getPlatformDXTradeDownloadLink, } from '../Helpers/constants'; +import { useCfdStore } from '../Stores/Modules/CFD/Helpers/useCfdStores'; + +import { CFDAccountCopy } from './cfd-account-copy'; import { TAccountIconValues, - TSpecBoxProps, - TPasswordBoxProps, - TCFDAccountCardActionProps, TCFDAccountCard, + TCFDAccountCardActionProps, + TPasswordBoxProps, + TSpecBoxProps, TTradingPlatformAccounts, TTradingPlatformAvailableAccount, } from './props.types'; -import { DetailsOfEachMT5Loginid } from '@deriv/api-types'; -import { useStore, observer } from '@deriv/stores'; -import { useCfdStore } from '../Stores/Modules/CFD/Helpers/useCfdStores'; -import { FormikValues } from 'formik'; const account_icons: { [key: string]: TAccountIconValues } = { mt5: { diff --git a/packages/cfd/src/Components/specbox.tsx b/packages/cfd/src/Components/specbox.tsx index a7ffdff76385..7af8302c1ff8 100644 --- a/packages/cfd/src/Components/specbox.tsx +++ b/packages/cfd/src/Components/specbox.tsx @@ -1,6 +1,8 @@ import React from 'react'; import classNames from 'classnames'; + import { Text } from '@deriv/components'; + import { CFDAccountCopy } from './cfd-account-copy'; export type TSpecBoxProps = { diff --git a/packages/cfd/src/Containers/compare-accounts-content.tsx b/packages/cfd/src/Containers/compare-accounts-content.tsx index f0c08058f2b5..5df647c940ef 100644 --- a/packages/cfd/src/Containers/compare-accounts-content.tsx +++ b/packages/cfd/src/Containers/compare-accounts-content.tsx @@ -1,8 +1,9 @@ import React from 'react'; -import { Table, Text, ThemedScrollbars, Div100vhContainer } from '@deriv/components'; -import { localize, Localize } from '@deriv/translations'; -import { isDesktop, CFD_PLATFORMS, isLandingCompanyEnabled } from '@deriv/shared'; + import { LandingCompany } from '@deriv/api-types'; +import { Div100vhContainer, Table, Text, ThemedScrollbars } from '@deriv/components'; +import { CFD_PLATFORMS, isDesktop, isLandingCompanyEnabled } from '@deriv/shared'; +import { Localize, localize } from '@deriv/translations'; type TCFDAttributeDescriberProps = { name: string; diff --git a/packages/cfd/src/Containers/dmt5-trade-modal.tsx b/packages/cfd/src/Containers/dmt5-trade-modal.tsx index 3ede695524ec..af33377a23e8 100644 --- a/packages/cfd/src/Containers/dmt5-trade-modal.tsx +++ b/packages/cfd/src/Containers/dmt5-trade-modal.tsx @@ -1,21 +1,24 @@ import React from 'react'; -import { Text, Icon, Money } from '@deriv/components'; -import { TTradingPlatformAccounts } from '../Components/props.types'; + import { DetailsOfEachMT5Loginid } from '@deriv/api-types'; +import { Icon, Money, Text } from '@deriv/components'; import { CFD_PLATFORMS, - isMobile, getCFDAccountDisplay, + getCFDAccountKey, getCFDPlatformLabel, getPlatformSettings, getUrlBase, - getCFDAccountKey, + isMobile, } from '@deriv/shared'; import { Localize, localize } from '@deriv/translations'; -import SpecBox from '../Components/specbox'; -import PasswordBox from '../Components/passwordbox'; -import { getPlatformMt5DownloadLink, getMT5WebTerminalLink } from '../Helpers/constants'; + import TradingPlatformIcon from '../Assets/svgs/trading-platform'; +import PasswordBox from '../Components/passwordbox'; +import { TTradingPlatformAccounts } from '../Components/props.types'; +import SpecBox from '../Components/specbox'; +import { getMT5WebTerminalLink, getPlatformMt5DownloadLink } from '../Helpers/constants'; + import { TCFDPasswordReset } from './props.types'; type TMT5TradeModalProps = { diff --git a/packages/cfd/src/Stores/Modules/CFD/cfd-store.js b/packages/cfd/src/Stores/Modules/CFD/cfd-store.js index 2a00f2728dc5..20723e8fda12 100644 --- a/packages/cfd/src/Stores/Modules/CFD/cfd-store.js +++ b/packages/cfd/src/Stores/Modules/CFD/cfd-store.js @@ -1,6 +1,6 @@ import { action, computed, observable, reaction, runInAction, makeObservable, override } from 'mobx'; import { getAccountListKey, getAccountTypeFields, CFD_PLATFORMS, WS, Jurisdiction } from '@deriv/shared'; -import BaseStore from 'Stores/base-store'; +import BaseStore from '../../base-store'; import { getDxCompanies, getMtCompanies, getDerivezCompanies } from './Helpers/cfd-config'; export default class CFDStore extends BaseStore { diff --git a/packages/cfd/src/Stores/base-store.js b/packages/cfd/src/Stores/base-store.js index 4e005596209a..9dd1131f5da7 100644 --- a/packages/cfd/src/Stores/base-store.js +++ b/packages/cfd/src/Stores/base-store.js @@ -1,5 +1,6 @@ -import { action, intercept, observable, reaction, toJS, when, makeObservable } from 'mobx'; -import { isProduction, isEmptyObject, Validator } from '@deriv/shared'; +import { action, intercept, makeObservable,observable, reaction, toJS, when } from 'mobx'; + +import { isEmptyObject, isProduction, Validator } from '@deriv/shared'; /** * BaseStore class is the base class for all defined stores in the application. It handles some stuff such as: diff --git a/packages/components/src/components/amount-input/__tests__/amount-input.spec.tsx b/packages/components/src/components/amount-input/__tests__/amount-input.spec.tsx new file mode 100644 index 000000000000..29d8b6a8d27c --- /dev/null +++ b/packages/components/src/components/amount-input/__tests__/amount-input.spec.tsx @@ -0,0 +1,84 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import AmountInput from '../amount-input'; + +describe('', () => { + it('should render with the initial value of "0.00" if {initial_value} is not specified', () => { + render(); + const input = screen.getByTestId('dt_amount-input'); + expect(input).toHaveDisplayValue('0.00'); + }); + + it('should render with the correct initial value if {initial_value} was supplied', () => { + render(); + const input = screen.getByTestId('dt_amount-input'); + expect(input).toHaveDisplayValue('42.00'); + }); + + it('should not change the value on non-numeric and non-"." inputs', () => { + render(); + const input = screen.getByTestId('dt_amount-input'); + userEvent.type(input, 'abcdef!@#$%^&*()_+-={}[];\'"|\\/,.<>'); + expect(input).toHaveDisplayValue('0.00'); + }); + + it('should change the value like an ATM, i.e. from right to left, when entering digits', () => { + render(); + const input = screen.getByTestId('dt_amount-input'); + userEvent.type(input, '1'); + expect(input).toHaveDisplayValue('0.01'); + userEvent.type(input, '2'); + expect(input).toHaveDisplayValue('0.12'); + userEvent.type(input, '3'); + expect(input).toHaveDisplayValue('1.23'); + }); + + it('should add commas for big values', () => { + render(); + const input = screen.getByTestId('dt_amount-input'); + userEvent.type(input, '123456789'); + expect(input).toHaveDisplayValue('1,234,567.89'); + }); + + it('should not remove "0.00" when backspacing', () => { + render(); + const input = screen.getByTestId('dt_amount-input'); + userEvent.type(input, '100'); + expect(input).toHaveDisplayValue('1.00'); + userEvent.clear(input); + expect(input).toHaveDisplayValue('0.00'); + }); + + it('should not accept more than {maxDigits} digits', () => { + render(); + const input = screen.getByTestId('dt_amount-input'); + userEvent.type(input, '1234567890987654321'); + expect(input).toHaveDisplayValue('1,234,567.89'); + }); + + it('should work correctly with explicitly set {decimal_points}', () => { + render(); + const input = screen.getByTestId('dt_amount-input'); + expect(input).toHaveDisplayValue('0.00000'); + userEvent.type(input, '12345678'); + expect(input).toHaveDisplayValue('123.45678'); + }); + + it('should allow pasting numbers and then interpret those correctly', () => { + render(); + const input = screen.getByTestId('dt_amount-input'); + const values = [ + ['123', '123.00'], + ['123.42', '123.42'], + ['123,42', '123.42'], + ['123,000.00', '123,000.00'], + ]; + values.forEach(pair => { + userEvent.clear(input); + userEvent.click(input); + userEvent.paste(input, pair[0]); + expect(input).toHaveDisplayValue(pair[1]); + }); + }); +}); diff --git a/packages/components/src/components/amount-input/amount-input.scss b/packages/components/src/components/amount-input/amount-input.scss new file mode 100644 index 000000000000..0a4b9f2f84a8 --- /dev/null +++ b/packages/components/src/components/amount-input/amount-input.scss @@ -0,0 +1,51 @@ +.amount-input-wrapper { + display: flex; + flex-direction: column; + padding: 0.8rem; + height: auto; +} + +.amount-input-container { + position: relative; + height: 2.8rem; + + .dc-input__field { + color: var(--text-general); + } + + &--error { + .dc-input__field { + color: var(--text-loss-danger); + -webkit-text-fill-color: var(--text-loss-danger); + } + } + + &--disabled { + .dc-input__field { + color: var(--text-disabled-1); + } + } + + @include mobile { + height: 2.4rem; + } +} + +.amount-input { + position: absolute; + border: none; + flex: 1; + margin: 0; + padding: 0; + height: 100%; + width: 100%; + + input { + font-size: var(--text-size-sm); + font-weight: var(--text-weight-bold); + + @include mobile { + font-size: var(--text-size-s); + } + } +} diff --git a/packages/components/src/components/amount-input/amount-input.tsx b/packages/components/src/components/amount-input/amount-input.tsx new file mode 100644 index 000000000000..95f1b21814e3 --- /dev/null +++ b/packages/components/src/components/amount-input/amount-input.tsx @@ -0,0 +1,151 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import classNames from 'classnames'; +import { isMobile } from '@deriv/shared'; +import Input from '../input'; +import Text from '../text'; + +type TAmountInput = { + currency: string; + decimal_places?: number; + disabled?: boolean; + has_error?: boolean; + initial_value?: number; + label?: string; + locale?: Intl.LocalesArgument; + max_digits?: number; + onChange?: (value: number) => void; +}; + +const AmountInput = ({ + currency, + decimal_places = 2, + disabled = false, + has_error, + initial_value = 0, + label, + locale, + max_digits = 8, + onChange, +}: TAmountInput) => { + const [value, setValue] = useState(initial_value); + const [focus, setFocus] = useState(false); + const [is_pasting, setIsPasting] = useState(false); + const [caret_right_offset, setCaretRightOffset] = useState(0); + const [selection, setSelection] = useState<{ + selectionStart: number; + selectionEnd: number; + }>({ selectionStart: 0, selectionEnd: 0 }); + const [target, setTarget] = useState['target']>(); + + const displayNumber = useCallback( + (number: number) => number.toLocaleString(locale, { minimumFractionDigits: decimal_places }), + [decimal_places, locale] + ); + + useEffect(() => { + setValue(initial_value); + }, [initial_value]); + + useEffect(() => { + // update caret position every time the value changes (this happens after onChange) + const updated_caret_position = displayNumber(value).length - caret_right_offset; + target?.setSelectionRange(updated_caret_position, updated_caret_position); + setSelection({ selectionStart: updated_caret_position, selectionEnd: updated_caret_position }); + }, [value, target, displayNumber]); + + const onChangeHandler: React.ComponentProps['onChange'] = e => { + if (!target) setTarget(e.target); + let new_value = value; + if (!is_pasting) { + // handle ATM typing: + // remove all characters that are not digit / point / comma: + const input_value = e.target.value.replace(/[^\d.,]/g, ''); + if (input_value.replace(/[.,]/g, '').replace(/^0+/g, '').length <= max_digits) + new_value = Number(input_value.replace(/[.,]/g, '')) / Math.pow(10, decimal_places); + } else { + // handle pasting: + const selection_length = selection.selectionEnd - selection.selectionStart; + const pasted_string_length = e.target.value.length + selection_length - displayNumber(value).length; + const pasted_string = e.target.value.substring( + selection.selectionStart, + selection.selectionStart + pasted_string_length + ); + // remove all characters that are not digit / point / comma: + const input_value = e.target.value.replace(/[^\d.,]/g, ''); + // understand the value user wants to paste: + const pasted_value = pasted_string + .replace(/[^\d.,]/g, '') // remove all characters that are not digit / point / comma + .replace(/[,.](?=.*[,.])/g, '') // leave only the last point / comma + .replace(',', '.'); // make the last point / comma separator a point + if (pasted_value.replace('.', '')) { + // if the value is a valid non-empty string, handle the two scenarios: + if ((value === 0 && caret_right_offset === 0) || selection_length === displayNumber(value).length) { + // handle pasting when there's nothing entered before it, or it is overridden (intention: reset value): + new_value = Number( + pasted_value.substring( + 0, + pasted_value.includes('.') ? max_digits + 1 : max_digits - decimal_places + ) + ); + } else if (input_value.replace(/[.,]/g, '').replace(/^0+/g, '').length <= max_digits) { + // handle pasting when there's something entered before it and there's space for the pasted value (intention: add to value): + new_value = Number(input_value.replace(/[.,]/g, '')) / Math.pow(10, decimal_places); + } + } + } + setValue(new_value); + setIsPasting(false); + onChange?.(new_value); + }; + + const inputActionHandler: React.ComponentProps['onMouseDown'] & + React.ComponentProps['onMouseUp'] & + React.ComponentProps['onKeyDown'] = e => { + if (e.currentTarget.selectionStart !== null && e.currentTarget.selectionEnd !== null) { + setCaretRightOffset(e.currentTarget.value.length - e.currentTarget.selectionEnd); + setSelection({ + selectionStart: e.currentTarget.selectionStart, + selectionEnd: e.currentTarget.selectionEnd, + }); + } + }; + + return ( +
+ {label} +
+ undefined} + /> + setFocus(true)} + onBlur={() => setFocus(false)} + onChange={onChangeHandler} + onKeyDown={inputActionHandler} + onMouseDown={inputActionHandler} + onMouseUp={inputActionHandler} + onPaste={() => setIsPasting(true)} + type='text' + inputMode='numeric' + value={displayNumber(value)} + /> +
+
+ ); +}; + +export default AmountInput; diff --git a/packages/components/src/components/amount-input/index.ts b/packages/components/src/components/amount-input/index.ts new file mode 100644 index 000000000000..ee447d524403 --- /dev/null +++ b/packages/components/src/components/amount-input/index.ts @@ -0,0 +1,4 @@ +import AmountInput from './amount-input'; +import './amount-input.scss'; + +export default AmountInput; diff --git a/packages/components/src/components/app-linked-with-wallet-icon/app-linked-with-wallet-icon.scss b/packages/components/src/components/app-linked-with-wallet-icon/app-linked-with-wallet-icon.scss new file mode 100644 index 000000000000..afc5625a4566 --- /dev/null +++ b/packages/components/src/components/app-linked-with-wallet-icon/app-linked-with-wallet-icon.scss @@ -0,0 +1,56 @@ +@mixin container-space($parent, $space) { + #{$parent}__top-icon { + top: $space * 1rem; + left: $space * 1rem; + } + + #{$parent}__bottom-icon { + bottom: $space * 1rem; + right: $space * 1rem; + } +} + +.app-icon { + $p-app-icon: &; + + position: relative; + display: flex; + align-items: center; + justify-content: center; + border-radius: $BORDER_RADIUS; + overflow: hidden; + + &__top-icon { + position: absolute; + top: 0; + left: 0; + z-index: 1; + } + + &__bottom-icon { + position: absolute; + bottom: 0; + right: 0; + } + + &--small { + width: 4rem; + height: 2.4rem; + + @include container-space($p-app-icon, 0.1); + } + + &--medium { + width: 6.4rem; + height: 4rem; + + @include container-space($p-app-icon, 0.2); + } + + &--large { + width: 12.8rem; + height: 8rem; + + @include container-space($p-app-icon, 0.4); + } +} diff --git a/packages/components/src/components/app-linked-with-wallet-icon/app-linked-with-wallet-icon.tsx b/packages/components/src/components/app-linked-with-wallet-icon/app-linked-with-wallet-icon.tsx new file mode 100644 index 000000000000..4b8942096d31 --- /dev/null +++ b/packages/components/src/components/app-linked-with-wallet-icon/app-linked-with-wallet-icon.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { WalletIcon } from '../wallet-icon'; +import './app-linked-with-wallet-icon.scss'; + +type TAppIconProps = { + app_icon: string; + gradient_class: string; + has_bg?: boolean; + hide_watermark?: boolean; + size?: 'small' | 'medium' | 'large'; + type: React.ComponentProps['type']; + wallet_icon: string; +}; + +/** + * Use the WalletIcon sizes + */ +const sizes = { + top: { + small: 'small', + medium: 'medium', + large: 'xlarge', + }, + bottom: { + small: 'xsmall', + medium: 'small', + large: 'large', + }, +} as const; + +const AppLinkedWithWalletIcon = ({ + app_icon, + gradient_class, + hide_watermark, + size = 'medium', + type, + wallet_icon, +}: TAppIconProps) => { + if (!app_icon || !wallet_icon || !gradient_class) { + return null; + } + + return ( +
+ {/* Top Icon */} +
+ +
+ + {/* Bottom Icon */} +
+ +
+
+ ); +}; + +export default AppLinkedWithWalletIcon; diff --git a/packages/components/src/components/app-linked-with-wallet-icon/index.ts b/packages/components/src/components/app-linked-with-wallet-icon/index.ts new file mode 100644 index 000000000000..b601135cad4c --- /dev/null +++ b/packages/components/src/components/app-linked-with-wallet-icon/index.ts @@ -0,0 +1,3 @@ +import AppLinkedWithWalletIcon from './app-linked-with-wallet-icon'; + +export { AppLinkedWithWalletIcon }; diff --git a/packages/components/src/components/badge/__tests__/badge.spec.tsx b/packages/components/src/components/badge/__tests__/badge.spec.tsx new file mode 100644 index 000000000000..5d93634423ad --- /dev/null +++ b/packages/components/src/components/badge/__tests__/badge.spec.tsx @@ -0,0 +1,107 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { isMobile } from '@deriv/shared'; +import Badge from '../badge'; + +jest.mock('@deriv/shared', () => ({ + ...jest.requireActual('@deriv/shared'), + isMobile: jest.fn(), +})); + +describe('Badge component', () => { + beforeEach(() => { + (isMobile as jest.Mock).mockReturnValue(true); + }); + + afterEach(() => { + (isMobile as jest.Mock).mockReset(); + }); + + it('Should render properly with default values and label', () => { + render(); + expect(screen.getByText('Badge')).toBeInTheDocument(); + }); + + it('Should render proper medium badge in mobile view', () => { + render(); + const badge = screen.getByText('Badge'); + expect(badge).toHaveStyle('height: 1.2rem'); + expect(badge).toHaveStyle('padding-inline: 0.4rem'); + }); + + it('Should render proper medium badge in desktop view', () => { + (isMobile as jest.Mock).mockReturnValue(false); + + render(); + const badge = screen.getByText('Badge'); + expect(badge).toHaveStyle('height: 1.4rem'); + expect(badge).toHaveStyle('padding-inline: 0.4rem'); + }); + + it('Should render proper large badge in mobile view', () => { + render(); + const badge = screen.getByText('Badge'); + expect(badge).toHaveStyle('height: 1.6rem'); + expect(badge).toHaveStyle('padding-inline: 0.8rem'); + }); + + it('Should render proper large badge in desktop view', () => { + (isMobile as jest.Mock).mockReturnValue(false); + + render(); + const badge = screen.getByText('Badge'); + expect(badge).toHaveStyle('height: 2.2rem'); + expect(badge).toHaveStyle('padding-inline: 0.8rem'); + }); + + it('Should render badge with normal font weight', () => { + render(); + const badge = screen.getByText('Badge'); + expect(badge).toHaveStyle('--text-weight: var(--text-weight-normal)'); + }); + + it('Should render badge with normal font weight', () => { + render(); + const badge = screen.getByText('Badge'); + expect(badge).toHaveStyle('--text-weight: var(--text-weight-bold)'); + }); + + it('Should render badge with proper font-size in mobile view', () => { + render(); + const badge = screen.getByText('Badge'); + expect(badge).toHaveStyle('--text-size: var(--text-size-xxxxs)'); + }); + + it('Should render badge with proper font-size in desktop view', () => { + (isMobile as jest.Mock).mockReturnValue(false); + + render(); + const badge = screen.getByText('Badge'); + expect(badge).toHaveStyle('--text-size: var(--text-size-xxxs)'); + }); + + it('Should render badge with proper amount of rounded corners', () => { + render(); + const badge = screen.getByText('Badge'); + expect(badge).toHaveClass('dc-badge--two-rounded'); + }); + + it('Should render badge with bordered type', () => { + render(); + const badge = screen.getByText('Badge'); + expect(badge).toHaveClass('dc-badge--bordered'); + }); + + it('Should render badge with contained type', () => { + render(); + const badge = screen.getByText('Badge'); + expect(badge).toHaveClass('dc-badge--contained'); + }); + + it('Should render badge with contained type and red background', () => { + render(); + const badge = screen.getByText('Badge'); + expect(badge).toHaveClass('dc-badge--contained dc-badge--red'); + expect(badge).toHaveClass('dc-badge--contained dc-badge--red'); + }); +}); diff --git a/packages/components/src/components/badge/badge.scss b/packages/components/src/components/badge/badge.scss new file mode 100644 index 000000000000..70392d4057ba --- /dev/null +++ b/packages/components/src/components/badge/badge.scss @@ -0,0 +1,40 @@ +.dc-badge { + display: inline-flex; + justify-content: center; + align-items: center; + color: $color-white; + + &--contained { + border: none; + } + + &--bordered { + border: 1px solid var(--text-prominent); + color: var(--text-prominent); + } + + &--blue { + background-color: $color-blue; + } + + &--orange { + background-color: $color-orange; + } + + &--red { + background-color: $color-red; + } + + &--gray { + background-color: $color-grey-5; + } + + &--full-rounded { + border-radius: $BORDER_RADIUS/2; + } + + &--two-rounded { + border-bottom-left-radius: $BORDER_RADIUS * 2; + border-top-right-radius: $BORDER_RADIUS * 2; + } +} diff --git a/packages/components/src/components/badge/badge.tsx b/packages/components/src/components/badge/badge.tsx new file mode 100644 index 000000000000..b588b1a3fd47 --- /dev/null +++ b/packages/components/src/components/badge/badge.tsx @@ -0,0 +1,96 @@ +import React from 'react'; +import classNames from 'classnames'; +import { isMobile } from '@deriv/shared'; +import Text from '../text'; + +type TBadgeSize = 'medium' | 'large'; +type TWeight = 'bold' | 'normal'; +type TBackgroundColor = 'blue' | 'orange' | 'red' | 'gray'; +type TRoundedCorners = 4 | 2; + +interface Badge extends React.HTMLAttributes { + className?: string; + custom_color?: string; + label: string; + rounded_corners?: TRoundedCorners; + size?: TBadgeSize; + weight?: TWeight; +} + +interface BadgeContained extends Badge { + type: 'contained'; + background_color: TBackgroundColor; +} + +interface BadgeBordered extends Badge { + type: 'bordered'; +} + +type BadgeProps = BadgeContained | BadgeBordered; + +const Badge = (props: BadgeProps) => { + const { + className, + custom_color, + label, + rounded_corners = 4, + size = 'medium', + type, + weight = 'bold', + ...rest + } = props; + + const is_contained = type === 'contained'; + const is_bordered = type === 'bordered'; + + const badge_height = React.useMemo(() => { + switch (size) { + case 'large': + return { + height: isMobile() ? '1.6rem' : '2.2rem', + paddingInline: '0.8rem', + }; + case 'medium': + default: + return { + height: isMobile() ? '1.2rem' : '1.4rem', + paddingInline: '0.4rem', + }; + } + }, [size]); + + const badge_class_names = React.useMemo(() => { + return classNames( + 'dc-badge', + { + 'dc-badge--contained': is_contained, + 'dc-badge--bordered': is_bordered, + 'dc-badge--blue': is_contained && props.background_color === 'blue', + 'dc-badge--orange': is_contained && props.background_color === 'orange', + 'dc-badge--red': is_contained && props.background_color === 'red', + 'dc-badge--gray': is_contained && props.background_color === 'gray', + 'dc-badge--full-rounded': rounded_corners === 4, + 'dc-badge--two-rounded': rounded_corners === 2, + }, + className + ); + }, [className, is_bordered, is_contained, is_contained && props.background_color, rounded_corners]); + + if (!label) return null; + + return ( + + {label} + + ); +}; + +export default Badge; diff --git a/packages/components/src/components/badge/index.ts b/packages/components/src/components/badge/index.ts new file mode 100644 index 000000000000..df4fc62f3ce8 --- /dev/null +++ b/packages/components/src/components/badge/index.ts @@ -0,0 +1,4 @@ +import Badge from './badge'; +import './badge.scss'; + +export default Badge; diff --git a/packages/components/src/components/contract-card/contract-card-items/toggle-card-dialog.tsx b/packages/components/src/components/contract-card/contract-card-items/toggle-card-dialog.tsx index ce87b3690213..1ef02a0e921f 100644 --- a/packages/components/src/components/contract-card/contract-card-items/toggle-card-dialog.tsx +++ b/packages/components/src/components/contract-card/contract-card-items/toggle-card-dialog.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { isDesktop, isMobile } from '@deriv/shared'; import ContractCardDialog from './contract-card-dialog'; import ContractUpdateForm from './contract-update-form'; +import PopoverMessageCheckbox from '../../popover-message-checkbox'; import Icon from '../../icon'; import DesktopWrapper from '../../desktop-wrapper'; import MobileDialog from '../../mobile-dialog'; @@ -56,7 +57,7 @@ const ToggleCardDialog = ({ const [is_visible, setIsVisible] = React.useState(false); const [top, setTop] = React.useState(0); const [left, setLeft] = React.useState(0); - const is_do_not_show_selected = !should_show_cancellation_warning; + const [should_hide_selected, setShouldHideSelected] = React.useState(!should_show_cancellation_warning); const toggle_ref = React.useRef(null); const dialog_ref = React.useRef(null); @@ -93,11 +94,15 @@ const ToggleCardDialog = ({ }; const onPopoverClose = () => { - if (is_do_not_show_selected) { + if (should_hide_selected) { toggleCancellationWarning?.(); } }; + const onPopoverCheckboxChange = () => { + setShouldHideSelected(!should_hide_selected); + }; + const toggleDialog = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); @@ -133,7 +138,15 @@ const ToggleCardDialog = ({ is_bubble_hover_enabled margin={2} zIndex='2' - message={getCardLabels().TAKE_PROFIT_LOSS_NOT_AVAILABLE} + message={ + + } onBubbleClose={onPopoverClose} >
{edit_icon}
diff --git a/packages/components/src/components/date-picker/index.ts b/packages/components/src/components/date-picker/index.ts index adfc2a303a54..e1fd3c52ef90 100644 --- a/packages/components/src/components/date-picker/index.ts +++ b/packages/components/src/components/date-picker/index.ts @@ -1,3 +1,4 @@ import DatePicker from './date-picker'; import './date-picker.scss'; + export default DatePicker; diff --git a/packages/components/src/components/div100vh-container/div100vh-container.tsx b/packages/components/src/components/div100vh-container/div100vh-container.tsx index 619466ffa4ca..426772e5a777 100644 --- a/packages/components/src/components/div100vh-container/div100vh-container.tsx +++ b/packages/components/src/components/div100vh-container/div100vh-container.tsx @@ -15,14 +15,14 @@ import Div100vh from 'react-div-100vh'; /* To bypass usage of component altogether, use is_bypassed */ type TDiv100vhContainer = { - id?: string; height_offset?: string; is_bypassed?: boolean; is_disabled?: boolean; max_height_offset?: string; className?: string; max_autoheight_offset?: string; -}; + id?: string; +} & React.ComponentProps<'div'>; const Div100vhContainer = ({ children, diff --git a/packages/components/src/components/empty-state/empty-state.scss b/packages/components/src/components/empty-state/empty-state.scss index c8d32e5fc7d1..36dc95f1aede 100644 --- a/packages/components/src/components/empty-state/empty-state.scss +++ b/packages/components/src/components/empty-state/empty-state.scss @@ -1,8 +1,19 @@ .empty-state { width: 100%; display: flex; - flex: 1; flex-direction: column; align-items: center; - gap: 1.4rem; + gap: 2.4rem; + + &__content { + display: flex; + flex-direction: column; + gap: 0.8rem; + } + + &__action .dc-btn__text { + @include mobile { + font-size: var(--text-size-xxs); + } + } } diff --git a/packages/components/src/components/empty-state/empty-state.tsx b/packages/components/src/components/empty-state/empty-state.tsx index c56b8a6ccc3e..21f9cf1bbca6 100644 --- a/packages/components/src/components/empty-state/empty-state.tsx +++ b/packages/components/src/components/empty-state/empty-state.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { isMobile } from '@deriv/shared'; import Button from '../button'; import Icon from '../icon'; import Text from '../text'; @@ -20,18 +21,18 @@ export type TProps = { const EmptyState: React.FC = ({ icon, title, description, action }) => (
{icon && } -
- {title && ( - - {title} - - )} - {description && ( - - {description} - - )} -
+
+ {title && ( + + {title} + + )} + {description && ( + + {description} + + )} +
{action && ( +
+ )} +
+ ); +}; + +export default AlertMessage; diff --git a/packages/components/src/components/message-list/index.ts b/packages/components/src/components/message-list/index.ts new file mode 100644 index 000000000000..bd4c491a159a --- /dev/null +++ b/packages/components/src/components/message-list/index.ts @@ -0,0 +1 @@ +export { default as MessageList } from './message-list'; diff --git a/packages/components/src/components/message-list/message-list.scss b/packages/components/src/components/message-list/message-list.scss new file mode 100644 index 000000000000..f5954f4ba4cd --- /dev/null +++ b/packages/components/src/components/message-list/message-list.scss @@ -0,0 +1,53 @@ +.message-list { + width: 100%; + max-width: 63.4rem; + margin: 0 auto; + min-height: 3.2rem; + + .alert-message { + display: flex; + flex-direction: row; + column-gap: 0.8rem; + justify-content: flex-start; + min-height: 3.2rem; + + @include mobile { + margin: 0; + } + + &__icon-container { + position: relative; + + .icon-container { + &__line { + border: 1px solid var(--border-normal); + position: absolute; + height: 100%; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + + &__icon { + display: flex; + position: relative; + margin-top: 0.8rem; + } + } + } + + &__message-container, + &__button-container { + display: flex; + align-items: center; + } + + &__message-container { + padding: 0.7rem 0; + } + + &__button-container { + margin-left: auto; + } + } +} diff --git a/packages/components/src/components/message-list/message-list.tsx b/packages/components/src/components/message-list/message-list.tsx new file mode 100644 index 000000000000..705fdba43438 --- /dev/null +++ b/packages/components/src/components/message-list/message-list.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import AlertMessage from './alert-message'; +import './message-list.scss'; + +type TMessageListProps = { list: (React.ComponentProps & { key: string })[] }; + +const animations = { + initial: { + height: 0, + opacity: 0, + }, + animate: { + height: 'auto', + opacity: 1, + transition: { + height: { + duration: 0.2, + }, + opacity: { + duration: 0.15, + delay: 0.05, + }, + }, + }, + exit: { + height: 0, + opacity: 0, + transition: { + height: { + duration: 0.2, + }, + opacity: { duration: 0.1 }, + }, + }, +}; + +const MessageList = ({ list }: TMessageListProps) => { + return ( +
+ + {list.map(item => { + return ( + + + + ); + })} + +
+ ); +}; + +export default MessageList; diff --git a/packages/components/src/components/mobile-dialog/mobile-dialog.tsx b/packages/components/src/components/mobile-dialog/mobile-dialog.tsx index 13355bb5d346..31189672c854 100644 --- a/packages/components/src/components/mobile-dialog/mobile-dialog.tsx +++ b/packages/components/src/components/mobile-dialog/mobile-dialog.tsx @@ -8,31 +8,31 @@ import Div100vhContainer from '../div100vh-container'; type TMobileDialog = { content_height_offset?: string; - onClose: React.MouseEventHandler; + footer?: React.ReactNode; has_content_scroll?: boolean; + has_close_icon?: boolean; + has_full_height?: boolean; + header_classname?: string; + onClose: React.MouseEventHandler; portal_element_id: string; renderTitle?: () => string; title?: React.ReactNode; visible?: boolean; wrapper_classname?: string; - header_classname?: string; - has_full_height?: boolean; - footer?: React.ReactNode; - has_close_icon?: boolean; }; const MobileDialog = (props: React.PropsWithChildren) => { const { - title, - visible, children, + footer, + has_close_icon = true, has_full_height, + header_classname, portal_element_id, renderTitle, + title, + visible, wrapper_classname, - footer, - has_close_icon = true, - header_classname, } = props; const footer_ref = React.useRef(null); diff --git a/packages/components/src/components/modal/modal.tsx b/packages/components/src/components/modal/modal.tsx index 526640ed61bd..9e36f9996fc3 100644 --- a/packages/components/src/components/modal/modal.tsx +++ b/packages/components/src/components/modal/modal.tsx @@ -209,6 +209,7 @@ type TModal = TModalElement & { exit_classname?: string; onEntered?: () => void; onExited?: () => void; + transition_timeout?: React.ComponentProps['timeout']; }; const Modal = ({ @@ -238,13 +239,14 @@ const Modal = ({ should_header_stick_body = true, small, title, + transition_timeout, toggleModal, width, }: React.PropsWithChildren) => ( ; setStep: React.Dispatch>; + is_transition?: boolean; }; -const ProgressBarTracker = ({ step, steps_list, setStep }: TProgressBarTracker) => ( +const ProgressBarTracker = ({ step, steps_list, setStep, is_transition = false }: TProgressBarTracker) => (
{steps_list.map((step_item, index) => { const active = step === index + 1; @@ -15,7 +17,11 @@ const ProgressBarTracker = ({ step, steps_list, setStep }: TProgressBarTracker)
setStep(index + 1)} - className={active ? 'dc-progress-bar-tracker-rectangle' : 'dc-progress-bar-tracker-circle'} + className={classNames({ + 'dc-progress-bar-tracker-rectangle': active, + 'dc-progress-bar-tracker-circle': !active, + 'dc-progress-bar-tracker-transition': is_transition, + })} /> ); })} diff --git a/packages/components/src/components/remaining-time/remaining-time.tsx b/packages/components/src/components/remaining-time/remaining-time.tsx index e67a4b3d7047..047336f831dd 100644 --- a/packages/components/src/components/remaining-time/remaining-time.tsx +++ b/packages/components/src/components/remaining-time/remaining-time.tsx @@ -1,6 +1,8 @@ import React from 'react'; -import { formatDuration, getDiffDuration } from '@deriv/shared'; import moment from 'moment'; + +import { formatDuration, getDiffDuration } from '@deriv/shared'; + import { TGetCardLables } from '../types'; type TRemainingTimeProps = { diff --git a/packages/components/src/components/wallet-card/index.ts b/packages/components/src/components/wallet-card/index.ts new file mode 100644 index 000000000000..c6b23d764216 --- /dev/null +++ b/packages/components/src/components/wallet-card/index.ts @@ -0,0 +1,3 @@ +import WalletCard from './wallet-card'; + +export { WalletCard }; diff --git a/packages/components/src/components/wallet-card/wallet-card.scss b/packages/components/src/components/wallet-card/wallet-card.scss new file mode 100644 index 000000000000..04ff96692983 --- /dev/null +++ b/packages/components/src/components/wallet-card/wallet-card.scss @@ -0,0 +1,147 @@ +.wallet-card { + position: relative; + + &__container { + border-radius: inherit; + width: 100%; + height: 100%; + + &--active { + border: 2px solid var(--text-red); + } + + &--small { + display: flex; + align-items: center; + justify-content: center; + } + + &-fade { + position: absolute; + inset: 0; + opacity: 0; + background: var(--wallets-card-active-gradient-background); + border-radius: inherit; + + &--active { + opacity: 1; + } + } + + &:hover & { + &-fade { + opacity: 1; + &--disabled, + &--faded, + &--added { + opacity: 0; + } + } + } + } + + &__shine { + position: absolute; + inset: 0; + clip-path: polygon(40% 10%, 104% -6.94%, 92.5% 100%, 28% 100%); + mix-blend-mode: overlay; + opacity: 0.16; + border-top-right-radius: $BORDER_RADIUS_2; + background-color: $color-white; + } + + &__content { + position: relative; + height: 100%; + width: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + padding: 1.6rem; + + &--medium { + @include mobile { + padding: 0.8rem; + } + } + } + + &--small { + width: 6.4rem; + height: 4rem; + border-radius: $BORDER_RADIUS; + } + + &--medium { + width: 20rem; + height: 12rem; + border-radius: $BORDER_RADIUS_2; + + @include mobile { + width: 16rem; + height: 9.6rem; + } + } + + &--large { + width: 24rem; + height: 14.4rem; + border-radius: $BORDER_RADIUS_2; + + @include mobile { + width: 21.6rem; + height: 12.8rem; + } + } + + &__active-icon { + position: absolute; + inset: 0; + display: flex; + justify-content: center; + align-items: center; + + &--small { + position: absolute; + top: 0; + left: 100%; + transform: translate(-1.2rem, -0.4rem); + width: unset; + height: unset; + } + } + + &__top-wrapper { + display: flex; + flex-direction: row; + justify-content: space-between; + width: 100%; + } + + &__bottom-wrapper { + display: flex; + flex-direction: column; + justify-content: flex-start; + width: 100%; + } + + &__wallet-button { + background: $color-white; + &-text { + color: $color-black-1; + } + + &--added { + opacity: 0.32; + } + } + + &--disabled { + opacity: 0.32; + } + + &--faded { + opacity: 0.72; + } +} diff --git a/packages/components/src/components/wallet-card/wallet-card.tsx b/packages/components/src/components/wallet-card/wallet-card.tsx new file mode 100644 index 000000000000..7e6c44d154f9 --- /dev/null +++ b/packages/components/src/components/wallet-card/wallet-card.tsx @@ -0,0 +1,113 @@ +import React from 'react'; +import classNames from 'classnames'; +import { localize } from '@deriv/translations'; +import Badge from '../badge'; +import Button from '../button'; +import Icon from '../icon'; +import Text from '../text'; +import { isMobile } from '@deriv/shared'; +import { WalletIcon } from '../wallet-icon'; +import './wallet-card.scss'; + +type TWalletCardProps = { + // TODO: This type should be updated when the response is ready + wallet: { + balance: string; + currency: string; + icon: string; + icon_type: 'fiat' | 'crypto' | 'app' | 'demo'; + jurisdiction_title: string; + name: string; + gradient_class: string; + }; + size?: 'small' | 'medium' | 'large'; + state?: 'active' | 'add' | 'added' | 'default' | 'disabled' | 'faded'; +}; + +type IconComponentProps = { + size: TWalletCardProps['size']; + icon_type: TWalletCardProps['wallet']['icon_type']; + icon: TWalletCardProps['wallet']['icon']; +}; + +const IconComponent = ({ size, icon_type, icon }: IconComponentProps) => { + let icon_size: React.ComponentProps['size'] = 'large'; + if (size === 'small') icon_size = 'medium'; + if (size === 'medium') icon_size = isMobile() && icon_type === 'crypto' ? 'medium' : 'large'; + return ; +}; + +const WalletCard: React.FC> = ({ + wallet, + size = 'medium', + state = 'default', +}) => ( +
+
+ {size !== 'small' &&
} +
+ {size === 'small' && } + {size !== 'small' && ( +
+
+ + {wallet.jurisdiction_title === 'virtual' ? ( + + ) : ( + + )} +
+ +
+ {state !== 'add' && state !== 'added' ? ( + + + {wallet.name} + + + {wallet.balance} {wallet.currency} + + + ) : ( +
+
+ )} +
+ {state === 'active' && ( +
+ +
+ )} +
+); +export default WalletCard; diff --git a/packages/components/src/components/wallet-icon/index.ts b/packages/components/src/components/wallet-icon/index.ts new file mode 100644 index 000000000000..200597c2f437 --- /dev/null +++ b/packages/components/src/components/wallet-icon/index.ts @@ -0,0 +1,3 @@ +import WalletIcon from './wallet-icon'; + +export { WalletIcon }; diff --git a/packages/components/src/components/wallet-icon/wallet-icon.scss b/packages/components/src/components/wallet-icon/wallet-icon.scss new file mode 100644 index 000000000000..0044ee04e5b8 --- /dev/null +++ b/packages/components/src/components/wallet-icon/wallet-icon.scss @@ -0,0 +1,39 @@ +.wallet-icon { + position: relative; + display: flex; + align-items: center; + justify-content: center; + border-radius: $BORDER_RADIUS; + overflow: hidden; + + &__default-bg { + // Default Background + background: radial-gradient(100% 4130.74% at 0% 100%, rgba(244, 67, 54, 0.24) 0%, rgba(40, 57, 145, 0.48) 100%) + #ffffff; + } + + &--xsmall { + width: 2.4rem; + height: 1.4rem; + } + + &--small { + width: 4rem; + height: 2.4rem; + } + + &--medium { + width: 6.4rem; + height: 4rem; + } + + &--large { + width: 8.4rem; + height: 5.2rem; + } + + &--xlarge { + width: 12.8rem; + height: 8rem; + } +} diff --git a/packages/components/src/components/wallet-icon/wallet-icon.tsx b/packages/components/src/components/wallet-icon/wallet-icon.tsx new file mode 100644 index 000000000000..37616338e99c --- /dev/null +++ b/packages/components/src/components/wallet-icon/wallet-icon.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import classNames from 'classnames'; +import Icon from '../icon'; +import './wallet-icon.scss'; + +type TWalletIconSizes = keyof typeof sizes['square' | 'box']; + +type TWalletIconProps = { + gradient_class?: string; + icon: string; + size?: TWalletIconSizes; + type?: 'demo' | 'fiat' | 'crypto' | 'app'; + has_bg?: boolean; + hide_watermark?: boolean; +}; + +const sizes = { + square: { + xsmall: 12, + small: 16, + medium: 24, + large: 32, + xlarge: 48, + xxlarge: 64, + }, + // The crypto and demo sizes are the same + box: { + xsmall: { + width: 20, + height: 12, + }, + small: { + width: 32, + height: 20, + }, + medium: { + width: 48, + height: 30, + }, + large: { + width: 64, + height: 40, + }, + xlarge: { + width: 96, + height: 60, + }, + xxlarge: { + width: 120, + height: 80, + }, + }, +} as const; + +const WalletIcon = ({ gradient_class, icon, size = 'medium', type, has_bg, hide_watermark }: TWalletIconProps) => { + if (!icon) { + return null; + } + + return ( +
+ {(type === 'fiat' || type === 'app') && } + {(type === 'demo' || type === 'crypto') && ( + + )} +
+ ); +}; + +export default WalletIcon; diff --git a/packages/components/src/hooks/use-hover.ts b/packages/components/src/hooks/use-hover.ts index c56fa0c4e94b..9583eacf6d99 100644 --- a/packages/components/src/hooks/use-hover.ts +++ b/packages/components/src/hooks/use-hover.ts @@ -1,4 +1,5 @@ import React, { RefObject } from 'react'; + import { isMobileOs } from '@deriv/shared'; export const useHover = ( diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index 56937ff875a7..d531d9c211af 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -10,11 +10,13 @@ // export default { Label, Button }; export { default as Accordion } from './components/accordion'; +export { default as AmountInput } from './components/amount-input'; export { default as AutoHeightWrapper } from './components/auto-height-wrapper'; export { default as Autocomplete } from './components/autocomplete'; export { default as AutoSizer } from './components/autosizer'; export { default as Button } from './components/button'; export { default as SpanButton } from './components/span-button'; +export { default as Badge } from './components/badge'; export { default as ButtonLink } from './components/button-link'; export { default as ButtonToggle } from './components/button-toggle'; export { default as Calendar } from './components/calendar'; @@ -51,6 +53,8 @@ export { default as FormProgress } from './components/form-progress'; export { default as FormSubmitButton } from './components/form-submit-button'; export { default as FormSubmitErrorMessage } from './components/form-submit-error-message'; export { default as FormCancelButton } from './components/form-cancel-button'; +export * from './components/gradient-background'; +export * from './components/gradient-background-two-point'; export { default as HintBox } from './components/hint-box'; export { default as HorizontalSwipe } from './components/horizontal-swipe'; export { default as Icon } from './components/icon'; @@ -105,6 +109,7 @@ export { default as Text } from './components/text'; export { default as Toast } from './components/toast'; export { default as ThemedScrollbars } from './components/themed-scrollbars'; export { default as ToggleSwitch } from './components/toggle-switch'; +export * from './components/message-list'; export { default as TickPicker } from './components/tick-picker'; export { default as TickProgress } from './components/tick-progress'; export { default as Timeline } from './components/timeline'; @@ -113,5 +118,8 @@ export { default as UILoader } from './components/u-i-loader'; export { default as UnhandledErrorModal } from './components/unhandled-error-modal'; export { default as VerticalTab } from './components/vertical-tab'; export { default as Wizard } from './components/wizard'; +export * from './components/wallet-card'; +export * from './components/wallet-icon'; +export * from './components/app-linked-with-wallet-icon'; export * from './hooks'; export * from './components/types'; diff --git a/packages/components/stories/icon/icons.js b/packages/components/stories/icon/icons.js index a6e90972c513..c8dfc15c055a 100644 --- a/packages/components/stories/icon/icons.js +++ b/packages/components/stories/icon/icons.js @@ -30,6 +30,8 @@ export const icons = 'IcAppstoreOptionTradeType', 'IcAppstoreSkeletonCardBorder', 'IcAppstoreSuccess', + 'IcAppstoreTickWhite', + 'IcAppstoreTick', 'IcAppstoreTradersHubHome', 'IcAppstoreTradingHubBeta', 'IcAppstoreTradingHubOnboardingDark', @@ -54,6 +56,7 @@ export const icons = 'IcAppstoreWalletUsdLight', 'IcAppstoreWalletUsdcLight', 'IcAppstoreWalletUsdtLight', + 'IcAppstoreWalletsLink', 'IcAppstoreWarning', ], 'brand': [ @@ -111,6 +114,8 @@ export const icons = 'IcCashierClipboard', 'IcCashierCommissionDeposit', 'IcCashierCommissionWithdrawal', + 'IcCashierCreditDebitDark', + 'IcCashierCreditDebitLight', 'IcCashierCryptoDark', 'IcCashierCryptoLight', 'IcCashierDaiDark', @@ -363,6 +368,8 @@ export const icons = 'IcDbotUserGuide', 'IcDbotViewDetail', 'IcDelete', + 'IcDemoResetBalanceDone', + 'IcDemoResetBalance', 'IcDemo', 'IcDerivOutline', 'IcDeriv', @@ -615,6 +622,8 @@ export const icons = 'IcWindowsLogo', 'IcWindows', 'IcWip', + 'IcWithdrawRequestVerificationSent', + 'IcWithdrawRequestVerification', 'IcZingpay', 'IcZoomIn', 'IcZoomOut', @@ -762,7 +771,11 @@ export const icons = 'IcRebrandingDxtradeDashboard', 'IcRebrandingDxtradeWordmark', 'IcRebrandingDxtrade', + 'IcRebrandingMt5Cfds', + 'IcRebrandingMt5DerivedDashboard', + 'IcRebrandingMt5FinancialDashboard', 'IcRebrandingMt5Logo', + 'IcRebrandingMt5SwapFree', 'IcRebrandingSmarttraderDashboard', 'IcRebrandingSmarttrader', ], @@ -1015,39 +1028,34 @@ export const icons = 'IcUnderlyingWLDXAU', ], 'wallet': [ - 'IcWalletClearFunds', - 'IcWalletCreditDebitDark', - 'IcWalletCreditDebitLight', - 'IcWalletCredit', - 'IcWalletCryptoLight', + 'IcWalletBitcoinDark', + 'IcWalletBitcoinLight', + 'IcWalletCurrencyAud', + 'IcWalletCurrencyEur', + 'IcWalletCurrencyGbp', + 'IcWalletCurrencyUsd', 'IcWalletDerivApps', - 'IcWalletDeriv', - 'IcWalletDp2pLight', - 'IcWalletExplore', - 'IcWalletFasapayDark', - 'IcWalletFasapayLight', - 'IcWalletJetonDark', - 'IcWalletJetonLight', - 'IcWalletMarkets', + 'IcWalletDerivDemoDark', + 'IcWalletDerivDemoLight', + 'IcWalletDerivP2pDark', + 'IcWalletDerivP2pLight', + 'IcWalletErrorMessageWithCross', + 'IcWalletEthereumDark', + 'IcWalletEthereumLight', + 'IcWalletInfoMessageWithThreeDots', + 'IcWalletLiteCoinDark', + 'IcWalletLiteCoinLight', + 'IcWalletModalTetherDark', + 'IcWalletModalTetherLight', 'IcWalletMyApps', - 'IcWalletNetellerDark', - 'IcWalletNetellerLight', - 'IcWalletNeteller', + 'IcWalletOptionsDark', + 'IcWalletOptionsLight', + 'IcWalletPaymentAgentDark', 'IcWalletPaymentAgentLight', - 'IcWalletPaytrustDark', - 'IcWalletPaytrustLight', - 'IcWalletPlatforms', - 'IcWalletSkrillDark', - 'IcWalletSkrillLight', - 'IcWalletSticpayDark', - 'IcWalletSticpayLight', - 'IcWalletSticpay', - 'IcWalletTradeTypes', - 'IcWalletTransactionsEmpty', - 'IcWalletWallets', - 'IcWalletWebmoneyLight', - 'IcWalletWebmoney', - 'IcWalletZingpayDark', - 'IcWalletZingpayLight', + 'IcWalletSuccessMessage', + 'IcWalletTetherDark', + 'IcWalletTetherLight', + 'IcWalletUsdCoinDark', + 'IcWalletUsdCoinLight', ], } diff --git a/packages/core/package.json b/packages/core/package.json index 865185cf0b33..c42b72233f8d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -95,7 +95,7 @@ "@deriv/cashier": "^1.0.0", "@deriv/cfd": "^1.0.0", "@deriv/components": "^1.0.0", - "@deriv/deriv-api": "^1.0.11", + "@deriv/deriv-api": "^1.0.13", "@deriv/deriv-charts": "1.4.0", "@deriv/hooks": "^1.0.0", "@deriv/p2p": "^0.7.3", diff --git a/packages/core/src/App/Components/Layout/Header/toggle-menu-drawer.jsx b/packages/core/src/App/Components/Layout/Header/toggle-menu-drawer.jsx index b0b591bfd6c8..81e796d2634f 100644 --- a/packages/core/src/App/Components/Layout/Header/toggle-menu-drawer.jsx +++ b/packages/core/src/App/Components/Layout/Header/toggle-menu-drawer.jsx @@ -402,7 +402,7 @@ const ToggleMenuDrawer = observer(({ platform_config }) => { )} - + diff --git a/packages/core/src/App/Components/dev-tools/index.tsx b/packages/core/src/App/Components/dev-tools/index.tsx new file mode 100644 index 000000000000..2492cf1cb312 --- /dev/null +++ b/packages/core/src/App/Components/dev-tools/index.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import MockDialog from './mock-dialog'; +import { useLocalStorageData } from '@deriv/hooks'; + +const DevTools = () => { + const [show_mockserver_panel, setShowMockServerPanel] = useLocalStorageData( + 'show_mockserver_panel', + false + ); + + React.useEffect(() => { + const handleShortcutKey = (event: globalThis.KeyboardEvent) => { + if (event.ctrlKey && event.key === '0') { + setShowMockServerPanel(prev => !prev); + } + }; + + window.addEventListener('keydown', handleShortcutKey); + + return () => { + window.removeEventListener('keydown', handleShortcutKey); + }; + }, [setShowMockServerPanel]); + + if (show_mockserver_panel) { + return ; + } + + return ; +}; + +export default DevTools; diff --git a/packages/core/src/App/Components/dev-tools/mock-dialog.scss b/packages/core/src/App/Components/dev-tools/mock-dialog.scss new file mode 100644 index 000000000000..5dac9eb13f5d --- /dev/null +++ b/packages/core/src/App/Components/dev-tools/mock-dialog.scss @@ -0,0 +1,65 @@ +.mock-dialog { + position: absolute; + bottom: 4.6rem; + right: 1rem; + width: 34rem; + border-radius: $BORDER_RADIUS; + padding: 1.2rem 1.6rem; + background-color: var(--general-main-1); + box-shadow: 0 20px 13px rgba(0, 0, 0, 0.01), 0 4px 3px rgba(0, 0, 0, 0.02); + border: 1px solid var(--general-hover); + z-index: 69420; + + &__title { + margin-bottom: 1.4rem; + } + + &__status { + border-radius: 4px; + border: 2px solid; + padding: 0.8rem 1.2rem; + margin-bottom: 2.4rem; + + &--offline { + background-color: #f3f4f6; + border-color: #e5e7eb; + + .dc-text { + color: #6b7280; + } + } + } + + &__form { + display: flex; + min-height: 40rem; + flex-direction: column; + gap: 2rem; + + &--dropdown-container { + display: inline-flex; + gap: 1rem; + + button { + height: unset; + } + } + + &--input { + margin-bottom: unset; + } + + &--submit-container { + display: flex; + justify-content: end; + gap: 1rem; + + button:disabled { + cursor: not-allowed; + span { + color: #9ca3af; + } + } + } + } +} diff --git a/packages/core/src/App/Components/dev-tools/mock-dialog.tsx b/packages/core/src/App/Components/dev-tools/mock-dialog.tsx new file mode 100644 index 000000000000..c91d65ece887 --- /dev/null +++ b/packages/core/src/App/Components/dev-tools/mock-dialog.tsx @@ -0,0 +1,163 @@ +import React from 'react'; +import classNames from 'classnames'; +import { Button, Dropdown, Input, Text } from '@deriv/components'; +import { useLocalStorageData } from '@deriv/hooks'; +import { useStore } from '@deriv/stores'; +import { useWS } from '@deriv/shared'; +import { getLanguage } from '@deriv/translations'; +import './mock-dialog.scss'; + +type MockServerStatus = 'online' | 'connecting' | 'offline'; + +type MockServerData = { + session_id: string; + is_mockserver_enabled: boolean; +}; + +const default_mock_data = { + session_id: '', + is_mockserver_enabled: false, +}; + +const MockDialog = () => { + const WS = useWS(); + const { client } = useStore(); + const [session_list, setSessionList] = React.useState([]); + const [mock_server_data, setMockServerData, clearAllData] = useLocalStorageData( + 'mock_server_data', + default_mock_data + ); + + React.useEffect(() => { + getSessionList(); + }, []); + + const getSessionList = async () => { + const response = await WS.send({ + generate_mock: 1, + session_list: 1, + }); + + const { session_list: list } = response; + if (Array.isArray(list)) { + setSessionList(prev => [...prev, ...list]); + } + }; + + const handleMockLogin = async () => { + const response = await WS.send({ + generate_mock: 1, + login: 1, + session_id: mock_server_data?.session_id, + }); + + delete response.echo_req; + delete response.req_id; + delete response.active_loginid; + + const param_obj: Record = {}; + Object.keys(response).forEach((loginid, index) => { + const current_index = index + 1; + param_obj[`acct${current_index}`] = loginid; + param_obj[`token${current_index}`] = response[loginid].token ?? ''; + param_obj[`cur${current_index}`] = response[loginid].currency || 'USD'; + }); + + const params = new URLSearchParams(param_obj); + const new_url = new URL(`${window.location.href}?${params}`); + window.location.replace(new_url); + }; + + const handleSessionIdChange = (id: string) => { + if (id) { + WS.closeAndOpenNewConnection(getLanguage(), id); + } + }; + + const handleClearAll = () => { + clearAllData(); + WS.closeAndOpenNewConnection(getLanguage(), ''); + }; + + const getServerStatus = (): MockServerStatus => { + if (mock_server_data?.session_id && WS.hasReadyState(1)) { + return 'online'; + } else if (mock_server_data?.session_id && (!client.is_logged_in || client.is_logging_in)) { + return 'connecting'; + } + return 'offline'; + }; + + return ( +
+
+ + Mock Server Config + +
+
+ + Mock Server status: {getServerStatus().toLocaleUpperCase()} + +
+
+
+ ({ + text: s, + value: s, + }))} + onChange={e => + setMockServerData(prev => (prev !== null ? { ...prev, session_id: e.target.value } : prev)) + } + value={mock_server_data?.session_id} + is_align_text_left + /> + +
+ + setMockServerData(prev => (prev !== null ? { ...prev, session_id: e.target.value } : prev)) + } + /> +
+ +
+
+ + +
+
+
+ ); +}; + +export default MockDialog; diff --git a/packages/core/src/App/Containers/AccountSignupModal/account-signup-modal.jsx b/packages/core/src/App/Containers/AccountSignupModal/account-signup-modal.jsx index 1cbfedc8767a..db7e32bf388b 100644 --- a/packages/core/src/App/Containers/AccountSignupModal/account-signup-modal.jsx +++ b/packages/core/src/App/Containers/AccountSignupModal/account-signup-modal.jsx @@ -1,17 +1,22 @@ -import { Formik, Form } from 'formik'; -import PropTypes from 'prop-types'; import React from 'react'; -import { getLocation, SessionStore } from '@deriv/shared'; +import { Form,Formik } from 'formik'; +import PropTypes from 'prop-types'; + import { Button, Checkbox, Dialog, Loading, Text } from '@deriv/components'; +import { getLocation, SessionStore } from '@deriv/shared'; import { localize } from '@deriv/translations'; + import { WS } from 'Services'; import { connect } from 'Stores/connect'; + +import CitizenshipForm from '../CitizenshipModal/set-citizenship-form.jsx'; import PasswordSelectionModal from '../PasswordSelectionModal/password-selection-modal.jsx'; import ResidenceForm from '../SetResidenceModal/set-residence-form.jsx'; -import CitizenshipForm from '../CitizenshipModal/set-citizenship-form.jsx'; -import 'Sass/app/modules/account-signup.scss'; + import validateSignupFields from './validate-signup-fields.jsx'; +import 'Sass/app/modules/account-signup.scss'; + const AccountSignup = ({ enableApp, is_mobile, isModalVisible, clients_country, onSignup, residence_list }) => { const signupInitialValues = { citizenship: '', password: '', residence: '' }; const [api_error, setApiError] = React.useState(false); diff --git a/packages/core/src/App/Containers/CitizenshipModal/set-citizenship-form.jsx b/packages/core/src/App/Containers/CitizenshipModal/set-citizenship-form.jsx index 4cd4581eff0c..b856dd77b323 100644 --- a/packages/core/src/App/Containers/CitizenshipModal/set-citizenship-form.jsx +++ b/packages/core/src/App/Containers/CitizenshipModal/set-citizenship-form.jsx @@ -1,6 +1,7 @@ +import React from 'react'; import { Field } from 'formik'; import PropTypes from 'prop-types'; -import React from 'react'; + import { Autocomplete, Text } from '@deriv/components'; import { Localize, localize } from '@deriv/translations'; diff --git a/packages/core/src/App/Containers/Layout/header/default-mobile-links.tsx b/packages/core/src/App/Containers/Layout/header/default-mobile-links.tsx index 8bc021ab4901..6dcd2a36e4ed 100644 --- a/packages/core/src/App/Containers/Layout/header/default-mobile-links.tsx +++ b/packages/core/src/App/Containers/Layout/header/default-mobile-links.tsx @@ -1,8 +1,11 @@ import React from 'react'; + import { Button, Icon, Popover } from '@deriv/components'; import { routes } from '@deriv/shared'; import { Localize } from '@deriv/translations'; + import { BinaryLink } from 'App/Components/Routes'; + import ShowNotifications from './show-notifications'; import TradersHubOnboarding from './traders-hub-onboarding'; diff --git a/packages/core/src/App/Containers/Layout/header/dtrader-header.jsx b/packages/core/src/App/Containers/Layout/header/dtrader-header.jsx index e36cbd596cdd..14dbf0fa9a89 100644 --- a/packages/core/src/App/Containers/Layout/header/dtrader-header.jsx +++ b/packages/core/src/App/Containers/Layout/header/dtrader-header.jsx @@ -4,6 +4,7 @@ import React from 'react'; import { withRouter } from 'react-router-dom'; import { DesktopWrapper, MobileWrapper } from '@deriv/components'; import { routes, isMobile, getDecimalPlaces, platforms } from '@deriv/shared'; +// import { useWalletMigration } from '@deriv/hooks'; import { AccountActions, MenuLinks, PlatformSwitcher } from 'App/Components/Layout/Header'; import platform_config from 'App/Constants/platform-config.ts'; import RealAccountSignup from 'App/Containers/RealAccountSignup'; @@ -59,6 +60,11 @@ const DTraderHeader = ({ [removeNotificationMessage] ); + //TODO: Uncomment once useWalletMigration hook is optimized for production release. + // const { is_migrated, is_failed } = useWalletMigration(); + // if (is_migrated) addNotificationMessage(client_notifications.wallets_migrated); + // if (is_failed) addNotificationMessage(client_notifications.wallets_failed); + React.useEffect(() => { document.addEventListener('IgnorePWAUpdate', removeUpdateNotification); return () => document.removeEventListener('IgnorePWAUpdate', removeUpdateNotification); diff --git a/packages/core/src/App/Containers/Modals/app-modals.jsx b/packages/core/src/App/Containers/Modals/app-modals.jsx index 64f42e8750d4..4745f776648b 100644 --- a/packages/core/src/App/Containers/Modals/app-modals.jsx +++ b/packages/core/src/App/Containers/Modals/app-modals.jsx @@ -1,17 +1,20 @@ import React from 'react'; import { useLocation } from 'react-router-dom'; -import { ContentFlag, routes, moduleLoader, SessionStore } from '@deriv/shared'; -import { connect } from 'Stores/connect'; -import MT5Notification from './mt5-notification'; + +import { ContentFlag, moduleLoader, routes, SessionStore } from '@deriv/shared'; + +import DerivRealAccountRequiredModal from 'App/Components/Elements/Modals/deriv-real-account-required-modal.jsx'; import MT5AccountNeededModal from 'App/Components/Elements/Modals/mt5-account-needed-modal.jsx'; import RedirectNoticeModal from 'App/Components/Elements/Modals/RedirectNotice'; -import CooldownWarningModal from './cooldown-warning-modal.jsx'; -import TradingAssessmentExistingUser from './trading-assessment-existing-user.jsx'; +import { connect } from 'Stores/connect'; + import CompletedAssessmentModal from './completed-assessment-modal.jsx'; -import DerivRealAccountRequiredModal from 'App/Components/Elements/Modals/deriv-real-account-required-modal.jsx'; +import CooldownWarningModal from './cooldown-warning-modal.jsx'; +import MT5Notification from './mt5-notification'; +import NeedRealAccountForCashierModal from './need-real-account-for-cashier-modal'; import ReadyToDepositModal from './ready-to-deposit-modal'; import RiskAcceptTestWarningModal from './risk-accept-test-warning-modal'; -import NeedRealAccountForCashierModal from './need-real-account-for-cashier-modal'; +import TradingAssessmentExistingUser from './trading-assessment-existing-user.jsx'; const AccountSignupModal = React.lazy(() => moduleLoader(() => import(/* webpackChunkName: "account-signup-modal" */ '../AccountSignupModal')) diff --git a/packages/core/src/App/Containers/PasswordSelectionModal/password-selection-modal.jsx b/packages/core/src/App/Containers/PasswordSelectionModal/password-selection-modal.jsx index 3976f1c650ac..a5244400cbd4 100644 --- a/packages/core/src/App/Containers/PasswordSelectionModal/password-selection-modal.jsx +++ b/packages/core/src/App/Containers/PasswordSelectionModal/password-selection-modal.jsx @@ -1,11 +1,14 @@ +import React from 'react'; import classNames from 'classnames'; import { Field } from 'formik'; -import React from 'react'; + import { Button, PasswordInput, PasswordMeter, Text } from '@deriv/components'; import { getErrorMessages, redirectToSignUp } from '@deriv/shared'; import { observer, useStore } from '@deriv/stores'; -import { localize, Localize } from '@deriv/translations'; +import { Localize,localize } from '@deriv/translations'; + import SignupSeparatorContainer from '../AccountSignupModal/signup-separator-container.jsx'; + import 'Sass/app/modules/account-signup.scss'; const PasswordSelectionModal = observer( diff --git a/packages/core/src/App/Containers/RealAccountSignup/__tests__/real-account-signup.spec.jsx b/packages/core/src/App/Containers/RealAccountSignup/__tests__/real-account-signup.spec.jsx index f0f70131a0a8..7864791beb1f 100644 --- a/packages/core/src/App/Containers/RealAccountSignup/__tests__/real-account-signup.spec.jsx +++ b/packages/core/src/App/Containers/RealAccountSignup/__tests__/real-account-signup.spec.jsx @@ -1,6 +1,8 @@ import React from 'react'; -import { render, screen } from '@testing-library/react'; import { BrowserRouter } from 'react-router-dom'; + +import { render, screen } from '@testing-library/react'; + import RealAccountSignup from '../real-account-signup.jsx'; jest.mock('Stores/connect', () => ({ diff --git a/packages/core/src/App/Containers/RealAccountSignup/real-account-signup.jsx b/packages/core/src/App/Containers/RealAccountSignup/real-account-signup.jsx index 3f09be4247d7..22a7b14354fc 100644 --- a/packages/core/src/App/Containers/RealAccountSignup/real-account-signup.jsx +++ b/packages/core/src/App/Containers/RealAccountSignup/real-account-signup.jsx @@ -1,21 +1,25 @@ /* eslint-disable react/display-name */ -import classNames from 'classnames'; import React from 'react'; import { withRouter } from 'react-router-dom'; -import { Button, Text, Modal, DesktopWrapper, MobileDialog, MobileWrapper } from '@deriv/components'; -import { routes } from '@deriv/shared'; +import classNames from 'classnames'; + import { RiskToleranceWarningModal, TestWarningModal } from '@deriv/account'; -import { localize, Localize } from '@deriv/translations'; +import { Button, DesktopWrapper, MobileDialog, MobileWrapper,Modal, Text } from '@deriv/components'; +import { routes } from '@deriv/shared'; +import { Localize,localize } from '@deriv/translations'; + import { connect } from 'Stores/connect'; + import AccountWizard from './account-wizard.jsx'; import AddCurrency from './add-currency.jsx'; import AddOrManageAccounts from './add-or-manage-accounts.jsx'; import ChooseCurrency from './choose-currency.jsx'; -import SetCurrency from './set-currency.jsx'; import FinishedAddCurrency from './finished-add-currency.jsx'; import FinishedSetCurrency from './finished-set-currency.jsx'; +import SetCurrency from './set-currency.jsx'; import SignupErrorContent from './signup-error-content.jsx'; import StatusDialogContainer from './status-dialog-container.jsx'; + import 'Sass/account-wizard.scss'; import 'Sass/real-account-signup.scss'; diff --git a/packages/core/src/App/Containers/SetResidenceModal/set-residence-form.jsx b/packages/core/src/App/Containers/SetResidenceModal/set-residence-form.jsx index 765112537ac2..0c5f99c644b7 100644 --- a/packages/core/src/App/Containers/SetResidenceModal/set-residence-form.jsx +++ b/packages/core/src/App/Containers/SetResidenceModal/set-residence-form.jsx @@ -1,6 +1,7 @@ +import React from 'react'; import { Field } from 'formik'; import PropTypes from 'prop-types'; -import React from 'react'; + import { Autocomplete, Text } from '@deriv/components'; import { Localize, localize } from '@deriv/translations'; diff --git a/packages/core/src/App/Containers/SetResidenceModal/set-residence-modal.jsx b/packages/core/src/App/Containers/SetResidenceModal/set-residence-modal.jsx index 05b2f60fa92a..659c037224aa 100644 --- a/packages/core/src/App/Containers/SetResidenceModal/set-residence-modal.jsx +++ b/packages/core/src/App/Containers/SetResidenceModal/set-residence-modal.jsx @@ -1,12 +1,16 @@ +import React from 'react'; import classNames from 'classnames'; -import { Formik, Form } from 'formik'; +import { Form,Formik } from 'formik'; import PropTypes from 'prop-types'; -import React from 'react'; + import { Button, Dialog, Text } from '@deriv/components'; +import { website_name } from '@deriv/shared'; import { Localize, localize } from '@deriv/translations'; + import { connect } from 'Stores/connect'; -import { website_name } from '@deriv/shared'; + import SetResidenceForm from './set-residence-form.jsx'; + import 'Sass/app/modules/set-residence.scss'; // TODO: Move some of these functions to helpers since some of them are shared with AccountSignUpModal diff --git a/packages/core/src/App/Containers/app-notification-messages.jsx b/packages/core/src/App/Containers/app-notification-messages.jsx index 98e98a0d6ef1..15a39296e8ca 100644 --- a/packages/core/src/App/Containers/app-notification-messages.jsx +++ b/packages/core/src/App/Containers/app-notification-messages.jsx @@ -112,7 +112,8 @@ const AppNotificationMessages = ({ 'svg_needs_poi', 'svg_needs_poi_poa', 'svg_poi_expired', - 'switched_to_real', + 'wallets_migrated', + 'wallets_failed', 'tnc', 'trustpilot', 'unwelcome', diff --git a/packages/core/src/Stores/Helpers/client-notifications.js b/packages/core/src/Stores/Helpers/client-notifications.js index 537c3d421746..21af86dbb7b9 100644 --- a/packages/core/src/Stores/Helpers/client-notifications.js +++ b/packages/core/src/Stores/Helpers/client-notifications.js @@ -62,7 +62,7 @@ export const getCashierValidations = cashier_arr => { }; // Notifications keys will not be added to localStorage and will appear again after user logout/login -export const excluded_notifications = ['contract_sold', 'switched_to_real', 'has_changed_two_fa']; +export const excluded_notifications = ['contract_sold', 'has_changed_two_fa']; export const maintenance_notifications = ['system_maintenance', 'site_maintenance']; @@ -71,6 +71,8 @@ export const priority_toast_messages = [ 'need_fa', 'p2p_daily_limit_increase', 'authenticate', + 'wallets_migrated', + 'wallets_failed', 'notify_financial_assessment', 'svg_needs_poi_poa', 'svg_needs_poa', diff --git a/packages/core/src/Stores/base-store.js b/packages/core/src/Stores/base-store.js index 1c5f1c2de71a..9fe9159f7a47 100644 --- a/packages/core/src/Stores/base-store.js +++ b/packages/core/src/Stores/base-store.js @@ -1,5 +1,6 @@ -import { action, intercept, observable, reaction, toJS, when, makeObservable } from 'mobx'; -import { isProduction, isEmptyObject, Validator } from '@deriv/shared'; +import { action, intercept, makeObservable,observable, reaction, toJS, when } from 'mobx'; + +import { isEmptyObject, isProduction, Validator } from '@deriv/shared'; /** * BaseStore class is the base class for all defined stores in the application. It handles some stuff such as: diff --git a/packages/core/src/Stores/client-store.js b/packages/core/src/Stores/client-store.js index 9aecc60bc185..01343ee83792 100644 --- a/packages/core/src/Stores/client-store.js +++ b/packages/core/src/Stores/client-store.js @@ -867,7 +867,7 @@ export default class ClientStore extends BaseStore { const mt_gaming_shortcode = mt_gaming_company?.financial.shortcode || mt_gaming_company?.swap_free.shortcode; const is_current_mf = this.landing_company_shortcode === 'maltainvest'; return ( - is_current_mf || //is_currently logged in mf account via trdaershub + is_current_mf || //is_currently logged in mf account via tradershub (financial_shortcode || gaming_shortcode || mt_gaming_shortcode ? (eu_shortcode_regex.test(financial_shortcode) && gaming_shortcode !== 'svg') || eu_shortcode_regex.test(gaming_shortcode) @@ -1949,7 +1949,7 @@ export default class ClientStore extends BaseStore { // if real to virtual --> switch to blue // if virtual to real --> switch to green // else keep the existing connection - const should_switch_socket_connection = this.is_virtual || /VRTC/.test(from_login_id); + const should_switch_socket_connection = this.is_virtual || /VRTC|VRW/.test(from_login_id); if (should_switch_socket_connection) { BinarySocket.closeAndOpenNewConnection(); diff --git a/packages/core/src/Stores/contract-trade-store.js b/packages/core/src/Stores/contract-trade-store.js index 68dab75e117c..b8259f94b554 100644 --- a/packages/core/src/Stores/contract-trade-store.js +++ b/packages/core/src/Stores/contract-trade-store.js @@ -1,13 +1,14 @@ -import { action, computed, observable, toJS, makeObservable, override, reaction, runInAction } from 'mobx'; +import { action, computed, makeObservable, observable, override, reaction, runInAction, toJS } from 'mobx'; + import { getAccuBarriersDTraderTimeout, getContractTypesConfig, isAccumulatorContract, isAccumulatorContractOpen, isCallPut, - isHighLow, isDesktop, isEnded, + isHighLow, isMobile, isMultiplierContract, isTurbosContract, @@ -15,8 +16,9 @@ import { LocalStore, switch_to_tick_chart, } from '@deriv/shared'; -import ContractStore from './contract-store'; + import BaseStore from './base-store'; +import ContractStore from './contract-store'; export default class ContractTradeStore extends BaseStore { // --- Observable properties --- diff --git a/packages/core/src/Stores/notification-store.js b/packages/core/src/Stores/notification-store.js index 1b8c6b895220..6fb9386d30e0 100644 --- a/packages/core/src/Stores/notification-store.js +++ b/packages/core/src/Stores/notification-store.js @@ -1,9 +1,9 @@ import React from 'react'; import debounce from 'lodash.debounce'; import { action, computed, makeObservable, observable, reaction } from 'mobx'; + import { StaticUrl } from '@deriv/components'; import { - LocalStore, daysSince, formatDate, formatMoney, @@ -15,13 +15,18 @@ import { isEmptyObject, isMobile, isMultiplierContract, + LocalStore, platform_name, routes, unique, } from '@deriv/shared'; import { Localize, localize } from '@deriv/translations'; + import { BinaryLink } from 'App/Components/Routes'; import { WS } from 'Services'; + +import { sortNotifications, sortNotificationsMobile } from '../App/Components/Elements/NotificationMessage/constants'; + import { excluded_notifications, getCashierValidations, @@ -29,7 +34,6 @@ import { hasMissingRequiredField, maintenance_notifications, } from './Helpers/client-notifications'; -import { sortNotifications, sortNotificationsMobile } from '../App/Components/Elements/NotificationMessage/constants'; import BaseStore from './base-store'; export default class NotificationStore extends BaseStore { @@ -598,7 +602,7 @@ export default class NotificationStore extends BaseStore { this.handleClientNotifications(); } - removeAllNotificationMessages(should_close_persistent) { + removeAllNotificationMessages(should_close_persistent = false) { this.notification_messages = should_close_persistent ? [] : [...this.notification_messages.filter(notifs => notifs.is_persistent)]; @@ -672,7 +676,7 @@ export default class NotificationStore extends BaseStore { setClientNotifications(client_data = {}) { const { ui } = this.root_store; - const { has_enabled_two_fa, setTwoFAChangedStatus } = this.root_store.client; + const { has_enabled_two_fa, setTwoFAChangedStatus, logout } = this.root_store.client; const { setMT5NotificationModal } = this.root_store.traders_hub; const two_fa_status = has_enabled_two_fa ? localize('enabled') : localize('disabled'); @@ -1393,6 +1397,34 @@ export default class NotificationStore extends BaseStore { text: localize('Resubmit proof of identity'), }, }, + wallets_migrated: { + key: 'wallets_migrated', + header: localize('Your Wallets are ready'), + message: localize( + 'To complete the upgrade, please log out and log in again to add more accounts and make transactions with your Wallets.' + ), + action: { + onClick: async () => { + await logout(); + }, + text: localize('Log out'), + }, + type: 'announce', + }, + wallets_failed: { + key: 'wallets_failed', + header: localize('Sorry for the interruption'), + message: localize( + "We're unable to complete with the Wallet upgrade. Please try again later or contact us via live chat." + ), + action: { + onClick: async () => { + window.LC_API.open_chat_window(); + }, + text: localize('Go to LiveChat'), + }, + type: 'danger', + }, mt5_notification: { key: 'mt5_notification', header: localize('Trouble accessing Deriv MT5 on your mobile?'), @@ -1487,23 +1519,6 @@ export default class NotificationStore extends BaseStore { }); }; - showAccountSwitchToRealNotification = (loginid, currency) => { - const regulation = loginid?.startsWith('CR') ? localize('non-EU') : localize('EU'); - - this.addNotificationMessage({ - key: 'switched_to_real', - header: localize('Switched to real account'), - message: ( - - ), - type: 'info', - should_show_again: true, - }); - }; - async getP2pCompletedOrders() { await WS.wait('authorize'); const response = await WS.send?.({ p2p_order_list: 1, active: 0 }); diff --git a/packages/core/src/Stores/traders-hub-store.js b/packages/core/src/Stores/traders-hub-store.js index 836dfdb715de..036105ed4763 100644 --- a/packages/core/src/Stores/traders-hub-store.js +++ b/packages/core/src/Stores/traders-hub-store.js @@ -30,6 +30,10 @@ export default class TradersHubStore extends BaseStore { }; is_account_transfer_modal_open = false; selected_account = {}; + is_real_wallets_upgrade_on = false; + is_wallet_migration_failed = false; + active_modal_tab; + active_modal_wallet_id; constructor(root_store) { super({ root_store }); @@ -54,8 +58,12 @@ export default class TradersHubStore extends BaseStore { selected_account: observable, selected_account_type: observable, selected_platform_type: observable, + active_modal_tab: observable, + active_modal_wallet_id: observable, selected_region: observable, open_failed_verification_for: observable, + is_real_wallets_upgrade_on: observable, + is_wallet_migration_failed: observable, closeModal: action.bound, content_flag: computed, getAccount: action.bound, @@ -65,6 +73,8 @@ export default class TradersHubStore extends BaseStore { getAvailableDerivEzAccounts: action.bound, getExistingAccounts: action.bound, handleTabItemClick: action.bound, + setWalletModalActiveTab: action.bound, + setWalletModalActiveWalletID: action.bound, has_any_real_account: computed, is_demo_low_risk: computed, is_demo: computed, @@ -76,6 +86,7 @@ export default class TradersHubStore extends BaseStore { no_MF_account: computed, multipliers_account_status: computed, CFDs_restricted_countries: computed, + financial_restricted_countries: computed, openDemoCFDAccount: action.bound, openModal: action.bound, openRealAccount: action.bound, @@ -99,7 +110,8 @@ export default class TradersHubStore extends BaseStore { toggleIsTourOpen: action.bound, toggleRegulatorsCompareModal: action.bound, showTopUpModal: action.bound, - financial_restricted_countries: computed, + toggleWalletsUpgrade: action.bound, + setWalletsMigrationFailedPopup: action.bound, }); reaction( @@ -142,7 +154,6 @@ export default class TradersHubStore extends BaseStore { const residence = this.root_store.client.residence; const active_demo = /^VRT|VRW/.test(this.root_store.client.loginid); const active_real_mf = /^MF|MFW/.test(this.root_store.client.loginid); - const default_region = () => { if (((active_demo || active_real_mf) && isEuCountry(residence)) || active_real_mf) { return 'EU'; @@ -173,6 +184,14 @@ export default class TradersHubStore extends BaseStore { } } + setWalletModalActiveTab(tab) { + this.active_modal_tab = tab; + } + + setWalletModalActiveWalletID(wallet_id) { + this.active_modal_wallet_id = wallet_id; + } + get no_MF_account() { const { has_maltainvest_account } = this.root_store.client; return this.selected_region === 'EU' && !has_maltainvest_account; @@ -784,4 +803,12 @@ export default class TradersHubStore extends BaseStore { }); openTopUpModal(); } + + toggleWalletsUpgrade(value) { + this.is_real_wallets_upgrade_on = value; + } + + setWalletsMigrationFailedPopup(value) { + this.is_wallet_migration_failed = value; + } } diff --git a/packages/core/src/Stores/ui-store.js b/packages/core/src/Stores/ui-store.js index 0944dcd95815..d6b147811043 100644 --- a/packages/core/src/Stores/ui-store.js +++ b/packages/core/src/Stores/ui-store.js @@ -1,6 +1,9 @@ +import { action, autorun, computed, makeObservable, observable } from 'mobx'; + import { isMobile, isTouchDevice, LocalStore, routes } from '@deriv/shared'; + import { MAX_MOBILE_WIDTH, MAX_TABLET_WIDTH } from 'Constants/ui'; -import { action, autorun, computed, observable, makeObservable } from 'mobx'; + import BaseStore from './base-store'; const store_name = 'ui_store'; @@ -159,6 +162,7 @@ export default class UIStore extends BaseStore { should_show_assessment_complete_modal = false; app_contents_scroll_ref = null; is_deriv_account_needed_modal_visible = false; + is_wallet_modal_visible = false; is_ready_to_deposit_modal_visible = false; is_need_real_account_for_cashier_modal_visible = false; is_switch_to_deriv_account_modal_visible = false; @@ -245,6 +249,7 @@ export default class UIStore extends BaseStore { is_closing_create_real_account_modal: observable, is_dark_mode_on: observable, is_deriv_account_needed_modal_visible: observable, + is_wallet_modal_visible: observable, is_history_tab_active: observable, is_landscape: observable, @@ -343,6 +348,7 @@ export default class UIStore extends BaseStore { toggleShouldShowMultipliersOnboarding: action.bound, shouldNavigateAfterChooseCrypto: action.bound, setShouldShowRiskWarningModal: action.bound, + setIsWalletModalVisible: action.bound, setIsNewAccount: action.bound, setIsRealTabEnabled: action.bound, setIsTradingAssessmentForExistingUserEnabled: action.bound, @@ -855,6 +861,10 @@ export default class UIStore extends BaseStore { this.is_deriv_account_needed_modal_visible = !this.is_deriv_account_needed_modal_visible; } + setIsWalletModalVisible(value) { + this.is_wallet_modal_visible = value; + } + setShouldShowRiskWarningModal(value) { this.should_show_risk_warning_modal = value; } diff --git a/packages/core/src/_common/base/api_middleware.js b/packages/core/src/_common/base/api_middleware.js index 11d0d835351c..f3935110a937 100644 --- a/packages/core/src/_common/base/api_middleware.js +++ b/packages/core/src/_common/base/api_middleware.js @@ -1,7 +1,14 @@ class APIMiddleware { - constructor(config) { + session_id; + + constructor(config, session_id = '') { this.config = config; this.debounced_calls = {}; + this.session_id = session_id; + } + + requestDataTransformer(request) { + return this.session_id ? { ...request, session_id: this.session_id } : request; } sendWillBeCalled({ args: [request] }) { diff --git a/packages/core/src/_common/base/socket_base.js b/packages/core/src/_common/base/socket_base.js index 71a1bf60966b..9781290673a6 100644 --- a/packages/core/src/_common/base/socket_base.js +++ b/packages/core/src/_common/base/socket_base.js @@ -30,8 +30,12 @@ const BinarySocketBase = (() => { is_down: false, }; - const getSocketUrl = language => - `wss://${getSocketURL()}/websockets/v3?app_id=${getAppId()}&l=${language}&brand=${website_name.toLowerCase()}`; + const getSocketUrl = (language, is_mock_server = false) => { + if (is_mock_server) { + return 'ws://127.0.0.1:42069'; + } + return `wss://${getSocketURL()}/websockets/v3?app_id=${getAppId()}&l=${language}&brand=${website_name.toLowerCase()}`; + }; const isReady = () => hasReadyState(1); @@ -41,10 +45,10 @@ const BinarySocketBase = (() => { binary_socket.close(); }; - const closeAndOpenNewConnection = (language = getLanguage()) => { + const closeAndOpenNewConnection = (language = getLanguage(), session_id = '') => { close(); is_switching_socket = true; - openNewConnection(language); + openNewConnection(language, session_id); }; const hasReadyState = (...states) => binary_socket && states.some(s => binary_socket.readyState === s); @@ -56,18 +60,32 @@ const BinarySocketBase = (() => { client_store = client; }; + const getMockServerConfig = () => { + const mock_server_config = localStorage.getItem('mock_server_data'); + return mock_server_config + ? JSON.parse(mock_server_config) + : { + session_id: '', + is_mockserver_enabled: false, + }; + }; + const openNewConnection = (language = getLanguage()) => { + const mock_server_config = getMockServerConfig(); + const session_id = mock_server_config?.session_id || ''; + if (wrong_app_id === getAppId()) return; if (!is_switching_socket) config.wsEvent('init'); if (isClose()) { is_disconnect_called = false; - binary_socket = new WebSocket(getSocketUrl(language)); + binary_socket = new WebSocket(getSocketUrl(language, session_id)); + deriv_api = new DerivAPIBasic({ connection: binary_socket, storage: SocketCache, - middleware: new APIMiddleware(config), + middleware: new APIMiddleware(config, session_id), }); } diff --git a/packages/core/src/public/images/app/wallet/wallet-demo-bg-dark.svg b/packages/core/src/public/images/app/wallet/wallet-demo-bg-dark.svg new file mode 100644 index 000000000000..6d1bf48f4626 --- /dev/null +++ b/packages/core/src/public/images/app/wallet/wallet-demo-bg-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core/src/public/images/app/wallet/wallet-demo-bg-light.svg b/packages/core/src/public/images/app/wallet/wallet-demo-bg-light.svg new file mode 100644 index 000000000000..12660fa7e760 --- /dev/null +++ b/packages/core/src/public/images/app/wallet/wallet-demo-bg-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core/src/sass/app.scss b/packages/core/src/sass/app.scss index 32a0db6d08b8..c190c65ed790 100644 --- a/packages/core/src/sass/app.scss +++ b/packages/core/src/sass/app.scss @@ -47,6 +47,7 @@ @import 'app/_common/components/cookie-banner'; @import 'app/_common/components/notification-banner'; @import 'app/_common/components/notification-promo'; +@import 'app/_common/components/wallet'; @import 'app/_common/components/onfido-container'; @import 'app/_common/components/cfd-poa'; // Modules diff --git a/packages/core/src/sass/app/_common/components/wallet.scss b/packages/core/src/sass/app/_common/components/wallet.scss new file mode 100644 index 000000000000..b9c319115762 --- /dev/null +++ b/packages/core/src/sass/app/_common/components/wallet.scss @@ -0,0 +1,815 @@ +// As these currencies have the same color, will use this variable for them in the map +$usdt-color: ( + 'card': ( + 'light': ( + 'mobile': radial-gradient(100% 277.78% at 0% 100%, rgba(0, 147, 147, 0.24) 0%, rgba(0, 147, 147, 0.48) 100%) + #ffffff, + 'desktop': + radial-gradient(100% 277.78% at 0% 100%, rgba(0, 147, 147, 0.24) 0%, rgba(0, 147, 147, 0.48) 100%) + #ffffff, + ), + 'dark': ( + 'mobile': radial-gradient(100% 277.78% at 0% 100%, rgba(0, 147, 147, 0.24) 0%, rgba(0, 147, 147, 0.48) 100%) + #151717, + 'desktop': + radial-gradient(100% 277.78% at 0% 100%, rgba(0, 147, 147, 0.24) 0%, rgba(0, 147, 147, 0.48) 100%) + #151717, + ), + ), + 'header': ( + 'light': ( + 'mobile': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(0, 147, 147, 0.4) 0%, + rgba(0, 147, 147, 0.16) 50.52%, + rgba(4, 217, 217, 0.4) 100% + ) + #ffffff, + 'desktop': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(0, 147, 147, 0.4) 0%, + rgba(0, 147, 147, 0.16) 50.52%, + rgba(4, 217, 217, 0.4) 100% + ) + #ffffff, + ), + 'dark': ( + 'mobile': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(0, 147, 147, 0.4) 0%, + rgba(0, 147, 147, 0.16) 50.52%, + rgba(4, 217, 217, 0.4) 100% + ) + #151717, + 'desktop': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(0, 147, 147, 0.4) 0%, + rgba(0, 147, 147, 0.16) 50.52%, + rgba(4, 217, 217, 0.4) 100% + ) + #151717, + ), + ), +); + +$wallet-bg-color: ( + 'usd': ( + 'card': ( + 'light': ( + 'mobile': + radial-gradient(100% 4130.74% at 0% 100%, rgba(244, 67, 54, 0.24) 0%, rgba(40, 57, 145, 0.48) 100%) + #ffffff, + 'desktop': + radial-gradient(100% 4130.74% at 0% 100%, rgba(244, 67, 54, 0.24) 0%, rgba(40, 57, 145, 0.48) 100%) + #ffffff, + ), + 'dark': ( + 'mobile': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(244, 67, 54, 0.4) 0%, + rgba(244, 67, 54, 0.16) 50.52%, + rgba(40, 57, 145, 0.56) 100% + ) + #151717, + 'desktop': + radial-gradient(100% 4130.74% at 0% 100%, rgba(244, 67, 54, 0.24) 0%, rgba(40, 57, 145, 0.48) 100%) + #151717, + ), + ), + 'header': ( + 'light': ( + 'mobile': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(244, 67, 54, 0.4) 0%, + rgba(244, 67, 54, 0.16) 50.52%, + rgba(40, 57, 145, 0.56) 100% + ) + #ffffff, + 'desktop': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(244, 67, 54, 0.4) 0%, + rgba(244, 67, 54, 0.16) 50.52%, + rgba(40, 57, 145, 0.56) 100% + ) + #ffffff, + ), + 'dark': ( + 'mobile': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(244, 67, 54, 0.4) 0%, + rgba(244, 67, 54, 0.16) 50.52%, + rgba(40, 57, 145, 0.56) 100% + ) + #151717, + 'desktop': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(244, 67, 54, 0.4) 0%, + rgba(244, 67, 54, 0.16) 50.52%, + rgba(40, 57, 145, 0.56) 100% + ) + #151717, + ), + ), + ), + 'aud': ( + 'card': ( + 'light': ( + 'mobile': + radial-gradient(100% 4130.74% at 0% 100%, rgba(13, 180, 61, 0.24) 0%, rgba(255, 205, 0, 0.48) 100%) + #ffffff, + 'desktop': + radial-gradient(100% 4130.74% at 0% 100%, rgba(13, 180, 61, 0.24) 0%, rgba(255, 205, 0, 0.48) 100%) + #ffffff, + ), + 'dark': ( + 'mobile': + radial-gradient(100% 4130.74% at 0% 100%, rgba(13, 180, 61, 0.24) 0%, rgba(255, 205, 0, 0.48) 100%) + #151717, + 'desktop': + radial-gradient(100% 4130.74% at 0% 100%, rgba(13, 180, 61, 0.24) 0%, rgba(255, 205, 0, 0.48) 100%) + #151717, + ), + ), + 'header': ( + 'light': ( + 'mobile': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(13, 180, 61, 0.4) 0%, + rgba(13, 180, 61, 0.16) 50.52%, + rgba(255, 205, 0, 0.56) 100% + ) + #ffffff, + 'desktop': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(13, 180, 61, 0.4) 0%, + rgba(13, 180, 61, 0.16) 50.52%, + rgba(255, 205, 0, 0.56) 100% + ) + #ffffff, + ), + 'dark': ( + 'mobile': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(13, 180, 61, 0.4) 0%, + rgba(13, 180, 61, 0.16) 50.52%, + rgba(255, 205, 0, 0.56) 100% + ) + #151717, + 'desktop': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(13, 180, 61, 0.4) 0%, + rgba(13, 180, 61, 0.16) 50.52%, + rgba(255, 205, 0, 0.56) 100% + ) + #151717, + ), + ), + ), + 'eur': ( + 'card': ( + 'light': ( + 'mobile': + radial-gradient(100% 4130.74% at 0% 100%, rgba(40, 57, 145, 0.24) 0%, rgba(248, 209, 46, 0.48) 100%) + #ffffff, + 'desktop': + radial-gradient(100% 4130.74% at 0% 100%, rgba(40, 57, 145, 0.24) 0%, rgba(248, 209, 46, 0.48) 100%) + #ffffff, + ), + 'dark': ( + 'mobile': + radial-gradient(100% 4130.74% at 0% 100%, rgba(40, 57, 145, 0.24) 0%, rgba(248, 209, 46, 0.48) 100%) + #151717, + 'desktop': + radial-gradient(100% 4130.74% at 0% 100%, rgba(40, 57, 145, 0.24) 0%, rgba(248, 209, 46, 0.48) 100%) + #151717, + ), + ), + 'header': ( + 'light': ( + 'mobile': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(40, 57, 145, 0.4) 0%, + rgba(40, 57, 145, 0.16) 50.52%, + rgba(248, 209, 46, 0.56) 100% + ) + #ffffff, + 'desktop': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(40, 57, 145, 0.4) 0%, + rgba(40, 57, 145, 0.16) 50.52%, + rgba(248, 209, 46, 0.56) 100% + ) + #ffffff, + ), + 'dark': ( + 'mobile': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(40, 57, 145, 0.4) 0%, + rgba(40, 57, 145, 0.16) 50.52%, + rgba(248, 209, 46, 0.56) 100% + ) + #151717, + 'desktop': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(40, 57, 145, 0.4) 0%, + rgba(40, 57, 145, 0.16) 50.52%, + rgba(248, 209, 46, 0.56) 100% + ) + #151717, + ), + ), + ), + 'gbp': ( + 'card': ( + 'light': ( + 'mobile': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(40, 57, 145, 0.24) 0%, + rgba(40, 57, 145, 0.24) 0.01%, + rgba(244, 67, 54, 0.48) 100% + ) + #ffffff, + 'desktop': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(40, 57, 145, 0.24) 0%, + rgba(40, 57, 145, 0.24) 0.01%, + rgba(244, 67, 54, 0.48) 100% + ) + #ffffff, + ), + 'dark': ( + 'mobile': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(40, 57, 145, 0.24) 0%, + rgba(40, 57, 145, 0.24) 0.01%, + rgba(244, 67, 54, 0.48) 100% + ) + #151717, + 'desktop': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(40, 57, 145, 0.24) 0%, + rgba(40, 57, 145, 0.24) 0.01%, + rgba(244, 67, 54, 0.48) 100% + ) + #151717, + ), + ), + 'header': ( + 'light': ( + 'mobile': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(40, 57, 145, 0.4) 0%, + rgba(40, 57, 145, 0.16) 50.52%, + rgba(244, 67, 54, 0.56) 100% + ) + #ffffff, + 'desktop': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(40, 57, 145, 0.4) 0%, + rgba(40, 57, 145, 0.16) 50.52%, + rgba(244, 67, 54, 0.56) 100% + ) + #ffffff, + ), + 'dark': ( + 'mobile': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(40, 57, 145, 0.4) 0%, + rgba(40, 57, 145, 0.16) 50.52%, + rgba(244, 67, 54, 0.56) 100% + ) + #151717, + 'desktop': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(40, 57, 145, 0.4) 0%, + rgba(40, 57, 145, 0.16) 50.52%, + rgba(244, 67, 54, 0.56) 100% + ) + #151717, + ), + ), + ), + 'p2p': ( + 'card': ( + 'light': ( + 'mobile': + radial-gradient(100% 277.78% at 0% 100%, rgba(255, 68, 79, 0.24) 0%, rgba(255, 100, 68, 0.48) 100%) + #ffffff, + 'desktop': + radial-gradient(100% 277.78% at 0% 100%, rgba(255, 68, 79, 0.24) 0%, rgba(255, 100, 68, 0.48) 100%) + #ffffff, + ), + 'dark': ( + 'mobile': + radial-gradient(100% 277.78% at 0% 100%, rgba(255, 68, 79, 0.24) 0%, rgba(255, 100, 68, 0.48) 100%) + #151717, + 'desktop': + radial-gradient(100% 277.78% at 0% 100%, rgba(255, 68, 79, 0.24) 0%, rgba(255, 100, 68, 0.48) 100%) + #151717, + ), + ), + 'header': ( + 'light': ( + 'mobile': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(255, 68, 79, 0.4) 0%, + rgba(255, 68, 79, 0.16) 50.52%, + rgba(255, 100, 68, 0.56) 100% + ) + #ffffff, + 'desktop': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(255, 68, 79, 0.4) 0%, + rgba(255, 68, 79, 0.16) 50.52%, + rgba(255, 100, 68, 0.56) 100% + ) + #ffffff, + ), + 'dark': ( + 'mobile': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(255, 68, 79, 0.4) 0%, + rgba(255, 68, 79, 0.16) 50.52%, + rgba(255, 100, 68, 0.56) 100% + ) + #151717, + 'desktop': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(255, 68, 79, 0.4) 0%, + rgba(255, 68, 79, 0.16) 50.52%, + rgba(255, 100, 68, 0.56) 100% + ) + #151717, + ), + ), + ), + 'payment-agent': ( + 'card': ( + 'light': ( + 'mobile': + radial-gradient( + 100% 277.78% at 0% 100%, + rgba(151, 151, 151, 0.24) 0%, + rgba(178, 194, 195, 0.48) 100% + ) + #ffffff, + 'desktop': + radial-gradient( + 100% 277.78% at 0% 100%, + rgba(151, 151, 151, 0.24) 0%, + rgba(178, 194, 195, 0.48) 100% + ) + #ffffff, + ), + 'dark': ( + 'mobile': + radial-gradient( + 100% 277.78% at 0% 100%, + rgba(151, 151, 151, 0.24) 0%, + rgba(178, 194, 195, 0.48) 100% + ) + #151717, + 'desktop': + radial-gradient( + 100% 277.78% at 0% 100%, + rgba(151, 151, 151, 0.24) 0%, + rgba(178, 194, 195, 0.48) 100% + ) + #151717, + ), + ), + 'header': ( + 'light': ( + 'mobile': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(151, 151, 151, 0.4) 0%, + rgba(151, 151, 151, 0.16) 50.52%, + rgba(178, 194, 195, 0.56) 100% + ) + #ffffff, + 'desktop': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(151, 151, 151, 0.4) 0%, + rgba(151, 151, 151, 0.16) 50.52%, + rgba(178, 194, 195, 0.56) 100% + ) + #ffffff, + ), + 'dark': ( + 'mobile': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(151, 151, 151, 0.4) 0%, + rgba(151, 151, 151, 0.16) 50.52%, + rgba(178, 194, 195, 0.56) 100% + ) + #151717, + 'desktop': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(151, 151, 151, 0.4) 0%, + rgba(151, 151, 151, 0.16) 50.52%, + rgba(178, 194, 195, 0.56) 100% + ) + #151717, + ), + ), + ), + 'btc': ( + 'card': ( + 'light': ( + 'mobile': + radial-gradient( + 100% 277.78% at 0% 100%, + rgba(247, 147, 27, 0.24) 0%, + rgba(247, 199, 27, 0.477) 99.99% + ) + #ffffff, + 'desktop': + radial-gradient( + 100% 277.78% at 0% 100%, + rgba(247, 147, 27, 0.24) 0%, + rgba(247, 199, 27, 0.477) 99.99% + ) + #ffffff, + ), + 'dark': ( + 'mobile': + radial-gradient( + 100% 277.78% at 0% 100%, + rgba(247, 147, 27, 0.24) 0%, + rgba(247, 199, 27, 0.477) 99.99%, + rgba(255, 100, 68, 0.48) 100% + ) + #151717, + 'desktop': + radial-gradient( + 100% 277.78% at 0% 100%, + rgba(247, 147, 27, 0.24) 0%, + rgba(247, 199, 27, 0.477) 99.99%, + rgba(255, 100, 68, 0.48) 100% + ) + #151717, + ), + ), + 'header': ( + 'light': ( + 'mobile': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(247, 147, 27, 0.4) 0%, + rgba(247, 147, 27, 0.16) 50.52%, + rgba(247, 199, 27, 0.4) 100% + ) + #ffffff, + 'desktop': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(247, 147, 27, 0.4) 0%, + rgba(247, 147, 27, 0.16) 50.52%, + rgba(247, 199, 27, 0.4) 100% + ) + #ffffff, + ), + 'dark': ( + 'mobile': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(247, 147, 27, 0.4) 0%, + rgba(247, 147, 27, 0.16) 50.52%, + rgba(247, 199, 27, 0.4) 100% + ) + #151717, + 'desktop': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(247, 147, 27, 0.4) 0%, + rgba(247, 147, 27, 0.16) 50.52%, + rgba(247, 199, 27, 0.4) 100% + ) + #151717, + ), + ), + ), + 'eth': ( + 'card': ( + 'light': ( + 'mobile': + radial-gradient(100% 277.78% at 0% 100%, rgba(82, 86, 127, 0.24) 0%, rgba(130, 140, 173, 0.48) 100%) + #ffffff, + 'desktop': + radial-gradient(100% 277.78% at 0% 100%, rgba(82, 86, 127, 0.24) 0%, rgba(130, 140, 173, 0.48) 100%) + #ffffff, + ), + 'dark': ( + 'mobile': + radial-gradient(100% 277.78% at 0% 100%, rgba(82, 86, 127, 0.24) 0%, rgba(130, 140, 173, 0.48) 100%) + #151717, + 'desktop': + radial-gradient(100% 277.78% at 0% 100%, rgba(82, 86, 127, 0.24) 0%, rgba(130, 140, 173, 0.48) 100%) + #151717, + ), + ), + 'header': ( + 'light': ( + 'mobile': + radial-gradient(100% 277.78% at 0% 100%, rgba(82, 86, 127, 0.24) 0%, rgba(130, 140, 173, 0.48) 100%) + #ffffff, + 'desktop': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(82, 86, 127, 0.4) 0%, + rgba(82, 86, 127, 0.16) 50.52%, + rgba(130, 140, 173, 0.4) 100% + ) + #ffffff, + ), + 'dark': ( + 'mobile': + radial-gradient(100% 277.78% at 0% 100%, rgba(82, 86, 127, 0.24) 0%, rgba(130, 140, 173, 0.48) 100%) + #151717, + 'desktop': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(82, 86, 127, 0.4) 0%, + rgba(82, 86, 127, 0.16) 50.52%, + rgba(130, 140, 173, 0.4) 100% + ) + #151717, + ), + ), + ), + 'ltc': ( + 'card': ( + 'light': ( + 'mobile': + radial-gradient( + 100% 277.78% at 0% 100%, + rgba(165, 168, 169, 0.24) 0%, + rgba(193, 204, 207, 0.48) 100% + ) + #ffffff, + 'desktop': + radial-gradient( + 100% 277.78% at 0% 100%, + rgba(165, 168, 169, 0.24) 0%, + rgba(193, 204, 207, 0.48) 100% + ) + #ffffff, + ), + 'dark': ( + 'mobile': + radial-gradient( + 100% 277.78% at 0% 100%, + rgba(165, 168, 169, 0.24) 0%, + rgba(193, 204, 207, 0.48) 100% + ) + #151717, + 'desktop': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(165, 168, 169, 0.4) 0%, + rgba(165, 168, 169, 0.16) 50.52%, + rgba(193, 204, 207, 0.4) 100% + ) + #151717, + ), + ), + 'header': ( + 'light': ( + 'mobile': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(165, 168, 169, 0.4) 0%, + rgba(165, 168, 169, 0.16) 50.52%, + rgba(193, 204, 207, 0.4) 100% + ) + #ffffff, + 'desktop': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(165, 168, 169, 0.4) 0%, + rgba(165, 168, 169, 0.16) 50.52%, + rgba(193, 204, 207, 0.4) 100% + ) + #ffffff, + ), + 'dark': ( + 'mobile': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(165, 168, 169, 0.4) 0%, + rgba(165, 168, 169, 0.16) 50.52%, + rgba(193, 204, 207, 0.4) 100% + ) + #151717, + 'desktop': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(165, 168, 169, 0.4) 0%, + rgba(165, 168, 169, 0.16) 50.52%, + rgba(193, 204, 207, 0.4) 100% + ) + #151717, + ), + ), + ), + 'ust': $usdt-color, + 'usdt': $usdt-color, + 'tusdt': $usdt-color, + 'eusdt': $usdt-color, + 'usdc': ( + 'card': ( + 'light': ( + 'mobile': + radial-gradient(100% 277.78% at 0% 100%, rgba(39, 117, 202, 0.24) 0%, rgba(34, 76, 225, 0.48) 100%) + #ffffff, + 'desktop': + radial-gradient(100% 277.78% at 0% 100%, rgba(39, 117, 202, 0.24) 0%, rgba(34, 76, 225, 0.48) 100%) + #ffffff, + ), + 'dark': ( + 'mobile': + radial-gradient(100% 277.78% at 0% 100%, rgba(39, 117, 202, 0.24) 0%, rgba(34, 76, 225, 0.48) 100%) + #151717, + 'desktop': + radial-gradient(100% 277.78% at 0% 100%, rgba(39, 117, 202, 0.24) 0%, rgba(34, 76, 225, 0.48) 100%) + #151717, + ), + ), + 'header': ( + 'light': ( + 'mobile': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(39, 117, 202, 0.4) 0%, + rgba(39, 117, 202, 0.16) 50.52%, + rgba(34, 76, 225, 0.4) 100% + ) + #ffffff, + 'desktop': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(39, 117, 202, 0.4) 0%, + rgba(39, 117, 202, 0.16) 50.52%, + rgba(34, 76, 225, 0.4) 100% + ) + #ffffff, + ), + 'dark': ( + 'mobile': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(39, 117, 202, 0.4) 0%, + rgba(39, 117, 202, 0.16) 50.52%, + rgba(34, 76, 225, 0.4) 100% + ) + #151717, + 'desktop': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(39, 117, 202, 0.4) 0%, + rgba(39, 117, 202, 0.16) 50.52%, + rgba(34, 76, 225, 0.4) 100% + ) + #151717, + ), + ), + ), + 'demo': ( + 'card': ( + 'light': ( + 'mobile': + radial-gradient(100% 277.78% at 0% 100%, rgba(255, 100, 68, 0.24) 0%, rgba(255, 68, 79, 0.48) 100%) + #212329, + 'desktop': + radial-gradient(100% 277.78% at 0% 100%, rgba(255, 100, 68, 0.24) 0%, rgba(255, 68, 79, 0.48) 100%) + #212329, + ), + 'dark': ( + 'mobile': + radial-gradient(100% 277.78% at 0% 100%, rgba(255, 100, 68, 0.24) 0%, rgba(255, 68, 79, 0.48) 100%) + #fbdddd, + 'desktop': + radial-gradient(100% 277.78% at 0% 100%, rgba(255, 100, 68, 0.24) 0%, rgba(255, 68, 79, 0.48) 100%) + #fbdddd, + ), + ), + 'header': ( + 'light': ( + 'mobile': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(255, 100, 68, 0.4) 0%, + rgba(255, 100, 68, 0.16) 50.52%, + rgba(255, 68, 79, 0.4) 100% + ) + #212329, + 'desktop': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(255, 100, 68, 0.4) 0%, + rgba(255, 100, 68, 0.16) 50.52%, + rgba(255, 68, 79, 0.4) 100% + ) + #212329, + ), + 'dark': ( + 'mobile': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(255, 100, 68, 0.4) 0%, + rgba(255, 100, 68, 0.16) 50.52%, + rgba(255, 68, 79, 0.4) 100% + ) + #fbdddd, + 'desktop': + radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(255, 100, 68, 0.4) 0%, + rgba(255, 100, 68, 0.16) 50.52%, + rgba(255, 68, 79, 0.4) 100% + ) + #fbdddd, + ), + ), + ), +); + +/// Wallet background color generator +/// +/// This mixin will generate a helper classes for wallet background color based on the given map +/// +/// @example +/// .wallet-header__usd-bg +/// .wallet-header__usd-bg--dark +@mixin wallet-bg-color($currencies-map: $wallet-bg-color) { + @each $currency, $types in $currencies-map { + @each $type, $themes in $types { + @each $theme, $colors in $themes { + $t: if($theme == 'dark', '--dark', ''); + + .wallet-#{$type}__#{to-lower-case($currency)}-bg#{$t} { + background: map-get($colors, 'desktop'); + + @if $currency == 'demo' { + position: relative; + $demo-icon: if($theme == 'dark', 'wallet-demo-bg-dark', 'wallet-demo-bg-light'); + + &:not([class*='--hide-watermark']):not([class*='--small']) { + &:before { + content: ''; + display: block; + position: absolute; + inset: 0; + background-image: url('~Images/app/wallet/#{$demo-icon}.svg'); + background-repeat: repeat; + background-size: 70px; + mix-blend-mode: overlay; + opacity: 0.24; + } + } + } + + @include mobile { + background: map-get($colors, 'mobile'); + } + } + } + } + } +} + +// Init mixins +@include wallet-bg-color(); diff --git a/packages/core/src/sass/app/_common/layout/traders-hub-header.scss b/packages/core/src/sass/app/_common/layout/traders-hub-header.scss index ea3841980160..1c5b291cb5a6 100644 --- a/packages/core/src/sass/app/_common/layout/traders-hub-header.scss +++ b/packages/core/src/sass/app/_common/layout/traders-hub-header.scss @@ -22,7 +22,8 @@ } @media screen and (max-width: 380px) { - &__cashier-button { + &__cashier-button, + &__logo-wrapper { display: none; } } diff --git a/packages/hooks/src/__tests__/useActiveWallet.spec.tsx b/packages/hooks/src/__tests__/useActiveWallet.spec.tsx new file mode 100644 index 000000000000..1c9d209d674b --- /dev/null +++ b/packages/hooks/src/__tests__/useActiveWallet.spec.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { renderHook } from '@testing-library/react-hooks'; +import useActiveWallet from '../useActiveWallet'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import { APIProvider } from '@deriv/api'; + +jest.mock('@deriv/api', () => ({ + ...jest.requireActual('@deriv/api'), + useFetch: jest.fn((name: string) => { + if (name === 'balance') { + return { + data: { + balance: { + accounts: { + CRW000000: { + balance: 100, + }, + }, + }, + }, + }; + } + if (name === 'authorize') { + return { + data: { + authorize: { + account_list: [ + { + loginid: 'CRW000000', + account_category: 'wallet', + is_virtual: 0, + landing_company_name: 'maltainvest', + currency: 'USD', + }, + { + loginid: 'MXN000000', + account_category: 'trading', + is_virtual: 0, + landing_company_name: 'maltainvest', + currency: 'BTC', + }, + ], + loginid: 'CRW000000', + }, + }, + }; + } + + return { data: undefined }; + }), +})); + +describe('useActiveWallet', () => { + it('should return active wallet', () => { + const mock = mockStore({ + client: { + loginid: 'CRW000000', + accounts: { + CRW000000: { + token: 'token', + }, + }, + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + + {children} + + ); + + const { result } = renderHook(() => useActiveWallet(), { wrapper }); + + expect(result?.current?.is_selected).toEqual(true); + expect(result?.current?.loginid).toEqual(mock.client.loginid); + }); +}); diff --git a/packages/hooks/src/__tests__/useAuthorize.spec.tsx b/packages/hooks/src/__tests__/useAuthorize.spec.tsx new file mode 100644 index 000000000000..39b90ee4f937 --- /dev/null +++ b/packages/hooks/src/__tests__/useAuthorize.spec.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { APIProvider } from '@deriv/api'; +import { renderHook } from '@testing-library/react-hooks'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import useAuthorize from '../useAuthorize'; + +jest.mock('@deriv/api', () => ({ + ...jest.requireActual('@deriv/api'), + useFetch: jest.fn((_, options: Record<'payload', Record<'authorize', string>>) => ({ + data: { + authorize: { + loginid: options.payload.authorize === '12345' ? 'CRW909900' : 'CRW909901', + account_list: [ + { + account_category: 'wallet', + currency: 'USD', + is_virtual: 0, + }, + ], + }, + }, + })), +})); + +describe('useAuthorize', () => { + test('should return correct data for the given token', () => { + const mock = mockStore({ client: { accounts: { CRW909900: { token: '12345' } }, loginid: 'CRW909900' } }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + + {children} + + ); + + const { result } = renderHook(() => useAuthorize(), { wrapper }); + + expect(result.current.data.loginid).toBe('CRW909900'); + expect(result.current.data.loginid).not.toBe('CRW909901'); + }); +}); diff --git a/packages/hooks/src/__tests__/useAvailableWallets.spec.tsx b/packages/hooks/src/__tests__/useAvailableWallets.spec.tsx new file mode 100644 index 000000000000..26f109a83550 --- /dev/null +++ b/packages/hooks/src/__tests__/useAvailableWallets.spec.tsx @@ -0,0 +1,94 @@ +import React from 'react'; +import { APIProvider } from '@deriv/api'; +import useAvailableWallets from '../useAvailableWallets'; +import { renderHook } from '@testing-library/react-hooks'; +import { StoreProvider, mockStore } from '@deriv/stores'; + +jest.mock('@deriv/api', () => ({ + ...jest.requireActual('@deriv/api'), + useFetch: jest.fn((name: string) => { + if (name === 'authorize') { + return { + data: { + authorize: { + account_list: [ + { account_category: 'wallet', landing_company_name: 'svg', is_virtual: 0, currency: 'USD' }, + ], + landing_company_name: 'svg', + }, + }, + }; + } + + if (name === 'get_account_types') { + return { + data: { + get_account_types: { + wallet: { + crypto: { + currencies: ['BTC', 'ETH', 'LTC'], + }, + doughflow: { + currencies: ['USD', 'EUR', 'AUD'], + }, + }, + }, + }, + }; + } + + return { data: undefined }; + }), +})); + +describe('useAvailableWallets', () => { + const createWrapper = (mock: ReturnType) => { + const Component = ({ children }: { children: JSX.Element }) => ( + + {children} + + ); + return Component; + }; + it('should return available wallets', () => { + const mock = mockStore({ + client: { + accounts: { CRW909900: { token: '12345', landing_company_name: 'svg' } }, + loginid: 'CRW909900', + is_crypto: (currency: string) => ['BTC', 'ETH', 'LTC'].includes(currency), + }, + }); + + const { result } = renderHook(() => useAvailableWallets(), { wrapper: createWrapper(mock) }); + + expect(result.current?.data).toEqual( + ['AUD', 'EUR', 'BTC', 'ETH', 'LTC', 'USD'].map(currency => ({ + currency, + is_added: currency === 'USD', + landing_company_name: 'svg', + gradient_card_class: `wallet-card__${currency.toLowerCase()}-bg`, + })) + ); + }); + + it('should not return unavailable wallets', () => { + const mock = mockStore({ + client: { + accounts: { CRW909900: { token: '12345', landing_company_name: 'svg' } }, + loginid: 'CRW909900', + is_crypto: (currency: string) => ['BTC', 'ETH', 'LTC'].includes(currency), + }, + }); + + const { result } = renderHook(() => useAvailableWallets(), { wrapper: createWrapper(mock) }); + + expect(result.current?.data).not.toEqual([ + { + currency: 'GBP', + is_added: false, + landing_company_name: 'svg', + gradient_card_class: 'wallet-card__gbp-bg', + }, + ]); + }); +}); diff --git a/packages/hooks/src/__tests__/useContentFlag.spec.tsx b/packages/hooks/src/__tests__/useContentFlag.spec.tsx new file mode 100644 index 000000000000..e00909baa1fd --- /dev/null +++ b/packages/hooks/src/__tests__/useContentFlag.spec.tsx @@ -0,0 +1,90 @@ +import * as React from 'react'; +import { mockStore, StoreProvider } from '@deriv/stores'; +import { renderHook } from '@testing-library/react-hooks'; +import useContentFlag from '../useContentFlag'; + +describe('useContentFlag', () => { + test('should return true for cr_demo when content flag is cr_demo', async () => { + const mock = mockStore({ traders_hub: { content_flag: 'cr_demo' } }); + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + const { result } = renderHook(() => useContentFlag(), { wrapper }); + expect(result.current.is_cr_demo).toBe(true); + expect(result.current.is_eu_demo).toBe(false); + expect(result.current.is_eu_real).toBe(false); + expect(result.current.is_high_risk_cr).toBe(false); + expect(result.current.is_low_risk_cr_eu).toBe(false); + expect(result.current.is_low_risk_cr_non_eu).toBe(false); + }); + + test('should return true for eu_demo when content flag is eu_demo', async () => { + const mock = mockStore({ traders_hub: { content_flag: 'eu_demo' } }); + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + const { result } = renderHook(() => useContentFlag(), { wrapper }); + expect(result.current.is_cr_demo).toBe(false); + expect(result.current.is_eu_demo).toBe(true); + expect(result.current.is_eu_real).toBe(false); + expect(result.current.is_high_risk_cr).toBe(false); + expect(result.current.is_low_risk_cr_eu).toBe(false); + expect(result.current.is_low_risk_cr_non_eu).toBe(false); + }); + + test('should return true for eu_real when content flag is eu_real', async () => { + const mock = mockStore({ traders_hub: { content_flag: 'eu_real' } }); + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + const { result } = renderHook(() => useContentFlag(), { wrapper }); + expect(result.current.is_cr_demo).toBe(false); + expect(result.current.is_eu_demo).toBe(false); + expect(result.current.is_eu_real).toBe(true); + expect(result.current.is_high_risk_cr).toBe(false); + expect(result.current.is_low_risk_cr_eu).toBe(false); + expect(result.current.is_low_risk_cr_non_eu).toBe(false); + }); + + test('should return true for high_risk_cr when content flag is high_risk_cr', async () => { + const mock = mockStore({ traders_hub: { content_flag: 'high_risk_cr' } }); + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + const { result } = renderHook(() => useContentFlag(), { wrapper }); + expect(result.current.is_cr_demo).toBe(false); + expect(result.current.is_eu_demo).toBe(false); + expect(result.current.is_eu_real).toBe(false); + expect(result.current.is_high_risk_cr).toBe(true); + expect(result.current.is_low_risk_cr_eu).toBe(false); + expect(result.current.is_low_risk_cr_non_eu).toBe(false); + }); + + test('should return true for low_risk_cr_eu when content flag is low_risk_cr_eu', async () => { + const mock = mockStore({ traders_hub: { content_flag: 'low_risk_cr_eu' } }); + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + const { result } = renderHook(() => useContentFlag(), { wrapper }); + expect(result.current.is_cr_demo).toBe(false); + expect(result.current.is_eu_demo).toBe(false); + expect(result.current.is_eu_real).toBe(false); + expect(result.current.is_high_risk_cr).toBe(false); + expect(result.current.is_low_risk_cr_eu).toBe(true); + expect(result.current.is_low_risk_cr_non_eu).toBe(false); + }); + + test('should return true for low_risk_cr_non_eu when content flag is low_risk_cr_non_eu', async () => { + const mock = mockStore({ traders_hub: { content_flag: 'low_risk_cr_non_eu' } }); + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + const { result } = renderHook(() => useContentFlag(), { wrapper }); + expect(result.current.is_cr_demo).toBe(false); + expect(result.current.is_eu_demo).toBe(false); + expect(result.current.is_eu_real).toBe(false); + expect(result.current.is_high_risk_cr).toBe(false); + expect(result.current.is_low_risk_cr_eu).toBe(false); + expect(result.current.is_low_risk_cr_non_eu).toBe(true); + }); +}); diff --git a/packages/hooks/src/__tests__/useExistingCFDAccounts.spec.tsx b/packages/hooks/src/__tests__/useExistingCFDAccounts.spec.tsx new file mode 100644 index 000000000000..d03eb6ec6d7d --- /dev/null +++ b/packages/hooks/src/__tests__/useExistingCFDAccounts.spec.tsx @@ -0,0 +1,173 @@ +import React from 'react'; +import { renderHook } from '@testing-library/react-hooks'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import { APIProvider } from '@deriv/api'; +import useExistingCFDAccounts from '../useExistingCFDAccounts'; + +jest.mock('@deriv/api', () => ({ + ...jest.requireActual('@deriv/api'), + useFetch: jest.fn(name => { + if (name === 'authorize') { + return { + data: { + account_list: [ + { + account_category: 'wallet', + currency: 'USD', + is_virtual: 1, + linked_to: [ + { + loginid: 'CRW909900', + platform: 'mt5', + }, + ], + }, + ], + }, + }; + } + + if (name === 'mt5_login_list') { + return { + data: { + mt5_login_list: [ + { + display_login: 'CRW909900', + email: '', + leverage: '10012123123', + login: 'CRW909900', + server: 'Deriv-Server', + server_description: 'Deriv-Server', + type: 'demo', + }, + ], + }, + }; + } + + if (name === 'trading_platform_accounts') { + return { + data: { + trading_platform_accounts: [ + { + account_id: 'DXR1646584', + account_type: 'real', + balance: 0, + currency: 'USD', + display_balance: '0.00', + enabled: 1, + landing_company_short: 'svg', + login: '8807230', + market_type: 'all', + platform: 'dxtrade', + }, + { + account_type: 'real', + balance: 0, + country: 'za', + currency: 'USD', + display_balance: '0.00', + email: 'mei+za1@binary.com', + group: 'real\\p02_ts01\\all\\svg_ez_usd', + landing_company_short: 'svg', + leverage: 1000, + login: 'EZR80001086', + market_type: 'all', + name: 'Baily Pan', + server: 'p02_ts01', + server_info: { + environment: 'Deriv-Server-02', + geolocation: { + group: 'africa_derivez', + location: 'South Africa', + region: 'Africa', + sequence: 2, + }, + id: 'p02_ts01', + }, + }, + ], + }, + }; + } + + return { data: undefined }; + }), +})); + +describe('useExistingCFDAccounts', () => { + it('should return the existing cfd accounts', () => { + const mock = mockStore({ + client: { accounts: { CRW909900: { token: '12345' } }, loginid: 'CRW909900' }, + traders_hub: { + combined_cfd_mt5_accounts: [ + { + platform: 'mt5', + description: 'Deriv-Server', + icon: 'Derived', + sub_title: 'sub_name', + name: 'Derived', + }, + ], + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + + {children} + + ); + const { result } = renderHook(() => useExistingCFDAccounts(), { wrapper }); + + expect(result.current.data.dxtrade_accounts).toEqual( + expect.arrayContaining([ + { + account_id: 'DXR1646584', + account_type: 'real', + balance: 0, + currency: 'USD', + display_balance: '0.00', + enabled: 1, + landing_company_short: 'svg', + login: '8807230', + loginid: 'DXR1646584', + market_type: 'all', + platform: 'dxtrade', + transfer_icon: 'IcRebrandingDerivX', + }, + ]) + ); + + expect(result.current.data.derivez_accounts).toEqual( + expect.arrayContaining([ + { + account_type: 'real', + balance: 0, + country: 'za', + currency: 'USD', + display_balance: '0.00', + email: 'mei+za1@binary.com', + group: 'real\\p02_ts01\\all\\svg_ez_usd', + landing_company_short: 'svg', + leverage: 1000, + login: 'EZR80001086', + loginid: 'EZR80001086', + market_type: 'all', + name: 'Baily Pan', + server: 'p02_ts01', + server_info: { + environment: 'Deriv-Server-02', + geolocation: { + group: 'africa_derivez', + location: 'South Africa', + region: 'Africa', + sequence: 2, + }, + id: 'p02_ts01', + }, + transfer_icon: 'IcRebrandingDerivEz', + }, + ]) + ); + }); +}); diff --git a/packages/hooks/src/__tests__/useLocalStorageData.spec.tsx b/packages/hooks/src/__tests__/useLocalStorageData.spec.tsx new file mode 100644 index 000000000000..e020f71c9024 --- /dev/null +++ b/packages/hooks/src/__tests__/useLocalStorageData.spec.tsx @@ -0,0 +1,65 @@ +import { renderHook, act } from '@testing-library/react-hooks'; +import useLocalStorageData from '../useLocalStorageData'; + +describe('useLocalStorageData', () => { + beforeEach(() => { + localStorage.clear(); + }); + + test('should get the current value from localStorage when it exists', () => { + const key = 'test_key'; + const value = 'some value'; + + localStorage.setItem(key, JSON.stringify(value)); + + const { result } = renderHook(() => useLocalStorageData(key)); + const [data] = result.current; + + expect(data).toBe(value); + }); + + test('should use the fallback value when localStorage key does not exist', () => { + const key = 'non_existent_key'; + const fallbackValue = 'default value'; + + const { result } = renderHook(() => useLocalStorageData(key, fallbackValue)); + const [data] = result.current; + + expect(data).toBe(fallbackValue); + }); + + test('should get null when localStorage key does not exist', () => { + const key = 'non_existent_key'; + const { result } = renderHook(() => useLocalStorageData(key)); + const [, , clearData] = result.current; + + act(() => { + clearData(); + }); + + const [data] = result.current; + expect(data).toBeNull(); + }); + + test('should clear the localStorage key and reset to fallback value', () => { + const key = 'test_key'; + const fallbackValue = 'default value'; + + const { result } = renderHook(() => useLocalStorageData(key, fallbackValue)); + const [, setData, clearData] = result.current; + + act(() => { + clearData(); + }); + + expect(localStorage.getItem(key)).toBeNull(); + expect(result.current[0]).toBe(fallbackValue); + + act(() => { + setData('new value'); + }); + + expect(localStorage.getItem(key)).toBe(JSON.stringify('new value')); + expect(result.current[0]).toBe('new value'); + }); +}); diff --git a/packages/hooks/src/__tests__/usePlatformAccounts.spec.tsx b/packages/hooks/src/__tests__/usePlatformAccounts.spec.tsx index d46a4cc10bac..bdc839050760 100644 --- a/packages/hooks/src/__tests__/usePlatformAccounts.spec.tsx +++ b/packages/hooks/src/__tests__/usePlatformAccounts.spec.tsx @@ -3,7 +3,7 @@ import { mockStore, StoreProvider } from '@deriv/stores'; import { renderHook } from '@testing-library/react-hooks'; import usePlatformAccounts from '../usePlatformAccounts'; -describe('usePlatformRealAccounts', () => { +describe('usePlatformAccounts', () => { test('should return proper data when user has no platform demo and real accounts', async () => { const mock = mockStore({}); @@ -64,17 +64,17 @@ describe('usePlatformRealAccounts', () => { accounts: { CR1234: { is_virtual: 0, - loginid: 'VR1234', + loginid: 'CR1234', landing_company_shortcode: 'svg', }, MF1234: { is_virtual: 0, - loginid: 'VR1235', + loginid: 'MF1234', landing_company_shortcode: 'maltainvest', }, VR1235: { is_virtual: 1, - loginid: 'VR1236', + loginid: 'VR1235', }, }, }, diff --git a/packages/hooks/src/__tests__/useTransferBetweenAccounts.spec.tsx b/packages/hooks/src/__tests__/useTransferBetweenAccounts.spec.tsx new file mode 100644 index 000000000000..82deee5e872b --- /dev/null +++ b/packages/hooks/src/__tests__/useTransferBetweenAccounts.spec.tsx @@ -0,0 +1,295 @@ +import React from 'react'; +import { renderHook } from '@testing-library/react-hooks'; +import { APIProvider } from '@deriv/api'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import useTransferBetweenAccounts from '../useTransferBetweenAccounts'; + +jest.mock('@deriv/api', () => ({ + ...jest.requireActual('@deriv/api'), + useFetch: jest.fn((name: string) => { + if (name === 'authorize') { + return { + data: { + authorize: { + account_list: [ + { + account_category: 'wallet', + account_type: 'doughflow', + created_at: 1688642811, + currency: 'USD', + is_disabled: 0, + is_selected: true, + is_virtual: 0, + landing_company_name: 'svg', + linked_to: [ + { + loginid: 'MTR100967300', + platform: 'mt5', + }, + { + loginid: 'MTR80057067', + platform: 'mt5', + }, + { + loginid: 'DXR1646584', + platform: 'dxtrade', + }, + { + loginid: 'EZR80001086', + platform: 'derivez', + }, + ], + loginid: 'CRW1030', + }, + ], + loginid: 'CRW1030', + }, + }, + }; + } + if (name === 'mt5_login_list') { + return { + data: { + mt5_login_list: [ + { + account_type: 'real', + balance: 0, + country: 'ng', + currency: 'USD', + display_balance: '0.00', + email: 'email@gmail.com', + group: 'real\\p02_ts02\\synthetic\\svg_std_usd\\04', + landing_company_short: 'svg', + leverage: 500, + login: 'MTR100967300', + market_type: 'synthetic', + name: 'Name', + server: 'p02_ts02', + server_info: { + environment: 'Deriv-Server-02', + geolocation: { + group: 'africa_synthetic', + location: 'South Africa', + region: 'Africa', + sequence: 2, + }, + id: 'p02_ts02', + }, + status: null, + sub_account_category: '', + sub_account_type: 'financial', + }, + { + account_type: 'real', + balance: 0, + country: 'ng', + currency: 'USD', + display_balance: '0.00', + email: 'email@gmail.com', + group: 'real\\p02_ts01\\all\\svg_std-sf_usd', + landing_company_short: 'svg', + leverage: 1000, + login: 'MTR80057067', + market_type: 'all', + name: 'Name', + server: 'p02_ts01', + server_info: { + environment: 'Deriv-Server-02', + geolocation: { + group: 'africa_derivez', + location: 'South Africa', + region: 'Africa', + sequence: 2, + }, + id: 'p02_ts01', + }, + status: null, + sub_account_category: 'swap_free', + sub_account_type: 'standard', + }, + ], + }, + }; + } + if (name === 'trading_platform_accounts') { + return { + data: { + trading_platform_accounts: [ + { + account_id: 'DXR1646584', + account_type: 'real', + balance: 0, + currency: 'USD', + display_balance: '0.00', + enabled: 1, + landing_company_short: 'svg', + login: '8807230', + market_type: 'all', + platform: 'dxtrade', + }, + { + account_type: 'real', + balance: 0, + country: 'za', + currency: 'USD', + display_balance: '0.00', + email: 'mei+za1@binary.com', + group: 'real\\p02_ts01\\all\\svg_ez_usd', + landing_company_short: 'svg', + leverage: 1000, + login: 'EZR80001086', + market_type: 'all', + name: 'Baily Pan', + server: 'p02_ts01', + server_info: { + environment: 'Deriv-Server-02', + geolocation: { + group: 'africa_derivez', + location: 'South Africa', + region: 'Africa', + sequence: 2, + }, + id: 'p02_ts01', + }, + }, + ], + }, + }; + } + if (name === 'website_status') { + return { + data: { + website_status: { + currencies_config: { + USD: { + fractional_digits: 2, + name: 'US Dollar', + type: 'fiat', + }, + }, + }, + }, + }; + } + if (name === 'transfer_between_accounts') { + return { + data: { + accounts: [ + { + account_type: 'wallet', + balance: '100.00', + currency: 'USD', + demo_account: 0, + loginid: 'CRW1030', + }, + { + account_type: 'mt5', + balance: '0.00', + currency: 'USD', + demo_account: 0, + loginid: 'MTR100967300', + }, + { + account_type: 'mt5', + balance: '0.00', + currency: 'USD', + demo_account: 0, + loginid: 'MTR80057067', + }, + { + account_type: 'derivez', + balance: '0.00', + currency: 'USD', + demo_account: 0, + loginid: 'EZR80001086', + }, + { + account_type: 'dxtrade', + balance: '0.00', + currency: 'USD', + demo_account: 0, + loginid: 'DXR1646584', + }, + ], + }, + }; + } + + return { data: undefined }; + }), +})); + +describe('useTransferBetweenAccounts', () => { + let mock_store: ReturnType, wrapper: ({ children }: { children: JSX.Element }) => JSX.Element; + + beforeEach(() => { + mock_store = mockStore({ + client: { + loginid: 'CRW1030', + accounts: { + CRW1030: { + token: 'token', + }, + }, + }, + }); + + wrapper = ({ children }: { children: JSX.Element }) => ( + + {children} + + ); + }); + + it('should be correct amount of transfer accounts', () => { + const { + result: { + current: { trading_accounts, wallet_accounts }, + }, + } = renderHook(() => useTransferBetweenAccounts(), { wrapper }); + + expect(Object.keys(trading_accounts)).toHaveLength(4); + expect(Object.keys(wallet_accounts)).toHaveLength(1); + }); + + it('all transfer accounts should have extended properties', () => { + const { + result: { + current: { trading_accounts, wallet_accounts }, + }, + } = renderHook(() => useTransferBetweenAccounts(), { wrapper }); + + Object.values({ ...trading_accounts, ...wallet_accounts }).forEach(account => { + expect(account).toHaveProperty('active_wallet_icon'); + expect(account).toHaveProperty('display_currency_code'); + expect(account).toHaveProperty('gradient_class'); + expect(account).toHaveProperty('icon'); + expect(account).toHaveProperty('is_demo'); + expect(account).toHaveProperty('shortcode'); + expect(account).toHaveProperty('type'); + }); + }); + + it('should return proper active account with extended properties', () => { + const { + result: { + current: { active_wallet }, + }, + } = renderHook(() => useTransferBetweenAccounts(), { wrapper }); + + expect(active_wallet).toEqual({ + account_type: 'wallet', + active_wallet_icon: 'IcWalletCurrencyUsd', + balance: 100, + currency: 'USD', + demo_account: 0, + display_currency_code: 'USD', + gradient_class: 'wallet-card__usd-bg', + icon: 'IcWalletCurrencyUsd', + is_demo: false, + loginid: 'CRW1030', + shortcode: 'svg', + type: 'fiat', + }); + }); +}); diff --git a/packages/hooks/src/__tests__/useWalletMigration.spec.tsx b/packages/hooks/src/__tests__/useWalletMigration.spec.tsx new file mode 100644 index 000000000000..a10345f8063f --- /dev/null +++ b/packages/hooks/src/__tests__/useWalletMigration.spec.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { APIProvider, useFetch, useRequest } from '@deriv/api'; +import { renderHook } from '@testing-library/react-hooks'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import useWalletMigration from '../useWalletMigration'; + +jest.mock('@deriv/api', () => ({ + ...jest.requireActual('@deriv/api'), + useFetch: jest.fn(), + useRequest: jest.fn(() => ({ mutate: jest.fn() })), +})); + +const mockUseFetch = useFetch as jest.MockedFunction>; +// @ts-expect-error need to come up with a way to mock the return type of useRequest +const mockUseRequest = useRequest as jest.MockedFunction>; + +describe('useWalletMigration', () => { + const mock = mockStore({}); + const wrapper = (mock: ReturnType) => { + const Component = ({ children }: { children: JSX.Element }) => { + return ( + + {children} + + ); + }; + return Component; + }; + + test('should return wallet migration state', () => { + // @ts-expect-error need to come up with a way to mock the return type of useFetch + mockUseFetch.mockReturnValue({ data: { wallet_migration: { state: 'eligible' } } }); + + const { result } = renderHook(() => useWalletMigration(), { wrapper: wrapper(mock) }); + + expect(result.current.state).toBe('eligible'); + }); + + test('should send start wallet migration request', () => { + // @ts-expect-error need to come up with a way to mock the return type of useRequest + mockUseRequest.mockReturnValue({ mutate: jest.fn() }); + + const { result } = renderHook(() => useWalletMigration(), { wrapper: wrapper(mock) }); + + result.current.start_migration(); + + expect(mockUseRequest('wallet_migration').mutate).toBeCalledWith({ payload: { wallet_migration: 'start' } }); + }); + + test('should send reset wallet migration request', () => { + // @ts-expect-error need to come up with a way to mock the return type of useRequest + mockUseRequest.mockReturnValue({ mutate: jest.fn() }); + + const { result } = renderHook(() => useWalletMigration(), { wrapper: wrapper(mock) }); + + result.current.reset_migration(); + + expect(mockUseRequest('wallet_migration').mutate).toBeCalledWith({ payload: { wallet_migration: 'reset' } }); + }); +}); diff --git a/packages/hooks/src/__tests__/useWalletTransactions.spec.tsx b/packages/hooks/src/__tests__/useWalletTransactions.spec.tsx new file mode 100644 index 000000000000..72982d7b4513 --- /dev/null +++ b/packages/hooks/src/__tests__/useWalletTransactions.spec.tsx @@ -0,0 +1,150 @@ +import * as React from 'react'; +import { APIProvider, useFetch } from '@deriv/api'; +import { mockStore, StoreProvider } from '@deriv/stores'; +import { renderHook } from '@testing-library/react-hooks'; +import useWalletTransactions from '../useWalletTransactions'; + +jest.mock('@deriv/api', () => ({ + ...jest.requireActual('@deriv/api'), + useFetch: jest.fn(), +})); + +const mockUseFetch = useFetch as jest.MockedFunction>; +describe('useWalletsList', () => { + test('should return a list of transactions', () => { + const mock = mockStore({ + client: { + accounts: { CRW909900: { token: '12345' } }, + currency: 'USD', + loginid: 'CRW909900', + }, + }); + + mockUseFetch.mockReturnValue({ + data: { + authorize: { + account_list: [ + { + account_category: 'wallet', + account_type: 'doughflow', + currency: 'USD', + is_virtual: 0, + loginid: 'CRW909900', + }, + ], + loginid: 'CRW909900', + }, + statement: { + transactions: [ + { + action_type: 'deposit', + amount: 25, + balance_after: 25, + transaction_id: 17494415481, + transaction_time: 1685942136, + }, + { + action_type: 'withdrawal', + amount: 750, + balance_after: 0, + transaction_id: 17494415480, + transaction_time: 1685942135, + }, + { + action_type: 'transfer', + amount: 5, + from: { + loginid: 'CRW909900', + }, + to: { + loginid: 'VRTCMOCK0001', + }, + balance_after: 9995, + transaction_id: 17494415484, + transaction_time: 1685942139, + }, + { + action_type: 'reset_balance', + amount: 350, + balance_after: 10000, + transaction_id: 13693003421, + transaction_time: 1685942138, + }, + { + action_type: 'transfer', + amount: 200, + from: { + loginid: 'VRTCMOCK0001', + }, + to: { + loginid: 'CRW909900', + }, + balance_after: 9650, + transaction_id: 17494415483, + transaction_time: 1685855740, + }, + { + action_type: 'deposit', + amount: 1000, + balance_after: 1000, + transaction_id: 17494117539, + transaction_time: 1685769338, + }, + ], + }, + website_status: { + currencies_config: { + USD: { + fractional_digits: 2, + is_deposit_suspended: 0, + is_suspended: 0, + is_withdrawal_suspended: 0, + name: 'US Dollar', + stake_default: 10, + type: 'fiat', + }, + }, + }, + }, + } as unknown as ReturnType); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + + {children} + + ); + + const { result } = renderHook(() => useWalletTransactions('deposit'), { wrapper }); + + expect(result.current.transactions).toEqual([ + { + action_type: 'deposit', + amount: 25, + balance_after: 25, + gradient_class: 'wallet-card__usd-bg', + transaction_id: 17494415481, + transaction_time: 1685942136, + account_category: 'wallet', + account_currency: 'USD', + account_name: 'USD Wallet', + account_type: 'doughflow', + icon: 'IcWalletCurrencyUsd', + icon_type: 'fiat', + }, + { + action_type: 'deposit', + amount: 1000, + balance_after: 1000, + gradient_class: 'wallet-card__usd-bg', + transaction_id: 17494117539, + transaction_time: 1685769338, + account_category: 'wallet', + account_currency: 'USD', + account_name: 'USD Wallet', + account_type: 'doughflow', + icon: 'IcWalletCurrencyUsd', + icon_type: 'fiat', + }, + ]); + }); +}); diff --git a/packages/hooks/src/__tests__/useWalletTransfer.spec.tsx b/packages/hooks/src/__tests__/useWalletTransfer.spec.tsx new file mode 100644 index 000000000000..4ecf2da6538e --- /dev/null +++ b/packages/hooks/src/__tests__/useWalletTransfer.spec.tsx @@ -0,0 +1,252 @@ +import React from 'react'; +import { renderHook } from '@testing-library/react-hooks'; +import useWalletTransfer from '../useWalletTransfer'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import { APIProvider } from '@deriv/api'; + +jest.mock('@deriv/api', () => ({ + ...jest.requireActual('@deriv/api'), + useFetch: jest.fn((name: string) => { + if (name === 'authorize') { + return { + data: { + authorize: { + account_list: [ + { + account_category: 'wallet', + account_type: 'doughflow', + created_at: 1688642811, + currency: 'USD', + is_disabled: 0, + is_selected: true, + is_virtual: 0, + landing_company_name: 'svg', + linked_to: [ + { + loginid: 'MTR100967300', + platform: 'mt5', + }, + { + loginid: 'MTR80057067', + platform: 'mt5', + }, + { + loginid: 'DXR1646584', + platform: 'dxtrade', + }, + { + loginid: 'EZR80001086', + platform: 'derivez', + }, + ], + loginid: 'CRW1030', + }, + ], + }, + }, + }; + } + if (name === 'mt5_login_list') { + return { + data: { + mt5_login_list: [ + { + account_type: 'real', + balance: 0, + country: 'ng', + currency: 'USD', + display_balance: '0.00', + email: 'email@gmail.com', + group: 'real\\p02_ts02\\synthetic\\svg_std_usd\\04', + landing_company_short: 'svg', + leverage: 500, + login: 'MTR100967300', + market_type: 'synthetic', + name: 'Name', + server: 'p02_ts02', + server_info: { + environment: 'Deriv-Server-02', + geolocation: { + group: 'africa_synthetic', + location: 'South Africa', + region: 'Africa', + sequence: 2, + }, + id: 'p02_ts02', + }, + status: null, + sub_account_category: '', + sub_account_type: 'financial', + }, + { + account_type: 'real', + balance: 0, + country: 'ng', + currency: 'USD', + display_balance: '0.00', + email: 'email@gmail.com', + group: 'real\\p02_ts01\\all\\svg_std-sf_usd', + landing_company_short: 'svg', + leverage: 1000, + login: 'MTR80057067', + market_type: 'all', + name: 'Name', + server: 'p02_ts01', + server_info: { + environment: 'Deriv-Server-02', + geolocation: { + group: 'africa_derivez', + location: 'South Africa', + region: 'Africa', + sequence: 2, + }, + id: 'p02_ts01', + }, + status: null, + sub_account_category: 'swap_free', + sub_account_type: 'standard', + }, + ], + }, + }; + } + if (name === 'trading_platform_accounts') { + return { + data: { + trading_platform_accounts: [ + { + account_id: 'DXR1646584', + account_type: 'real', + balance: 0, + currency: 'USD', + display_balance: '0.00', + enabled: 1, + landing_company_short: 'svg', + login: '8807230', + market_type: 'all', + platform: 'dxtrade', + }, + { + account_type: 'real', + balance: 0, + country: 'za', + currency: 'USD', + display_balance: '0.00', + email: 'mei+za1@binary.com', + group: 'real\\p02_ts01\\all\\svg_ez_usd', + landing_company_short: 'svg', + leverage: 1000, + login: 'EZR80001086', + market_type: 'all', + name: 'Baily Pan', + server: 'p02_ts01', + server_info: { + environment: 'Deriv-Server-02', + geolocation: { + group: 'africa_derivez', + location: 'South Africa', + region: 'Africa', + sequence: 2, + }, + id: 'p02_ts01', + }, + }, + ], + }, + }; + } + if (name === 'website_status') { + return { + data: { + website_status: { + currencies_config: { + USD: { + fractional_digits: 2, + name: 'US Dollar', + type: 'fiat', + }, + }, + }, + }, + }; + } + if (name === 'transfer_between_accounts') { + return { + data: { + accounts: [ + { + account_type: 'wallet', + balance: '100.00', + currency: 'USD', + demo_account: 0, + loginid: 'CRW1030', + }, + { + account_type: 'mt5', + balance: '0.00', + currency: 'USD', + demo_account: 0, + loginid: 'MTR100967300', + }, + { + account_type: 'mt5', + balance: '0.00', + currency: 'USD', + demo_account: 0, + loginid: 'MTR80057067', + }, + { + account_type: 'derivez', + balance: '0.00', + currency: 'USD', + demo_account: 0, + loginid: 'EZR80001086', + }, + { + account_type: 'dxtrade', + balance: '0.00', + currency: 'USD', + demo_account: 0, + loginid: 'DXR1646584', + }, + ], + }, + }; + } + + return { data: undefined }; + }), +})); + +describe('useWalletTransfer', () => { + let mock_store: ReturnType, wrapper: ({ children }: { children: JSX.Element }) => JSX.Element; + + beforeEach(() => { + mock_store = mockStore({ + client: { + loginid: 'CRW1030', + accounts: { + CRW1030: { + token: 'token', + }, + }, + }, + }); + + wrapper = ({ children }: { children: JSX.Element }) => ( + + {children} + + ); + }); + + it('from_account should be undefined by default', () => { + const { + result: { + current: { from_account }, + }, + } = renderHook(() => useWalletTransfer(), { wrapper }); + + expect(from_account).toBeUndefined(); + }); +}); diff --git a/packages/hooks/src/__tests__/useWalletsList.spec.tsx b/packages/hooks/src/__tests__/useWalletsList.spec.tsx new file mode 100644 index 000000000000..3ab9ac963f80 --- /dev/null +++ b/packages/hooks/src/__tests__/useWalletsList.spec.tsx @@ -0,0 +1,144 @@ +import * as React from 'react'; +import { APIProvider, useFetch } from '@deriv/api'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import { renderHook } from '@testing-library/react-hooks'; +import useWalletsList from '../useWalletsList'; + +const mockUseFetch = useFetch as jest.MockedFunction>; + +jest.mock('@deriv/api', () => ({ + ...jest.requireActual('@deriv/api'), + useFetch: jest.fn((name: string) => { + if (name === 'authorize') { + return { + data: { + authorize: { + account_list: [ + { + account_category: 'wallet', + currency: 'USD', + is_virtual: 1, + }, + { + account_category: 'wallet', + currency: 'UST', + is_virtual: 0, + }, + { + account_category: 'wallet', + currency: 'BTC', + is_virtual: 0, + }, + { + account_category: 'wallet', + currency: 'AUD', + is_virtual: 0, + }, + { + account_category: 'wallet', + currency: 'ETH', + is_virtual: 0, + }, + ], + }, + }, + }; + } else if (name === 'balance') { + return { + data: { + balance: { + accounts: { + CRW909900: { + balance: 0, + }, + }, + }, + }, + }; + } else if (name === 'website_status') { + return { + data: { + website_status: { + currencies_config: { + AUD: { type: 'fiat' }, + BTC: { type: 'crypto' }, + ETH: { type: 'crypto' }, + UST: { type: 'crypto' }, + USD: { type: 'fiat' }, + }, + }, + }, + }; + } else if (name === 'crypto_config') { + return { + data: { + crypto_config: { + currencies_config: { + BTC: {}, + }, + }, + }, + }; + } + + return undefined; + }), +})); + +describe('useWalletsList', () => { + const wrapper = (mock: ReturnType) => { + const Component = ({ children }: { children: JSX.Element }) => ( + + {children} + + ); + return Component; + }; + + test('should return wallets list for the current loginid', () => { + const mock = mockStore({ client: { accounts: { CRW909900: { token: '12345' } }, loginid: 'CRW909900' } }); + + const { result } = renderHook(() => useWalletsList(), { wrapper: wrapper(mock) }); + + expect(result.current.data?.every(wallet => wallet.account_category === 'wallet')).toEqual(true); + }); + + test('should return sorted wallet list where virtual is the last and crypto is after fiat currency', () => { + const mock = mockStore({ client: { accounts: { CRW909900: { token: '12345' } }, loginid: 'CRW909900' } }); + + const { result } = renderHook(() => useWalletsList(), { wrapper: wrapper(mock) }); + + expect(result.current.data?.map(wallet => wallet.currency)).toEqual(['AUD', 'BTC', 'ETH', 'UST', 'USD']); + }); + + test('should return has_wallet equals to true if the client has at least one wallet', () => { + const mock = mockStore({ client: { accounts: { CRW909900: { token: '12345' } }, loginid: 'CRW909900' } }); + + const { result } = renderHook(() => useWalletsList(), { wrapper: wrapper(mock) }); + + expect(result.current.has_wallet).toEqual(true); + }); + + test("should return has_wallet equals to false if the client doesn't have any wallet", () => { + const mock = mockStore({ client: { accounts: { CR123456: { token: '12345' } }, loginid: 'CR123456' } }); + + // @ts-expect-error need to come up with a way to mock the return type of useFetch + mockUseFetch.mockReturnValue({ + data: { + authorize: { + account_list: [ + { + account_category: 'trading', + currency: 'USD', + is_virtual: 0, + }, + ], + }, + }, + }); + + const { result } = renderHook(() => useWalletsList(), { wrapper: wrapper(mock) }); + + expect(result.current.has_wallet).toEqual(false); + }); +}); diff --git a/packages/hooks/src/index.ts b/packages/hooks/src/index.ts index fd9e68b0f16c..69ba5299f8f0 100644 --- a/packages/hooks/src/index.ts +++ b/packages/hooks/src/index.ts @@ -1,9 +1,13 @@ export { default as useAccountTransferVisible } from './useAccountTransferVisible'; +export { default as useActiveWallet } from './useActiveWallet'; +export { default as useAuthorize } from './useAuthorize'; +export { default as useAvailableWallets } from './useAvailableWallets'; export { default as useCFDAccounts } from './useCFDAccounts'; export { default as useCFDAllAccounts } from './useCFDAllAccounts'; export { default as useCFDDemoAccounts } from './useCFDDemoAccounts'; export { default as useCFDRealAccounts } from './useCFDRealAccounts'; export { default as useCashierLocked } from './useCashierLocked'; +export { default as useContentFlag } from './useContentFlag'; export { default as useCountdown } from './useCountdown'; export { default as useCryptoTransactions } from './useCryptoTransactions'; export { default as useCurrencyConfig } from './useCurrencyConfig'; @@ -13,6 +17,7 @@ export { default as useDepositCryptoAddress } from './useDepositCryptoAddress'; export { default as useDepositFiatAddress } from './useDepositFiatAddress'; export { default as useDepositLocked } from './useDepositLocked'; export { default as useExchangeRate } from './useExchangeRate'; +export { default as useExistingCFDAccounts } from './useExistingCFDAccounts'; export { default as useFeatureFlags } from './useFeatureFlags'; export { default as useFiatAccountList } from './useFiatAccountList'; export { default as useHasActiveRealAccount } from './useHasActiveRealAccount'; @@ -26,9 +31,11 @@ export { default as useHasSwapFreeAccount } from './useHasSwapFreeAccount'; export { default as useHasUSDCurrency } from './useHasUSDCurrency'; export { default as useInputATMFormatter } from './useInputATMFormatter'; export { default as useInputDecimalFormatter } from './useInputDecimalFormatter'; +export { default as useIsAccountStatusPresent } from './useIsAccountStatusPresent'; export { default as useIsP2PEnabled } from './useIsP2PEnabled'; export { default as useIsRealAccountNeededForCashier } from './useIsRealAccountNeededForCashier'; export { default as useIsSystemMaintenance } from './useIsSystemMaintenance'; +export { default as useLocalStorageData } from './useLocalStorageData'; export { default as useNeedAuthentication } from './useNeedAuthentication'; export { default as useNeedFinancialAssessment } from './useNeedFinancialAssessment'; export { default as useNeedPOI } from './useNeedPOI'; @@ -45,8 +52,12 @@ export { default as usePlatformDemoAccount } from './usePlatformDemoAccount'; export { default as usePlatformRealAccounts } from './usePlatformRealAccounts'; export { default as useRealSTPAccount } from './useRealSTPAccount'; export { default as useTotalAccountBalance } from './useTotalAccountBalance'; +export { default as useTransferBetweenAccounts } from './useTransferBetweenAccounts'; export { default as useVerifyEmail } from './useVerifyEmail'; -export { default as useIsAccountStatusPresent } from './useIsAccountStatusPresent'; +export { default as useWalletMigration } from './useWalletMigration'; +export { default as useWalletTransactions } from './useWalletTransactions'; +export { default as useWalletTransfer } from './useWalletTransfer'; +export { default as useWalletsList } from './useWalletsList'; export { default as useStatesList } from './useStatesList'; export { default as useP2PConfig } from './useP2PConfig'; export { default as useIsClientHighRiskForMT5 } from './useIsClientHighRiskForMT5'; diff --git a/packages/hooks/src/useActiveWallet.ts b/packages/hooks/src/useActiveWallet.ts new file mode 100644 index 000000000000..bb238eb3f727 --- /dev/null +++ b/packages/hooks/src/useActiveWallet.ts @@ -0,0 +1,13 @@ +import { useMemo } from 'react'; +import useWalletsList from './useWalletsList'; + +/** A custom hook that returns the wallet object for the current active wallet. */ +const useActiveWallet = () => { + const { data } = useWalletsList(); + const active_wallet = useMemo(() => data?.find(wallet => wallet.is_selected), [data]); + + /** User's current active wallet. */ + return active_wallet; +}; + +export default useActiveWallet; diff --git a/packages/hooks/src/useAuthorize.ts b/packages/hooks/src/useAuthorize.ts new file mode 100644 index 000000000000..e90502d3a06b --- /dev/null +++ b/packages/hooks/src/useAuthorize.ts @@ -0,0 +1,26 @@ +import { useMemo } from 'react'; +import { useFetch } from '@deriv/api'; +import { useStore } from '@deriv/stores'; + +/** A custom hook that authorize the user with the given token. If no token is given, it will use the current token. */ +const useAuthorize = (token?: string) => { + const { client } = useStore(); + const { accounts, loginid = '' } = client; + const current_token = accounts[loginid || '']?.token; + + const { data, ...rest } = useFetch('authorize', { + payload: { authorize: token ?? current_token }, + options: { enabled: Boolean(token ?? current_token) }, + }); + + // Add additional information to the authorize response. + const modified_authorize = useMemo(() => ({ ...data?.authorize }), [data?.authorize]); + + return { + /** The authorize response. */ + data: modified_authorize, + ...rest, + }; +}; + +export default useAuthorize; diff --git a/packages/hooks/src/useAvailableWallets.ts b/packages/hooks/src/useAvailableWallets.ts new file mode 100644 index 000000000000..8ba16d14e136 --- /dev/null +++ b/packages/hooks/src/useAvailableWallets.ts @@ -0,0 +1,76 @@ +import React from 'react'; +import { useFetch } from '@deriv/api'; +import { useStore } from '@deriv/stores'; +import useWalletsList from './useWalletsList'; +import useAuthorize from './useAuthorize'; + +const useAvailableWallets = () => { + const { client, ui } = useStore(); + const { is_dark_mode_on } = ui; + const { is_crypto } = client; + const { data } = useAuthorize(); + + const { data: account_type_data, ...rest } = useFetch('get_account_types', { + payload: { company: data?.landing_company_name === 'virtual' ? 'svg' : data?.landing_company_name }, + options: { enabled: Boolean(data?.landing_company_name) }, + }); + + const { data: added_wallets } = useWalletsList(); + + const sortedWallets = React.useMemo(() => { + if (!account_type_data) return null; + const { crypto, doughflow } = account_type_data?.get_account_types?.wallet || {}; + const crypto_currencies = crypto?.currencies; + const fiat_currencies = doughflow?.currencies; + + if (!crypto_currencies || !fiat_currencies) return null; + const available_currencies = [...fiat_currencies, ...crypto_currencies]; + const non_virtual_wallets = added_wallets?.filter(wallet => !wallet.is_virtual); + + const modified_wallets = non_virtual_wallets?.map(wallet => ({ + currency: wallet.currency, + landing_company_name: wallet.landing_company_name, + is_added: true, + gradient_card_class: wallet.gradient_card_class, + })); + + const available_wallets = available_currencies + .filter(currency => !modified_wallets?.some(wallet => wallet.currency === currency)) + .map(currency => ({ + currency, + landing_company_name: data?.landing_company_name === 'virtual' ? 'svg' : data?.landing_company_name, + is_added: false, + gradient_card_class: `wallet-card__${currency.toLowerCase()}-bg${is_dark_mode_on ? '--dark' : ''}`, + })); + + // Sort the unadded wallets alphabetically by fiat, crypto, then virtual + available_wallets?.sort((a, b) => { + if (is_crypto(a.currency) !== is_crypto(b.currency)) { + return is_crypto(a.currency) ? 1 : -1; + } + + return (a.currency || 'USD').localeCompare(b.currency || 'USD'); + }); + + // Sort the added wallets alphabetically by fiat, crypto, then virtual (if any) + if (Array.isArray(modified_wallets)) { + modified_wallets?.sort((a, b) => { + if (is_crypto(a.currency) !== is_crypto(b.currency)) { + return is_crypto(a.currency) ? 1 : -1; + } + + return (a.currency || 'USD').localeCompare(b.currency || 'USD'); + }); + return [...available_wallets, ...modified_wallets]; + } + + return [...available_wallets]; + }, [added_wallets, account_type_data, data?.landing_company_name, is_dark_mode_on, is_crypto]); + + return { + ...rest, + data: sortedWallets, + }; +}; + +export default useAvailableWallets; diff --git a/packages/hooks/src/useContentFlag.ts b/packages/hooks/src/useContentFlag.ts new file mode 100644 index 000000000000..b3993c0def39 --- /dev/null +++ b/packages/hooks/src/useContentFlag.ts @@ -0,0 +1,17 @@ +import { useStore } from '@deriv/stores'; + +const useContentFlag = () => { + const { traders_hub } = useStore(); + const { content_flag } = traders_hub; + + return { + is_low_risk_cr_non_eu: content_flag === 'low_risk_cr_non_eu', + is_low_risk_cr_eu: content_flag === 'low_risk_cr_eu', + is_high_risk_cr: content_flag === 'high_risk_cr', + is_cr_demo: content_flag === 'cr_demo', + is_eu_demo: content_flag === 'eu_demo', + is_eu_real: content_flag === 'eu_real', + }; +}; + +export default useContentFlag; diff --git a/packages/hooks/src/useCurrencyConfig.ts b/packages/hooks/src/useCurrencyConfig.ts index bffc564025d9..db9b3e8a4585 100644 --- a/packages/hooks/src/useCurrencyConfig.ts +++ b/packages/hooks/src/useCurrencyConfig.ts @@ -4,7 +4,7 @@ import { useFetch } from '@deriv/api'; /** @deprecated Use `useCurrencyConfig` from `@deriv/api` package instead. */ const useCurrencyConfig = () => { const { data: website_status_data } = useFetch('website_status'); - const { data: crypto_config_data } = useFetch('crypto_config'); + const { data: crypto_config_data, ...rest } = useFetch('crypto_config'); const currencies_config = useMemo(() => { if (!website_status_data?.website_status?.currencies_config) return undefined; @@ -82,6 +82,7 @@ const useCurrencyConfig = () => { getConfig, /** Available currencies and their information */ currencies_config, + ...rest, }; }; diff --git a/packages/hooks/src/useExistingCFDAccounts.ts b/packages/hooks/src/useExistingCFDAccounts.ts new file mode 100644 index 000000000000..2a1961a494cf --- /dev/null +++ b/packages/hooks/src/useExistingCFDAccounts.ts @@ -0,0 +1,106 @@ +import { useMemo } from 'react'; +import { useFetch } from '@deriv/api'; +import useActiveWallet from './useActiveWallet'; +import { useStore } from '@deriv/stores'; + +type TAccount = { + cfd_type?: 'mt5' | 'derivez' | 'dxtrade'; + market_type?: 'financial' | 'synthetic' | 'all'; +}; + +const getAccountIcon = ({ cfd_type, market_type }: TAccount) => { + switch (cfd_type) { + case 'mt5': { + switch (market_type) { + case 'financial': + return 'IcRebrandingMt5FinancialDashboard'; + case 'synthetic': + return 'IcRebrandingMt5DerivedDashboard'; + case 'all': + return 'IcRebrandingMt5SwapFree'; + default: + return 'IcRebrandingDmt5Dashboard'; + } + } + case 'derivez': + return 'IcRebrandingDerivEz'; + case 'dxtrade': + return 'IcRebrandingDerivX'; + default: + return ''; + } +}; + +/** + * @description This hook is used to get the created CFD accounts of the user. + */ +const useExistingCFDAccounts = () => { + const { traders_hub } = useStore(); + const { combined_cfd_mt5_accounts } = traders_hub; + const wallet = useActiveWallet(); + const { data: mt5, ...mt5_rest } = useFetch('mt5_login_list'); + const { data: derivez, ...derivez_rest } = useFetch('trading_platform_accounts', { + payload: { platform: 'derivez' }, + }); + const { data: dxtrade, ...dxtrade_rest } = useFetch('trading_platform_accounts', { + payload: { platform: 'dxtrade' }, + }); + + /** + * + * @description This is the modified MT5 accounts that will be used in the CFD account creation. + */ + const modified_mt5_accounts = useMemo(() => { + const getAccountInfo = (login?: string) => { + return { + platform: wallet?.linked_to?.find(linked => linked.loginid === login)?.platform, + icon: combined_cfd_mt5_accounts?.find(cfd => cfd.login === login)?.icon, + description: combined_cfd_mt5_accounts?.find(cfd => cfd.login === login)?.description, + name: combined_cfd_mt5_accounts?.find(cfd => cfd.login === login)?.name, + sub_title: combined_cfd_mt5_accounts?.find(cfd => cfd.login === login)?.sub_title, + action_type: 'multi-action', + }; + }; + + return mt5?.mt5_login_list?.map(account => ({ + ...account, + ...getAccountInfo(account.login), + loginid: account.login, + transfer_icon: getAccountIcon({ cfd_type: 'mt5', ...account }), + })); + }, [mt5?.mt5_login_list, wallet?.linked_to, combined_cfd_mt5_accounts]); + + const modified_derivez_accounts = useMemo( + () => + derivez?.trading_platform_accounts?.map(account => ({ + ...account, + loginid: account.login, + transfer_icon: getAccountIcon({ cfd_type: 'derivez' }), + })), + [derivez?.trading_platform_accounts] + ); + const modified_dxtrade_accounts = useMemo( + () => + dxtrade?.trading_platform_accounts?.map(account => ({ + ...account, + loginid: account.account_id, + transfer_icon: getAccountIcon({ cfd_type: 'dxtrade' }), + })), + [dxtrade?.trading_platform_accounts] + ); + const data = useMemo( + () => ({ + mt5_accounts: modified_mt5_accounts || [], + dxtrade_accounts: modified_dxtrade_accounts || [], + derivez_accounts: modified_derivez_accounts || [], + }), + [modified_mt5_accounts, modified_dxtrade_accounts, modified_derivez_accounts] + ); + + return { + data, + isSuccess: [mt5_rest.isSuccess, dxtrade_rest.isSuccess, derivez_rest.isSuccess].every(Boolean), + }; +}; + +export default useExistingCFDAccounts; diff --git a/packages/hooks/src/useLocalStorageData.ts b/packages/hooks/src/useLocalStorageData.ts new file mode 100644 index 000000000000..b3102dda702d --- /dev/null +++ b/packages/hooks/src/useLocalStorageData.ts @@ -0,0 +1,29 @@ +import React from 'react'; +import { getLocalStorage } from '@deriv/utils'; + +/** + * Hook that manages a localStorage value as a React state. + * @template T - The generic type of the localStorage value. + * @param {string} key - The localStorage key. + * @param {T} [fallback_value] - Optional fallback value if the key does not exist or has no value. + * @returns - An array containing the current value, a function to update the value, and a function to clear the value. + */ +const useLocalStorageData = ( + key: string, + fallback_value?: T +): [T | null, React.Dispatch>, VoidFunction] => { + const [data, setData] = React.useState(getLocalStorage(key) ?? fallback_value ?? null); + + React.useEffect(() => { + localStorage.setItem(key, JSON.stringify(data)); + }, [key, data]); + + const clearData = () => { + localStorage.removeItem(key); + setData(fallback_value ?? null); + }; + + return [data, setData, clearData]; +}; + +export default useLocalStorageData; diff --git a/packages/hooks/src/usePlatformDemoAccount.ts b/packages/hooks/src/usePlatformDemoAccount.ts index f416f22aa133..b5f1d41aaf61 100644 --- a/packages/hooks/src/usePlatformDemoAccount.ts +++ b/packages/hooks/src/usePlatformDemoAccount.ts @@ -8,7 +8,10 @@ import { useStore } from '@deriv/stores'; const usePlatformDemoAccount = () => { const { client } = useStore(); const { accounts } = client; - const account_list = Object.keys(accounts).map(loginid => accounts[loginid]); + const account_list = Object.keys(accounts).map(loginid => ({ + ...accounts[loginid], + loginid, + })); const platform_demo_account = account_list.find(account => account.is_virtual); diff --git a/packages/hooks/src/usePlatformRealAccounts.ts b/packages/hooks/src/usePlatformRealAccounts.ts index 3572592a0ef9..dd51f12ffac7 100644 --- a/packages/hooks/src/usePlatformRealAccounts.ts +++ b/packages/hooks/src/usePlatformRealAccounts.ts @@ -9,7 +9,10 @@ const usePlatformRealAccounts = () => { const { client, traders_hub } = useStore(); const { accounts } = client; const { is_eu_user } = traders_hub; - const account_list = Object.keys(accounts).map(loginid => accounts[loginid]); + const account_list = Object.keys(accounts).map(loginid => ({ + ...accounts[loginid], + loginid, + })); const platform_real_accounts = account_list.filter(account => { const is_maltainvest = diff --git a/packages/hooks/src/useTransferBetweenAccounts.ts b/packages/hooks/src/useTransferBetweenAccounts.ts new file mode 100644 index 000000000000..35f232ab805c --- /dev/null +++ b/packages/hooks/src/useTransferBetweenAccounts.ts @@ -0,0 +1,144 @@ +import { useMemo } from 'react'; +import { useStore } from '@deriv/stores'; +import { useFetch } from '@deriv/api'; +import useActiveWallet from './useActiveWallet'; +import useCurrencyConfig from './useCurrencyConfig'; +import useExistingCFDAccounts from './useExistingCFDAccounts'; +import useWalletsList from './useWalletsList'; + +const useTransferBetweenAccounts = () => { + const { ui } = useStore(); + const { is_dark_mode_on } = ui; + + const active_wallet = useActiveWallet(); + + const { data: wallets } = useWalletsList(); + + const { getConfig } = useCurrencyConfig(); + + const trading_apps_icon = is_dark_mode_on ? 'IcWalletOptionsDark' : 'IcWalletOptionsLight'; + + const { + data: { derivez_accounts, dxtrade_accounts, mt5_accounts }, + isSuccess: is_cfd_accounts_loaded, + } = useExistingCFDAccounts(); + + const { data, ...rest } = useFetch('transfer_between_accounts', { + payload: { accounts: 'all' }, + options: { enabled: is_cfd_accounts_loaded }, + }); + + const modified_transfer_accounts = useMemo(() => { + const all_linked_cfd_accounts = [...derivez_accounts, ...dxtrade_accounts, ...mt5_accounts]; + + const getAccountType = (is_demo?: number, currency?: string): 'fiat' | 'crypto' | 'demo' => { + if (is_demo) return 'demo'; + return getConfig(currency || '')?.is_crypto ? 'crypto' : 'fiat'; + }; + + const accounts = data?.accounts?.map(account => { + return { + ...account, + active_wallet_icon: active_wallet?.icon, + balance: parseFloat( + Number(account.balance).toFixed(getConfig(account.currency || '')?.fractional_digits) + ), + display_currency_code: getConfig(account.currency || '')?.display_code, + is_demo: Boolean(account?.demo_account), + shortcode: active_wallet?.landing_company_name, + type: getAccountType(account.demo_account, account.currency), + }; + }); + + return { + trading_accounts: + accounts?.reduce( + (trading_accounts, account) => { + if (account.account_type === 'wallet') return trading_accounts; + if (!account.loginid) return trading_accounts; + + const cfd_icon = all_linked_cfd_accounts.find( + cfd_account => account.loginid && cfd_account.loginid?.includes(account.loginid) + )?.transfer_icon; + + trading_accounts[account.loginid] = { + ...account, + gradient_class: active_wallet?.gradient_card_class, + icon: account.account_type === 'trading' ? trading_apps_icon : cfd_icon, + ...(account.account_type === 'mt5' && { + mt5_market_type: mt5_accounts?.find( + mt5_account => account.loginid && mt5_account.loginid?.includes(account.loginid) + )?.market_type, + }), + }; + + return trading_accounts; + }, + {} as Record< + string, + NonNullable< + typeof accounts[number] & { + gradient_class?: `wallet-card__${string}`; + icon?: string; + mt5_market_type?: 'all' | 'financial' | 'synthetic'; + } + > + > + ) || {}, + wallet_accounts: + accounts?.reduce( + (wallet_accounts, wallet) => { + if (wallet.account_type !== 'wallet') return wallet_accounts; + if (!wallet.loginid) return wallet_accounts; + + const available_wallet = wallets?.find(acc => acc.loginid === wallet.loginid); + + wallet_accounts[wallet.loginid] = { + ...wallet, + icon: available_wallet?.icon, + gradient_class: available_wallet?.gradient_card_class, + }; + + return wallet_accounts; + }, + {} as Record< + string, + NonNullable< + typeof accounts[number] & { + gradient_class?: `wallet-card__${string}`; + icon?: string; + } + > + > + ) || {}, + }; + }, [ + active_wallet?.gradient_card_class, + active_wallet?.icon, + active_wallet?.landing_company_name, + data?.accounts, + derivez_accounts, + dxtrade_accounts, + getConfig, + mt5_accounts, + trading_apps_icon, + wallets, + ]); + + const modified_active_wallet = useMemo(() => { + return active_wallet?.loginid + ? { + ...modified_transfer_accounts.wallet_accounts[active_wallet?.loginid], + } + : undefined; + }, [active_wallet?.loginid, modified_transfer_accounts.wallet_accounts]); + + return { + ...rest, + active_wallet: modified_active_wallet, + trading_accounts: modified_transfer_accounts.trading_accounts, + wallet_accounts: modified_transfer_accounts.wallet_accounts, + }; +}; + +export default useTransferBetweenAccounts; diff --git a/packages/hooks/src/useWalletMigration.ts b/packages/hooks/src/useWalletMigration.ts new file mode 100644 index 000000000000..1c2d4a7186d3 --- /dev/null +++ b/packages/hooks/src/useWalletMigration.ts @@ -0,0 +1,56 @@ +import { useCallback } from 'react'; +import { useAuthorize, useFetch, useInvalidateQuery, useRequest } from '@deriv/api'; +import { useStore } from '@deriv/stores'; + +/** A custom hook to get the status of wallet_migration API and to start/reset the migration process */ +const useWalletMigration = () => { + // TODO: delete it later, it's a temporary solution + // because we have to check for authorize from client store before doing API call + // This hook will be refactored later for subscribe when BE is ready + const { client } = useStore(); + const { is_authorize } = client; + + const invalidate = useInvalidateQuery(); + + /** Make a request to wallet_migration API and onSuccess it will invalidate the cached data */ + const { mutate } = useRequest('wallet_migration', { onSuccess: () => invalidate('wallet_migration') }); + + const { isSuccess } = useAuthorize(); + + /** Fetch the wallet_migration API and refetch it every second if the status is in_progress */ + const { data } = useFetch('wallet_migration', { + payload: { wallet_migration: 'state' }, + options: { + refetchInterval: response => (response?.wallet_migration?.state === 'in_progress' ? 500 : false), + // delete it later + enabled: is_authorize && isSuccess, + }, + }); + + const start_migration = useCallback(() => mutate({ payload: { wallet_migration: 'start' } }), [mutate]); + + const reset_migration = useCallback(() => mutate({ payload: { wallet_migration: 'reset' } }), [mutate]); + + const state = data?.wallet_migration?.state; + + return { + /** The status of the wallet_migration API */ + state, + /** A boolean to check if the status is not_eligible */ + is_ineligible: state === 'ineligible', + /** A boolean to check if the status is eligible */ + is_eligible: state === 'eligible', + /** A boolean to check if the status is in_progress */ + is_in_progress: state === 'in_progress', + /** A boolean to check if the status is completed */ + is_migrated: state === 'migrated', + /** A boolean to check if the status is failed */ + is_failed: state === 'failed', + /** Sends a request to wallet_migration API to start the migration process */ + start_migration, + /** Sends a request to wallet_migration API to reset the migration process */ + reset_migration, + }; +}; + +export default useWalletMigration; diff --git a/packages/hooks/src/useWalletTransactions.ts b/packages/hooks/src/useWalletTransactions.ts new file mode 100644 index 000000000000..847ada643a3e --- /dev/null +++ b/packages/hooks/src/useWalletTransactions.ts @@ -0,0 +1,336 @@ +import { useCallback, useMemo } from 'react'; +import { useStore } from '@deriv/stores'; +import { getWalletCurrencyIcon } from '@deriv/utils'; +import useActiveWallet from './useActiveWallet'; +import useCurrencyConfig from './useCurrencyConfig'; +import usePlatformAccounts from './usePlatformAccounts'; +import useWalletsList from './useWalletsList'; + +const trading_accounts_display_prefixes = { + standard: 'Deriv Apps', + mt5: 'MT5', + dxtrade: 'Deriv X', + binary: 'Binary', +} as const; + +const landing_company_display_shortcodes = { + svg: 'SVG', + malta: 'Malta', +} as const; + +const useWalletTransactions = ( + action_type: '' | 'deposit' | 'withdrawal' | 'initial_fund' | 'reset_balance' | 'transfer' +) => { + const { + client: { loginid, landing_company_shortcode: shortcode }, + ui: { is_dark_mode_on }, + } = useStore(); + const { data: wallets } = useWalletsList(); + const current_wallet = useActiveWallet(); + let { demo: demo_platform_account } = usePlatformAccounts(); + const { real: real_platform_accounts } = usePlatformAccounts(); + + // TODO remove these mocks when we're to switch to API data + demo_platform_account = { + account_category: 'trading', + account_type: 'standard', + currency: 'USD', + loginid: 'VRTCMOCK0001', + is_virtual: 1, + landing_company_shortcode: shortcode as 'svg' | 'malta', + token: '', + }; + real_platform_accounts.push({ + account_category: 'trading', + account_type: 'standard', + currency: 'USD', + loginid: 'CRMOCK0001', + is_virtual: 0, + landing_company_shortcode: shortcode as 'svg' | 'malta', + token: '', + }); + if (wallets && current_wallet) + wallets.push({ + account_type: 'crypto', + balance: 0, + currency: 'BTC', + gradient_header_class: 'wallet-header__btc-bg', + gradient_card_class: `wallet-card__btc-bg${is_dark_mode_on ? '--dark' : ''}`, + is_demo: !!current_wallet.is_virtual, + is_disabled: 0, + is_malta_wallet: false, + is_selected: false, + is_virtual: current_wallet.is_virtual, + landing_company_name: 'svg', + loginid: 'CRWMOCK00042', + currency_config: undefined, + icon: 'IcWalletCurrencyBtc', + wallet_currency_type: 'BTC', + }); + const accounts = useMemo( + () => [demo_platform_account, ...real_platform_accounts], + [demo_platform_account, real_platform_accounts] + ); + const { getConfig } = useCurrencyConfig(); + + const getTradingAccountName = useCallback( + ( + account_type: 'standard' | 'mt5' | 'dxtrade' | 'binary', + is_virtual: boolean, + landing_company_shortcode: 'svg' | 'malta' + ) => { + return `${trading_accounts_display_prefixes[account_type]} ${ + is_virtual ? 'Demo' : `(${landing_company_display_shortcodes[landing_company_shortcode]})` + } account`; + }, + [] + ); + + // TODO remove this mock when we're to switch to API data + const mock_transactions = current_wallet?.is_virtual + ? [ + { + action_type: 'transfer', + amount: 5, + from: { + loginid, + }, + to: { + loginid: 'VRTCMOCK0001', + }, + app_id: {}, + balance_after: 9995, + transaction_id: 17494415484, + transaction_time: 1685942139, + }, + { + action_type: 'reset_balance', + amount: 350, + balance_after: 10000, + transaction_id: 13693003421, + transaction_time: 1685942138, + }, + { + action_type: 'transfer', + amount: 200, + from: { + loginid: 'VRTCMOCK0001', + }, + to: { + loginid, + }, + balance_after: 9650, + transaction_id: 17494415483, + transaction_time: 1685855740, + }, + { + action_type: 'transfer', + amount: 550, + from: { + loginid, + }, + to: { + loginid: 'VRTCMOCK0001', + }, + app_id: {}, + balance_after: 9450, + transaction_id: 17494415482, + transaction_time: 1685855739, + }, + { + action_type: 'initial_fund', + amount: 10000, + balance_after: 10000, + transaction_id: 13693011401, + transaction_time: 1685855738, + }, + ] + : [ + { + action_type: 'transfer', + amount: 5, + from: { + loginid, + }, + to: { + loginid: 'CRMOCK0001', + }, + balance_after: 0, + transaction_id: 17494117541, + transaction_time: 1685942138, + }, + { + action_type: 'transfer', + amount: 20, + from: { + loginid, + }, + to: { + loginid: 'CRWMOCK00042', + }, + balance_after: 5, + transaction_id: 17494415489, + transaction_time: 1685942137, + }, + { + action_type: 'deposit', + amount: 25, + balance_after: 25, + transaction_id: 17494415481, + transaction_time: 1685942136, + }, + { + action_type: 'withdrawal', + amount: 750, + balance_after: 0, + transaction_id: 17494415480, + transaction_time: 1685942135, + }, + { + action_type: 'transfer', + amount: 100, + from: { + loginid: 'CRMOCK0001', + }, + to: { + loginid, + }, + balance_after: 750, + transaction_id: 17494415479, + transaction_time: 1685855738, + }, + { + action_type: 'transfer', + amount: 200, + from: { + loginid: 'CRWMOCK00042', + }, + to: { + loginid, + }, + balance_after: 650, + transaction_id: 17494117541, + transaction_time: 1685855737, + }, + { + action_type: 'transfer', + amount: 550, + from: { + loginid, + }, + to: { + loginid: 'CRMOCK0001', + }, + balance_after: 450, + transaction_id: 17494117540, + transaction_time: 1685855736, + }, + { + action_type: 'deposit', + amount: 1000, + balance_after: 1000, + transaction_id: 17494117539, + transaction_time: 1685769338, + }, + ]; + + // const { isLoading, isSuccess } = useFetch('statement', { + // options: { keepPreviousData: true }, + // payload: { action_type: }, + // }); + + // TODO: un-comment this code when we're to switch to API data + // const transactions = data?.statement?.transactions?.filter( + // el => + // !!el.action_type && + // ['deposit', 'withdrawal', 'initial_fund', 'reset_balance', 'transfer'].includes(el.action_type) + // ) as TWalletTransaction[]; + + const transactions = useMemo( + () => mock_transactions.filter(el => !action_type || el.action_type === action_type), + [action_type, mock_transactions] + ); + + const getTransferAccountName = useCallback( + (other_account: Exclude) => { + if (other_account.account_category === 'wallet') { + const wallet = wallets?.find(el => el.loginid === other_account.loginid); + return `${wallet?.is_virtual ? 'Demo ' : ''}${wallet?.currency} ${'Wallet'}`; + } + return getTradingAccountName( + other_account.account_type as 'standard' | 'mt5' | 'dxtrade' | 'binary', + !!other_account.is_virtual, + other_account.landing_company_shortcode as 'svg' | 'malta' + ); + }, + [getTradingAccountName, wallets] + ); + + const modified_transactions = useMemo( + () => + wallets && current_wallet + ? transactions + .map(transaction => { + if ( + transaction.amount === undefined || + transaction.balance_after === undefined || + transaction.action_type === undefined + ) + return null; + + let account_category = 'wallet'; + let account_type = current_wallet.account_type; + let account_name = `${current_wallet.is_virtual ? 'Demo ' : ''}${ + current_wallet.currency + } ${'Wallet'}`; + let account_currency = current_wallet.currency; + let gradient_class = current_wallet.gradient_card_class; + let icon = getWalletCurrencyIcon( + current_wallet.is_virtual ? 'demo' : current_wallet.currency || 'USD', + is_dark_mode_on + ); + if (transaction.action_type === 'transfer') { + const other_loginid = + transaction.to?.loginid === loginid + ? transaction.from?.loginid + : transaction.to?.loginid; + if (!other_loginid) return null; + const other_account = accounts.find(el => el?.loginid === other_loginid); + if (!other_account?.currency || !other_account?.account_type) return null; + account_category = other_account.account_category || 'wallet'; + account_currency = other_account.currency; + account_name = getTransferAccountName(other_account); + account_type = other_account.account_type; + gradient_class = `wallet-card__${ + other_account.is_virtual === 1 ? 'demo' : other_account?.currency?.toLowerCase() + }-bg${is_dark_mode_on ? '--dark' : ''}`; + icon = getWalletCurrencyIcon( + other_account.is_virtual ? 'demo' : other_account.currency || '', + is_dark_mode_on, + false + ); + } + const currency_config = getConfig(account_currency || ''); + const is_crypto = currency_config?.is_crypto; + const icon_type = is_crypto || current_wallet.is_virtual ? 'crypto' : 'fiat'; + + return { + ...transaction, + account_category, + account_currency, + account_name, + account_type, + gradient_class, + icon, + icon_type, + }; + }) + .filter((value: T | null): value is T => value !== null) + : [], + [accounts, current_wallet, getConfig, getTransferAccountName, is_dark_mode_on, loginid, transactions, wallets] + ); + + return { transactions: modified_transactions, isLoading: false, isSuccess: true }; +}; + +export default useWalletTransactions; diff --git a/packages/hooks/src/useWalletTransfer.ts b/packages/hooks/src/useWalletTransfer.ts new file mode 100644 index 000000000000..4f769ee57b43 --- /dev/null +++ b/packages/hooks/src/useWalletTransfer.ts @@ -0,0 +1,54 @@ +import { useMemo, useState, useEffect } from 'react'; +import useTransferBetweenAccounts from './useTransferBetweenAccounts'; + +const useWalletTransfer = () => { + const { + active_wallet, + trading_accounts, + wallet_accounts, + isLoading: is_accounts_loading, + } = useTransferBetweenAccounts(); + + const [from_account, setFromAccount] = useState(); + const [to_account, setToAccount] = useState(); + + const to_account_list = useMemo(() => { + if (!from_account?.loginid) return { trading_accounts: {}, wallet_accounts: {} }; + if (!active_wallet?.loginid) return { trading_accounts: {}, wallet_accounts: {} }; + + if (from_account?.loginid === active_wallet?.loginid) { + return { + trading_accounts, + wallet_accounts: Object.fromEntries( + Object.entries(wallet_accounts).filter( + ([key]) => active_wallet?.loginid && !key.includes(active_wallet?.loginid) + ) + ), + }; + } + return { trading_accounts: {}, wallet_accounts: { [active_wallet?.loginid]: active_wallet } }; + }, [active_wallet, from_account?.loginid, trading_accounts, wallet_accounts]); + + //this useEffect populates from/to accounts with updated values, if they were updated in the background + useEffect(() => { + setFromAccount(acc => { + return acc?.loginid ? { ...trading_accounts, ...wallet_accounts }[acc?.loginid] : undefined; + }); + setToAccount(acc => { + return acc?.loginid ? { ...trading_accounts, ...wallet_accounts }[acc?.loginid] : undefined; + }); + }, [setFromAccount, setToAccount, trading_accounts, wallet_accounts]); + + return { + active_wallet, + is_accounts_loading, + from_account, + to_account, + to_account_list, + transfer_accounts: { trading_accounts, wallet_accounts }, + setFromAccount, + setToAccount, + }; +}; + +export default useWalletTransfer; diff --git a/packages/hooks/src/useWalletsList.ts b/packages/hooks/src/useWalletsList.ts new file mode 100644 index 000000000000..470be9990895 --- /dev/null +++ b/packages/hooks/src/useWalletsList.ts @@ -0,0 +1,149 @@ +import { useMemo } from 'react'; +import { useFetch } from '@deriv/api'; +import { useStore } from '@deriv/stores'; +import useAuthorize from './useAuthorize'; +import useCurrencyConfig from './useCurrencyConfig'; + +const currency_to_icon_mapper: Record> = { + Demo: { + dark: 'IcWalletDerivDemoDark', + light: 'IcWalletDerivDemoLight', + }, + USD: { + dark: 'IcWalletCurrencyUsd', + light: 'IcWalletCurrencyUsd', + }, + EUR: { + dark: 'IcWalletCurrencyEur', + light: 'IcWalletCurrencyEur', + }, + AUD: { + dark: 'IcWalletCurrencyAud', + light: 'IcWalletCurrencyAud', + }, + GBP: { + dark: 'IcWalletCurrencyGbp', + light: 'IcWalletCurrencyGbp', + }, + BTC: { + dark: 'IcWalletBitcoinDark', + light: 'IcWalletBitcoinLight', + }, + ETH: { + dark: 'IcWalletEthereumDark', + light: 'IcWalletEthereumLight', + }, + USDT: { + dark: 'IcWalletTetherDark', + light: 'IcWalletTetherLight', + }, + eUSDT: { + dark: 'IcWalletTetherDark', + light: 'IcWalletTetherLight', + }, + tUSDT: { + dark: 'IcWalletTetherDark', + light: 'IcWalletTetherLight', + }, + UST: { + dark: 'IcWalletTetherDark', + light: 'IcWalletTetherLight', + }, + LTC: { + dark: 'IcWalletLiteCoinDark', + light: 'IcWalletLiteCoinLight', + }, + USDC: { + dark: 'IcWalletUsdCoinDark', + light: 'IcWalletUsdCoinLight', + }, +}; + +/** A custom hook to get the list of wallets for the current user. */ +const useWalletsList = () => { + const { ui, client } = useStore(); + const { is_dark_mode_on } = ui; + const { is_authorize } = client; + const { getConfig } = useCurrencyConfig(); + + const { data: authorize_data, isSuccess, ...rest } = useAuthorize(); + const { data: balance_data } = useFetch('balance', { + payload: { account: 'all' }, + options: { enabled: is_authorize && isSuccess }, + }); + + // Filter out non-wallet accounts. + const wallets = useMemo( + () => authorize_data?.account_list?.filter(account => account.account_category === 'wallet'), + [authorize_data?.account_list] + ); + + // Add balance to each wallet. + const wallets_with_balance = useMemo( + () => + wallets?.map(wallet => ({ + ...wallet, + /** Wallet balance */ + balance: balance_data?.balance?.accounts?.[wallet.loginid || '']?.balance || 0, + })), + [balance_data?.balance?.accounts, wallets] + ); + + // Add additional information to each wallet. + const modified_wallets = useMemo(() => { + return wallets_with_balance?.map(wallet => { + const wallet_currency_type = wallet.is_virtual === 1 ? 'Demo' : wallet.currency || ''; + const wallet_gradient_class_name = `${wallet_currency_type.toLowerCase()}-bg${ + is_dark_mode_on ? '--dark' : '' + }`; + const wallet_icon = currency_to_icon_mapper[wallet_currency_type]; + + return { + ...wallet, + /** Indicating whether the wallet is the currently selected wallet. */ + is_selected: wallet.loginid === authorize_data?.loginid, + /** Indicating whether the wallet is a virtual-money wallet. */ + is_demo: wallet.is_virtual === 1, + /** Returns the wallet's currency type. ex: `Demo`, `USD`, etc. */ + wallet_currency_type, + /** Landing company shortcode the account belongs to. */ + landing_company_name: wallet.landing_company_name?.replace('maltainvest', 'malta'), + /** Indicating whether the wallet is a maltainvest wallet. */ + is_malta_wallet: wallet.landing_company_name === 'malta', + /** The gradient class name for the wallet header background. */ + gradient_header_class: `wallet-header__${wallet_gradient_class_name}`, + /** The gradient class name for the wallet card background. */ + gradient_card_class: `wallet-card__${wallet_gradient_class_name}`, + /** Wallet's currency config information */ + currency_config: wallet.currency ? getConfig(wallet.currency) : undefined, + /** Local asset name for the wallet icon. ex: `IcWalletCurrencyUsd` for `USD` */ + icon: is_dark_mode_on ? wallet_icon.dark : wallet_icon.light, + } as const; + }); + }, [getConfig, is_dark_mode_on, authorize_data?.loginid, wallets_with_balance]); + + // Sort wallets alphabetically by fiat, crypto, then virtual. + const sorted_wallets = useMemo(() => { + if (!modified_wallets) return []; + + return [...modified_wallets].sort((a, b) => { + if (a.is_virtual !== b.is_virtual) { + return a.is_virtual ? 1 : -1; + } else if (a.currency_config?.is_crypto !== b.currency_config?.is_crypto) { + return a.currency_config?.is_crypto ? 1 : -1; + } + + return (a.currency || 'USD').localeCompare(b.currency || 'USD'); + }); + }, [modified_wallets]); + + return { + /** List of wallets for current user. */ + data: sorted_wallets, + /** Indicating whether the user has a wallet */ + has_wallet: sorted_wallets && sorted_wallets.length > 0, + ...rest, + }; +}; + +export default useWalletsList; diff --git a/packages/p2p/crowdin/messages.json b/packages/p2p/crowdin/messages.json index 1ee989577557..6cdb3364a717 100644 --- a/packages/p2p/crowdin/messages.json +++ b/packages/p2p/crowdin/messages.json @@ -1 +1 @@ -{"6794664":"Ads that match your Deriv P2P balance and limit.","19789721":"Nobody has blocked you. Yay!","24711354":"Total orders <0>30d | <1>lifetime","47573834":"Fixed rate (1 {{account_currency}})","50672601":"Bought","51881712":"You already have an ad with the same exchange rate for this currency pair and order type.

Please set a different rate for your ad.","55916349":"All","68867477":"Order ID {{ id }}","81450871":"We couldn’t find that page","121738739":"Send","122280248":"Avg release time <0>30d","134205943":"Your ads with fixed rates have been deactivated. Set floating rates to reactivate them.","140800401":"Float","145959105":"Choose a nickname","150156106":"Save changes","159757877":"You won't see {{advertiser_name}}'s ads anymore and they won't be able to place orders on your ads.","170072126":"Seen {{ duration }} days ago","173939998":"Avg. pay time <0>30d","197477687":"Edit {{ad_type}} ad","203271702":"Try again","231473252":"Preferred currency","233677840":"of the market rate","246815378":"Once set, your nickname cannot be changed.","276261353":"Avg pay time <0>30d","277542386":"Please use <0>live chat to contact our Customer Support team for help.","316725580":"You can no longer rate this transaction.","323002325":"Post ad","324970564":"Seller's contact details","338910048":"You will appear to other users as","358133589":"Unblock {{advertiser_name}}?","364681129":"Contact details","367579676":"Blocked","392469164":"You have blocked {{advertiser_name}}.","416167062":"You'll receive","424668491":"expired","439264204":"Please set a different minimum and/or maximum order limit.

The range of your ad should not overlap with any of your active ads.","452752527":"Rate (1 {{ currency }})","459886707":"E-wallets","460477293":"Enter message","464044457":"Buyer's nickname","473688701":"Enter a valid amount","476023405":"Didn't receive the email?","488150742":"Resend email","498500965":"Seller's nickname","500514593":"Hide my ads","501523417":"You have no orders.","517202770":"Set fixed rate","523301614":"Release {{amount}} {{currency}}","525380157":"Buy {{offered_currency}} order","531912261":"Bank name, account number, beneficiary name","554135844":"Edit","555447610":"You won't be able to change your buy and sell limits again after this. Do you want to continue?","560402954":"User rating","565060416":"Exchange rate","580715136":"Please register with us!","587882987":"Advertisers","611376642":"Clear","612069973":"Would you recommend this buyer?","628581263":"The {{local_currency}} market rate has changed.","639382772":"Please upload supported file type.","649549724":"I’ve not received any payment.","654193846":"The verification link appears to be invalid. Hit the button below to request for a new one","655733440":"Others","661808069":"Resend email {{remaining_time}}","662578726":"Available","683273691":"Rate (1 {{ account_currency }})","723172934":"Looking to buy or sell USD? You can post your own ad for others to respond.","728383001":"I’ve received more than the agreed amount.","733311523":"P2P transactions are locked. This feature is not available for payment agents.","767789372":"Wait for payment","782834680":"Time left","783454335":"Yes, remove","830703311":"My profile","834075131":"Blocked advertisers","838024160":"Bank details","842911528":"Don’t show this message again.","846659545":"Your ad is not listed on <0>Buy/Sell because the amount exceeds your daily limit of {{limit}} {{currency}}.\n <1 /><1 />You can still see your ad on <0>My ads. If you’d like to increase your daily limit, please contact us via <2>live chat.","847028402":"Check your email","858027714":"Seen {{ duration }} minutes ago","873437248":"Instructions (optional)","876086855":"Complete the financial assessment form","881351325":"Would you recommend this seller?","887667868":"Order","892431976":"If you cancel your order {{cancellation_limit}} times in {{cancellation_period}} hours, you will be blocked from using Deriv P2P for {{block_duration}} hours.
({{number_of_cancels_remaining}} cancellations remaining)","947389294":"We need your documents","949859957":"Submit","954233511":"Sold","957529514":"To place an order, add one of the advertiser’s preferred payment methods:","957807235":"Blocking wasn't possible as {{name}} is not using Deriv P2P anymore.","988380202":"Your instructions","1001160515":"Sell","1002264993":"Seller's real name","1020552673":"You're creating an ad to buy <0>{{ target_amount }} {{ target_currency }}...","1030390916":"You already have an ad with this range","1035893169":"Delete","1052094244":"Max order","1056821534":"Are you sure?","1057127276":"{{- avg_release_time_in_minutes}} min","1065551550":"Set floating rate","1080990424":"Confirm","1089110190":"You accidentally gave us another email address (usually a work or a personal one instead of the one you meant).","1091533736":"Don't risk your funds with cash transactions. Use bank transfers or e-wallets instead.","1106073960":"You've created an ad","1106485202":"Available Deriv P2P balance","1109217274":"Success!","1119887091":"Verification","1121630246":"Block","1137964885":"Can only contain letters, numbers, and special characters .- _ @.","1151608942":"Total amount","1157877436":"{{field_name}} should not exceed Amount","1161621759":"Choose your nickname","1162965175":"Buyer","1163072833":"<0>ID verified","1164771858":"I’ve received payment from 3rd party.","1191941618":"Enter a value that's within -{{limit}}% to +{{limit}}%","1192337383":"Seen {{ duration }} hour ago","1202500203":"Pay now","1228352589":"Not rated yet","1229976478":"You will be able to see {{ advertiser_name }}'s ads. They'll be able to place orders on your ads, too.","1236083813":"Your payment details","1258285343":"Oops, something went wrong","1265751551":"Deriv P2P Balance","1286797620":"Active","1287051975":"Nickname is too long","1300767074":"{{name}} is no longer on Deriv P2P","1303016265":"Yes","1313218101":"Rate this transaction","1314266187":"Joined today","1320670806":"Leave page","1326475003":"Activate","1328352136":"Sell {{ account_currency }}","1330528524":"Seen {{ duration }} month ago","1337027601":"You sold {{offered_amount}} {{offered_currency}}","1347322213":"How would you rate this transaction?","1347724133":"I have paid {{amount}} {{currency}}.","1366244749":"Limits","1370999551":"Floating rate","1371193412":"Cancel","1381949324":"<0>Address verified","1398938904":"We can't deliver the email to this address (usually because of firewalls or filtering).","1422356389":"No results for \"{{text}}\".","1430413419":"Maximum is {{value}} {{currency}}","1438103743":"Floating rates are enabled for {{local_currency}}. Ads with fixed rates will be deactivated. Switch to floating rates by {{end_date}}.","1448855725":"Add payment methods","1452260922":"Too many failed attempts","1467483693":"Past orders","1474532322":"Sort by","1480915523":"Skip","1497156292":"No ads for this currency 😞","1505293001":"Trade partners","1568512719":"Your daily limits have been increased to {{daily_buy_limit}} {{currency}} (buy) and {{daily_sell_limit}} {{currency}} (sell).","1583335572":"If the ad doesn't receive an order for {{adverts_archive_period}} days, it will be deactivated.","1587250288":"Ad ID {{advert_id}} ","1607051458":"Search by nickname","1615530713":"Something's not right","1620858613":"You're editing an ad to sell <0>{{ target_amount }} {{ target_currency }} for <0>{{ local_amount }} {{ local_currency }} <1>({{ price_rate }} {{local_currency}}/{{ target_currency }})","1623916605":"I wasn’t able to make full payment.","1654365787":"Unknown","1660278694":"The advertiser changed the rate before you confirmed the order.","1671725772":"If you choose to cancel, the edited details will be lost.","1675716253":"Min limit","1678804253":"Buy {{ currency }}","1685888862":"An internal error occurred","1691540875":"Edit payment method","1699829275":"Cannot upload a file over 5MB","1703154819":"You're editing an ad to sell <0>{{ target_amount }} {{ target_currency }}...","1721422292":"Show my real name","1734661732":"Your DP2P balance is {{ dp2p_balance }}","1747523625":"Go back","1752096323":"{{field_name}} should not be below Min limit","1767817594":"Buy completion <0>30d","1784151356":"at","1791767028":"Set a fixed rate for your ad.","1794470010":"I’ve made full payment, but the seller hasn’t released the funds.","1794474847":"I've received payment","1798116519":"Available amount","1809099720":"Expand all","1810217569":"Please refresh this page to continue.","1842172737":"You've received {{offered_amount}} {{offered_currency}}","1848044659":"You have no ads.","1859308030":"Give feedback","1874956952":"Hit the button below to add payment methods.","1902229457":"Unable to block advertiser","1908023954":"Sorry, an error occurred while processing your request.","1923443894":"Inactive","1928240840":"Sell {{ currency }}","1929119945":"There are no ads yet","1976156928":"You'll send","1992961867":"Rate (1 {{currency}})","1994023526":"The email address you entered had a mistake or typo (happens to the best of us).","2020104747":"Filter","2029375371":"Payment instructions","2032274854":"Recommended by {{recommended_count}} traders","2039361923":"You're creating an ad to sell...","2040110829":"Increase my limits","2060873863":"Your order {{order_id}} is complete","2063890788":"Cancelled","2064304887":"We accept JPG, PDF, or PNG (up to 5MB).","2091671594":"Status","2096014107":"Apply","2104905634":"No one has recommended this trader yet","2108340400":"Hello! This is where you can chat with the counterparty to confirm the order details.nNote: In case of a dispute, we'll use this chat as a reference.","2121837513":"Minimum is {{value}} {{currency}}","2142425493":"Ad ID","2142752968":"Please ensure you've received {{amount}} {{local_currency}} in your account and hit Confirm to complete the transaction.","2145292295":"Rate","-1837059346":"Buy / Sell","-1845037007":"Advertiser's page","-494667560":"Orders","-679691613":"My ads","-526636259":"Error 404","-1540251249":"Buy {{ account_currency }}","-1267880283":"{{field_name}} is required","-2019083683":"{{field_name}} can only include letters, numbers, spaces, and any of these symbols: -+.,'#@():;","-222920564":"{{field_name}} has exceeded maximum length","-2093768906":"{{name}} has released your funds.
Would you like to give your feedback?","-857786650":"Check your verification status.","-612892886":"We’ll need you to upload your documents to verify your identity.","-2090325029":"Identity verification is complete.","-1101273282":"Nickname is required","-919203928":"Nickname is too short","-1907100457":"Cannot start, end with, or repeat special characters.","-270502067":"Cannot repeat a character more than 4 times.","-499872405":"You have open orders for this ad. Complete all open orders before deleting this ad.","-2125702445":"Instructions","-1274358564":"Max limit","-1995606668":"Amount","-1965472924":"Fixed rate","-1081775102":"{{field_name}} should not be below Max limit","-885044836":"{{field_name}} should not exceed Max limit","-1921077416":"All ({{list_value}})","-608125128":"Blocked ({{list_value}})","-1764050750":"Payment details","-2021135479":"This field is required.","-2005205076":"{{field_name}} has exceeded maximum length of 200 characters.","-480724783":"You already have an ad with this rate","-1117584385":"Seen more than 6 months ago","-1766199849":"Seen {{ duration }} months ago","-591593016":"Seen {{ duration }} day ago","-1586918919":"Seen {{ duration }} hours ago","-664781013":"Seen {{ duration }} minute ago","-1717650468":"Online","-1948369500":"File uploaded is not supported","-1207312691":"Completed","-688728873":"Expired","-1951641340":"Under dispute","-1738697484":"Confirm payment","-1611857550":"Waiting for the seller to confirm","-1452684930":"Buyer's real name","-1597110099":"Receive","-892663026":"Your contact details","-1875343569":"Seller's payment details","-92830427":"Seller's instructions","-1940034707":"Buyer's instructions","-471384801":"Sorry, we're unable to increase your limits right now. Please try again in a few minutes.","-329713179":"Ok","-231863107":"No","-150224710":"Yes, continue","-205277874":"Your ad is not listed on Buy/Sell because its minimum order is higher than your Deriv P2P available balance ({{balance}} {{currency}}).","-971817673":"Your ad isn't visible to others","-1735126907":"This could be because your account balance is insufficient, your ad amount exceeds your daily limit, or both. You can still see your ad on <0>My ads.","-674715853":"Your ad exceeds the daily limit","-1530773708":"Block {{advertiser_name}}?","-1689905285":"Unblock","-2035037071":"Your Deriv P2P balance isn't enough. Please increase your balance before trying again.","-412680608":"Add payment method","-293182503":"Cancel adding this payment method?","-1850127397":"If you choose to cancel, the details you’ve entered will be lost.","-1601971804":"Cancel your edits?","-1571737200":"Don't cancel","-1072444041":"Update ad","-1088454544":"Get new link","-2124584325":"We've verified your order","-848068683":"Hit the link in the email we sent you to authorise this transaction.","-1238182882":"The link will expire in 10 minutes.","-142727028":"The email is in your spam folder (sometimes things get lost there).","-1306639327":"Payment methods","-227512949":"Check your spelling or use a different term.","-1554938377":"Search payment method","-1285759343":"Search","-75934135":"Matching ads","-1856204727":"Reset","-1728351486":"Invalid verification link","-433946201":"Leave page?","-818345434":"Are you sure you want to leave this page? Changes made will not be saved.","-392043307":"Do you want to delete this ad?","-854930519":"You will NOT be able to restore it.","-1600783504":"Set a floating rate for your ad.","-2008992756":"Do you want to cancel this order?","-1618084450":"If you cancel this order, you'll be blocked from using Deriv P2P for {{block_duration}} hours.","-2026176944":"Please do not cancel if you have already made payment.","-1989544601":"Cancel this order","-492996224":"Do not cancel","-1447732068":"Payment confirmation","-1951344681":"Please make sure that you've paid {{amount}} {{currency}} to {{other_user_name}}, and upload the receipt as proof of your payment","-670364940":"Upload receipt here","-937707753":"Go Back","-984140537":"Add","-1220275347":"You may choose up to 3 payment methods for this ad.","-1340125291":"Done","-510341549":"I’ve received less than the agreed amount.","-650030360":"I’ve paid more than the agreed amount.","-1192446042":"If your complaint isn't listed here, please contact our Customer Support team.","-573132778":"Complaint","-792338456":"What's your complaint?","-418870584":"Cancel order","-1392383387":"I've paid","-727273667":"Complain","-2016990049":"Sell {{offered_currency}} order","-811190405":"Time","-961632398":"Collapse all","-415476028":"Not rated","-26434257":"You have until {{remaining_review_time}} GMT to rate this transaction.","-768709492":"Your transaction experience","-652933704":"Recommended","-84139378":"Not Recommended","-2139303636":"You may have followed a broken link, or the page has moved to a new address.","-1448368765":"Error code: {{error_code}} page not found","-1660552437":"Return to P2P","-849068301":"Loading...","-2061807537":"Something’s not right","-1354983065":"Refresh","-137444201":"Buy","-904197848":"Limits {{min_order_amount_limit_display}}-{{max_order_amount_limit_display}} {{currency}}","-464361439":"{{- avg_buy_time_in_minutes}} min","-2109576323":"Sell completion <0>30d","-165392069":"Avg. release time <0>30d","-1154208372":"Trade volume <0>30d","-1887970998":"Unblocking wasn't possible as {{name}} is not using Deriv P2P anymore.","-2017825013":"Got it","-1070228546":"Joined {{days_since_joined}}d","-2015102262":"({{number_of_ratings}} rating)","-1412298133":"({{number_of_ratings}} ratings)","-260332243":"{{user_blocked_count}} person has blocked you","-117094654":"{{user_blocked_count}} people have blocked you","-1148912768":"If the market rate changes from the rate shown here, we won't be able to process your order.","-55126326":"Seller","-835196958":"Receive payment to","-1218007718":"You may choose up to 3.","-1933432699":"Enter {{transaction_type}} amount","-2021730616":"{{ad_type}}","-490637584":"Limit: {{min}}–{{max}} {{currency}}","-1974067943":"Your bank details","-1657433201":"There are no matching ads.","-1862812590":"Limits {{ min_order }}–{{ max_order }} {{ currency }}","-375836822":"Buy {{account_currency}}","-1035421133":"Sell {{account_currency}}","-1503997652":"No ads for this currency.","-1048001140":"No results for \"{{value}}\".","-1179827369":"Create new ad","-73663931":"Create ad","-141315849":"No ads for this currency at the moment 😞","-1889014820":"<0>Don’t see your payment method? <1>Add new.","-1406830100":"Payment method","-1561775203":"Buy {{currency}}","-1527285935":"Sell {{currency}}","-592818187":"Your Deriv P2P balance is {{ dp2p_balance }}","-1654157453":"Fixed rate (1 {{currency}})","-379708059":"Min order","-1459289144":"This information will be visible to everyone.","-207756259":"You may tap and choose up to 3.","-1282343703":"You're creating an ad to buy <0>{{ target_amount }} {{ target_currency }} for <0>{{ local_amount }} {{ local_currency }} <1>({{ price_rate }} {{local_currency}}/{{ target_currency }})","-2139632895":"You're creating an ad to sell <0>{{ target_amount }} {{ target_currency }} for <0>{{ local_amount }} {{ local_currency }} <1>({{ price_rate }} {{local_currency}}/{{ target_currency }})","-40669120":"You're creating an ad to sell <0>{{ target_amount }} {{ target_currency }}...","-514789442":"You're creating an ad to buy...","-230677679":"{{text}}","-1914431773":"You're editing an ad to buy <0>{{ target_amount }} {{ target_currency }} for <0>{{ local_amount }} {{ local_currency }} <1>({{ price_rate }} {{local_currency}}/{{ target_currency }})","-107996509":"You're editing an ad to buy <0>{{ target_amount }} {{ target_currency }}...","-863580260":"You're editing an ad to buy...","-1396464057":"You're editing an ad to sell...","-372210670":"Rate (1 {{account_currency}})","-1318334333":"Deactivate","-1667041441":"Rate (1 {{ offered_currency }})","-1886565882":"Your ads with floating rates have been deactivated. Set fixed rates to reactivate them.","-792015701":"Deriv P2P cashier is unavailable in your country.","-1241719539":"When you block someone, you won't see their ads, and they can't see yours. Your ads will be hidden from their search results, too.","-1007339977":"There are no matching name.","-1298666786":"My counterparties","-179005984":"Save","-2059312414":"Ad details","-1769584466":"Stats","-808161760":"Deriv P2P balance = deposits that can’t be reversed","-684271315":"OK","-2090878601":"Daily limit","-474123616":"Want to increase your daily limits to <0>{{max_daily_buy}} {{currency}} (buy) and <1>{{max_daily_sell}} {{currency}} (sell)?","-130547447":"Trade volume <0>30d | <1>lifetime","-1792280476":"Choose your payment method","-383030149":"You haven’t added any payment methods yet","-1156559889":"Bank Transfers","-1269362917":"Add new","-1983512566":"This conversation is closed.","-283017497":"Retry","-979459594":"Buy/Sell","-2052184983":"Order ID","-2096350108":"Counterparty","-750202930":"Active orders","-1626659964":"I've received {{amount}} {{currency}}.","-1638172550":"To enable this feature you must complete the following:","-1086586743":"Please submit your <0>proof of address. You can use Deriv P2P after we’ve verified your documents.","-559300364":"Your Deriv P2P cashier is blocked","-740038242":"Your rate is","-146021156":"Delete {{payment_method_name}}?","-1846700504":"Are you sure you want to remove this payment method?","-1422779483":"That payment method cannot be deleted","-532709160":"Your nickname","-237014436":"Recommended by {{recommended_count}} trader","-2054589794":"You've been temporarily barred from using our services due to multiple cancellation attempts. Try again after {{date_time}} GMT.","-1079963355":"trades","-930400128":"To use Deriv P2P, you need to choose a display name (a nickname) and verify your identity.","-992568889":"No one to show here"} \ No newline at end of file +{"6794664":"Ads that match your Deriv P2P balance and limit.","19789721":"Nobody has blocked you. Yay!","24711354":"Total orders <0>30d | <1>lifetime","47573834":"Fixed rate (1 {{account_currency}})","50672601":"Bought","51881712":"You already have an ad with the same exchange rate for this currency pair and order type.

Please set a different rate for your ad.","55916349":"All","68867477":"Order ID {{ id }}","81450871":"We couldn’t find that page","121738739":"Send","122280248":"Avg release time <0>30d","134205943":"Your ads with fixed rates have been deactivated. Set floating rates to reactivate them.","140800401":"Float","145959105":"Choose a nickname","150156106":"Save changes","159757877":"You won't see {{advertiser_name}}'s ads anymore and they won't be able to place orders on your ads.","170072126":"Seen {{ duration }} days ago","173939998":"Avg. pay time <0>30d","197477687":"Edit {{ad_type}} ad","203271702":"Try again","231473252":"Preferred currency","233677840":"of the market rate","246815378":"Once set, your nickname cannot be changed.","276261353":"Avg pay time <0>30d","277542386":"Please use <0>live chat to contact our Customer Support team for help.","316725580":"You can no longer rate this transaction.","323002325":"Post ad","324970564":"Seller's contact details","338910048":"You will appear to other users as","358133589":"Unblock {{advertiser_name}}?","364681129":"Contact details","367579676":"Blocked","392469164":"You have blocked {{advertiser_name}}.","416167062":"You'll receive","424668491":"expired","439264204":"Please set a different minimum and/or maximum order limit.

The range of your ad should not overlap with any of your active ads.","452752527":"Rate (1 {{ currency }})","459886707":"E-wallets","460477293":"Enter message","464044457":"Buyer's nickname","473688701":"Enter a valid amount","476023405":"Didn't receive the email?","488150742":"Resend email","498500965":"Seller's nickname","500514593":"Hide my ads","501523417":"You have no orders.","517202770":"Set fixed rate","523301614":"Release {{amount}} {{currency}}","525380157":"Buy {{offered_currency}} order","531912261":"Bank name, account number, beneficiary name","554135844":"Edit","555447610":"You won't be able to change your buy and sell limits again after this. Do you want to continue?","560402954":"User rating","565060416":"Exchange rate","580715136":"Please register with us!","587882987":"Advertisers","611376642":"Clear","612069973":"Would you recommend this buyer?","628581263":"The {{local_currency}} market rate has changed.","639382772":"Please upload supported file type.","649549724":"I’ve not received any payment.","654193846":"The verification link appears to be invalid. Hit the button below to request for a new one","655733440":"Others","661808069":"Resend email {{remaining_time}}","662578726":"Available","683273691":"Rate (1 {{ account_currency }})","723172934":"Looking to buy or sell USD? You can post your own ad for others to respond.","728383001":"I’ve received more than the agreed amount.","733311523":"P2P transactions are locked. This feature is not available for payment agents.","767789372":"Wait for payment","782834680":"Time left","783454335":"Yes, remove","830703311":"My profile","834075131":"Blocked advertisers","838024160":"Bank details","842911528":"Don’t show this message again.","846659545":"Your ad is not listed on <0>Buy/Sell because the amount exceeds your daily limit of {{limit}} {{currency}}.\n <1 /><1 />You can still see your ad on <0>My ads. If you’d like to increase your daily limit, please contact us via <2>live chat.","847028402":"Check your email","858027714":"Seen {{ duration }} minutes ago","873437248":"Instructions (optional)","876086855":"Complete the financial assessment form","881351325":"Would you recommend this seller?","887667868":"Order","892431976":"If you cancel your order {{cancellation_limit}} times in {{cancellation_period}} hours, you will be blocked from using Deriv P2P for {{block_duration}} hours.
({{number_of_cancels_remaining}} cancellations remaining)","947389294":"We need your documents","949859957":"Submit","954233511":"Sold","957529514":"To place an order, add one of the advertiser’s preferred payment methods:","957807235":"Blocking wasn't possible as {{name}} is not using Deriv P2P anymore.","988380202":"Your instructions","1001160515":"Sell","1002264993":"Seller's real name","1020552673":"You're creating an ad to buy <0>{{ target_amount }} {{ target_currency }}...","1030390916":"You already have an ad with this range","1035893169":"Delete","1052094244":"Max order","1056821534":"Are you sure?","1057127276":"{{- avg_release_time_in_minutes}} min","1065551550":"Set floating rate","1080990424":"Confirm","1089110190":"You accidentally gave us another email address (usually a work or a personal one instead of the one you meant).","1091533736":"Don't risk your funds with cash transactions. Use bank transfers or e-wallets instead.","1106073960":"You've created an ad","1106485202":"Available Deriv P2P balance","1109217274":"Success!","1119887091":"Verification","1121630246":"Block","1137964885":"Can only contain letters, numbers, and special characters .- _ @.","1151608942":"Total amount","1157877436":"{{field_name}} should not exceed Amount","1161621759":"Choose your nickname","1162965175":"Buyer","1163072833":"<0>ID verified","1164771858":"I’ve received payment from 3rd party.","1191941618":"Enter a value that's within -{{limit}}% to +{{limit}}%","1192337383":"Seen {{ duration }} hour ago","1202500203":"Pay now","1228352589":"Not rated yet","1229976478":"You will be able to see {{ advertiser_name }}'s ads. They'll be able to place orders on your ads, too.","1236083813":"Your payment details","1258285343":"Oops, something went wrong","1265751551":"Deriv P2P Balance","1286797620":"Active","1287051975":"Nickname is too long","1300767074":"{{name}} is no longer on Deriv P2P","1303016265":"Yes","1313218101":"Rate this transaction","1314266187":"Joined today","1320670806":"Leave page","1326475003":"Activate","1328352136":"Sell {{ account_currency }}","1330528524":"Seen {{ duration }} month ago","1337027601":"You sold {{offered_amount}} {{offered_currency}}","1347322213":"How would you rate this transaction?","1347724133":"I have paid {{amount}} {{currency}}.","1366244749":"Limits","1370999551":"Floating rate","1371193412":"Cancel","1381949324":"<0>Address verified","1398938904":"We can't deliver the email to this address (usually because of firewalls or filtering).","1422356389":"No results for \"{{text}}\".","1430413419":"Maximum is {{value}} {{currency}}","1438103743":"Floating rates are enabled for {{local_currency}}. Ads with fixed rates will be deactivated. Switch to floating rates by {{end_date}}.","1448855725":"Add payment methods","1452260922":"Too many failed attempts","1467483693":"Past orders","1474532322":"Sort by","1480915523":"Skip","1497156292":"No ads for this currency 😞","1505293001":"Trade partners","1568512719":"Your daily limits have been increased to {{daily_buy_limit}} {{currency}} (buy) and {{daily_sell_limit}} {{currency}} (sell).","1583335572":"If the ad doesn't receive an order for {{adverts_archive_period}} days, it will be deactivated.","1587250288":"Ad ID {{advert_id}} ","1607051458":"Search by nickname","1615530713":"Something's not right","1620858613":"You're editing an ad to sell <0>{{ target_amount }} {{ target_currency }} for <0>{{ local_amount }} {{ local_currency }} <1>({{ price_rate }} {{local_currency}}/{{ target_currency }})","1623916605":"I wasn’t able to make full payment.","1654365787":"Unknown","1660278694":"The advertiser changed the rate before you confirmed the order.","1671725772":"If you choose to cancel, the edited details will be lost.","1675716253":"Min limit","1678804253":"Buy {{ currency }}","1685888862":"An internal error occurred","1691540875":"Edit payment method","1699829275":"Cannot upload a file over 5MB","1703154819":"You're editing an ad to sell <0>{{ target_amount }} {{ target_currency }}...","1721422292":"Show my real name","1734661732":"Your DP2P balance is {{ dp2p_balance }}","1747523625":"Go back","1752096323":"{{field_name}} should not be below Min limit","1767817594":"Buy completion <0>30d","1784151356":"at","1791767028":"Set a fixed rate for your ad.","1794470010":"I’ve made full payment, but the seller hasn’t released the funds.","1794474847":"I've received payment","1798116519":"Available amount","1809099720":"Expand all","1810217569":"Please refresh this page to continue.","1842172737":"You've received {{offered_amount}} {{offered_currency}}","1848044659":"You have no ads.","1859308030":"Give feedback","1874956952":"Hit the button below to add payment methods.","1902229457":"Unable to block advertiser","1908023954":"Sorry, an error occurred while processing your request.","1923443894":"Inactive","1928240840":"Sell {{ currency }}","1929119945":"There are no ads yet","1976156928":"You'll send","1992961867":"Rate (1 {{currency}})","1994023526":"The email address you entered had a mistake or typo (happens to the best of us).","2020104747":"Filter","2029375371":"Payment instructions","2032274854":"Recommended by {{recommended_count}} traders","2039361923":"You're creating an ad to sell...","2040110829":"Increase my limits","2060873863":"Your order {{order_id}} is complete","2063890788":"Cancelled","2064304887":"We accept JPG, PDF, or PNG (up to 5MB).","2091671594":"Status","2096014107":"Apply","2104905634":"No one has recommended this trader yet","2108340400":"Hello! This is where you can chat with the counterparty to confirm the order details.nNote: In case of a dispute, we'll use this chat as a reference.","2121837513":"Minimum is {{value}} {{currency}}","2142425493":"Ad ID","2142752968":"Please ensure you've received {{amount}} {{local_currency}} in your account and hit Confirm to complete the transaction.","2145292295":"Rate","-1837059346":"Buy / Sell","-1845037007":"Advertiser's page","-494667560":"Orders","-679691613":"My ads","-526636259":"Error 404","-1540251249":"Buy {{ account_currency }}","-1267880283":"{{field_name}} is required","-2019083683":"{{field_name}} can only include letters, numbers, spaces, and any of these symbols: -+.,'#@():;","-222920564":"{{field_name}} has exceeded maximum length","-2093768906":"{{name}} has released your funds.
Would you like to give your feedback?","-857786650":"Check your verification status.","-612892886":"We’ll need you to upload your documents to verify your identity.","-2090325029":"Identity verification is complete.","-1101273282":"Nickname is required","-919203928":"Nickname is too short","-1907100457":"Cannot start, end with, or repeat special characters.","-270502067":"Cannot repeat a character more than 4 times.","-499872405":"You have open orders for this ad. Complete all open orders before deleting this ad.","-2125702445":"Instructions","-1274358564":"Max limit","-1995606668":"Amount","-1965472924":"Fixed rate","-1081775102":"{{field_name}} should not be below Max limit","-885044836":"{{field_name}} should not exceed Max limit","-1921077416":"All ({{list_value}})","-608125128":"Blocked ({{list_value}})","-1764050750":"Payment details","-2021135479":"This field is required.","-2005205076":"{{field_name}} has exceeded maximum length of 200 characters.","-480724783":"You already have an ad with this rate","-1117584385":"Seen more than 6 months ago","-1766199849":"Seen {{ duration }} months ago","-591593016":"Seen {{ duration }} day ago","-1586918919":"Seen {{ duration }} hours ago","-664781013":"Seen {{ duration }} minute ago","-1717650468":"Online","-1948369500":"File uploaded is not supported","-1207312691":"Completed","-688728873":"Expired","-1951641340":"Under dispute","-1738697484":"Confirm payment","-1611857550":"Waiting for the seller to confirm","-1452684930":"Buyer's real name","-1597110099":"Receive","-892663026":"Your contact details","-1875343569":"Seller's payment details","-92830427":"Seller's instructions","-1940034707":"Buyer's instructions","-471384801":"Sorry, we're unable to increase your limits right now. Please try again in a few minutes.","-329713179":"Ok","-231863107":"No","-150224710":"Yes, continue","-205277874":"Your ad is not listed on Buy/Sell because its minimum order is higher than your Deriv P2P available balance ({{balance}} {{currency}}).","-971817673":"Your ad isn't visible to others","-1735126907":"This could be because your account balance is insufficient, your ad amount exceeds your daily limit, or both. You can still see your ad on <0>My ads.","-674715853":"Your ad exceeds the daily limit","-1530773708":"Block {{advertiser_name}}?","-1689905285":"Unblock","-2035037071":"Your Deriv P2P balance isn't enough. Please increase your balance before trying again.","-412680608":"Add payment method","-293182503":"Cancel adding this payment method?","-1850127397":"If you choose to cancel, the details you’ve entered will be lost.","-1601971804":"Cancel your edits?","-1571737200":"Don't cancel","-1072444041":"Update ad","-1088454544":"Get new link","-2124584325":"We've verified your order","-848068683":"Hit the link in the email we sent you to authorise this transaction.","-1238182882":"The link will expire in 10 minutes.","-142727028":"The email is in your spam folder (sometimes things get lost there).","-1306639327":"Payment methods","-227512949":"Check your spelling or use a different term.","-1554938377":"Search payment method","-1285759343":"Search","-75934135":"Matching ads","-1856204727":"Reset","-1728351486":"Invalid verification link","-433946201":"Leave page?","-818345434":"Are you sure you want to leave this page? Changes made will not be saved.","-392043307":"Do you want to delete this ad?","-854930519":"You will NOT be able to restore it.","-1600783504":"Set a floating rate for your ad.","-2008992756":"Do you want to cancel this order?","-1618084450":"If you cancel this order, you'll be blocked from using Deriv P2P for {{block_duration}} hours.","-2026176944":"Please do not cancel if you have already made payment.","-1989544601":"Cancel this order","-492996224":"Do not cancel","-1447732068":"Payment confirmation","-1951344681":"Please make sure that you've paid {{amount}} {{currency}} to {{other_user_name}}, and upload the receipt as proof of your payment","-670364940":"Upload receipt here","-937707753":"Go Back","-984140537":"Add","-1220275347":"You may choose up to 3 payment methods for this ad.","-1340125291":"Done","-510341549":"I’ve received less than the agreed amount.","-650030360":"I’ve paid more than the agreed amount.","-1192446042":"If your complaint isn't listed here, please contact our Customer Support team.","-573132778":"Complaint","-792338456":"What's your complaint?","-418870584":"Cancel order","-1392383387":"I've paid","-727273667":"Complain","-2016990049":"Sell {{offered_currency}} order","-811190405":"Time","-961632398":"Collapse all","-415476028":"Not rated","-26434257":"You have until {{remaining_review_time}} GMT to rate this transaction.","-768709492":"Your transaction experience","-652933704":"Recommended","-84139378":"Not Recommended","-2139303636":"You may have followed a broken link, or the page has moved to a new address.","-1448368765":"Error code: {{error_code}} page not found","-1660552437":"Return to P2P","-849068301":"Loading...","-2061807537":"Something’s not right","-1354983065":"Refresh","-137444201":"Buy","-904197848":"Limits {{min_order_amount_limit_display}}-{{max_order_amount_limit_display}} {{currency}}","-464361439":"{{- avg_buy_time_in_minutes}} min","-2109576323":"Sell completion <0>30d","-165392069":"Avg. release time <0>30d","-1154208372":"Trade volume <0>30d","-1887970998":"Unblocking wasn't possible as {{name}} is not using Deriv P2P anymore.","-2017825013":"Got it","-1070228546":"Joined {{days_since_joined}}d","-2015102262":"({{number_of_ratings}} rating)","-1412298133":"({{number_of_ratings}} ratings)","-260332243":"{{user_blocked_count}} person has blocked you","-117094654":"{{user_blocked_count}} people have blocked you","-1148912768":"If the market rate changes from the rate shown here, we won't be able to process your order.","-55126326":"Seller","-835196958":"Receive payment to","-1218007718":"You may choose up to 3.","-1933432699":"Enter {{transaction_type}} amount","-2021730616":"{{ad_type}}","-490637584":"Limit: {{min}}–{{max}} {{currency}}","-1974067943":"Your bank details","-1657433201":"There are no matching ads.","-1862812590":"Limits {{ min_order }}–{{ max_order }} {{ currency }}","-375836822":"Buy {{account_currency}}","-1035421133":"Sell {{account_currency}}","-1503997652":"No ads for this currency.","-1048001140":"No results for \"{{value}}\".","-1179827369":"Create new ad","-73663931":"Create ad","-141315849":"No ads for this currency at the moment 😞","-1889014820":"<0>Don’t see your payment method? <1>Add new.","-1406830100":"Payment method","-1561775203":"Buy {{currency}}","-1527285935":"Sell {{currency}}","-592818187":"Your Deriv P2P balance is {{ dp2p_balance }}","-1654157453":"Fixed rate (1 {{currency}})","-379708059":"Min order","-1459289144":"This information will be visible to everyone.","-207756259":"You may tap and choose up to 3.","-1282343703":"You're creating an ad to buy <0>{{ target_amount }} {{ target_currency }} for <0>{{ local_amount }} {{ local_currency }} <1>({{ price_rate }} {{local_currency}}/{{ target_currency }})","-2139632895":"You're creating an ad to sell <0>{{ target_amount }} {{ target_currency }} for <0>{{ local_amount }} {{ local_currency }} <1>({{ price_rate }} {{local_currency}}/{{ target_currency }})","-40669120":"You're creating an ad to sell <0>{{ target_amount }} {{ target_currency }}...","-514789442":"You're creating an ad to buy...","-230677679":"{{text}}","-1914431773":"You're editing an ad to buy <0>{{ target_amount }} {{ target_currency }} for <0>{{ local_amount }} {{ local_currency }} <1>({{ price_rate }} {{local_currency}}/{{ target_currency }})","-107996509":"You're editing an ad to buy <0>{{ target_amount }} {{ target_currency }}...","-863580260":"You're editing an ad to buy...","-1396464057":"You're editing an ad to sell...","-372210670":"Rate (1 {{account_currency}})","-1318334333":"Deactivate","-1667041441":"Rate (1 {{ offered_currency }})","-1886565882":"Your ads with floating rates have been deactivated. Set fixed rates to reactivate them.","-792015701":"Deriv P2P cashier is unavailable in your country.","-1241719539":"When you block someone, you won't see their ads, and they can't see yours. Your ads will be hidden from their search results, too.","-1007339977":"There are no matching name.","-1298666786":"My counterparties","-179005984":"Save","-2059312414":"Ad details","-1769584466":"Stats","-808161760":"Deriv P2P balance = deposits that can’t be reversed","-684271315":"OK","-2090878601":"Daily limit","-474123616":"Want to increase your daily limits to <0>{{max_daily_buy}} {{currency}} (buy) and <1>{{max_daily_sell}} {{currency}} (sell)?","-130547447":"Trade volume <0>30d | <1>lifetime","-1792280476":"Choose your payment method","-383030149":"You haven’t added any payment methods yet","-1156559889":"Bank Transfers","-1269362917":"Add new","-1983512566":"This conversation is closed.","-283017497":"Retry","-979459594":"Buy/Sell","-2052184983":"Order ID","-2096350108":"Counterparty","-750202930":"Active orders","-1626659964":"I've received {{amount}} {{currency}}.","-1638172550":"To enable this feature you must complete the following:","-1086586743":"Please submit your <0>proof of address. You can use Deriv P2P after we’ve verified your documents.","-559300364":"Your Deriv P2P cashier is blocked","-740038242":"Your rate is","-146021156":"Delete {{payment_method_name}}?","-1846700504":"Are you sure you want to remove this payment method?","-1422779483":"That payment method cannot be deleted","-532709160":"Your nickname","-237014436":"Recommended by {{recommended_count}} trader","-2054589794":"You've been temporarily barred from using our services due to multiple cancellation attempts. Try again after {{date_time}} GMT.","-1079963355":"trades","-930400128":"To use Deriv P2P, you need to choose a display name (a nickname) and verify your identity.","-992568889":"No one to show here"} diff --git a/packages/p2p/src/components/app-content.jsx b/packages/p2p/src/components/app-content.jsx index d629787548c8..ec7aceb8e9cf 100644 --- a/packages/p2p/src/components/app-content.jsx +++ b/packages/p2p/src/components/app-content.jsx @@ -1,16 +1,19 @@ -import classNames from 'classnames'; import React from 'react'; import { useHistory } from 'react-router-dom'; +import classNames from 'classnames'; import { isAction, reaction } from 'mobx'; import { observer } from 'mobx-react-lite'; + import { Loading, Tabs } from '@deriv/components'; import { useP2PNotificationCount } from '@deriv/hooks'; import { isMobile } from '@deriv/shared'; import { useStore } from '@deriv/stores'; -import TemporarilyBarredHint from 'Components/temporarily-barred-hint'; + import { useModalManagerContext } from 'Components/modal-manager/modal-manager-context'; +import TemporarilyBarredHint from 'Components/temporarily-barred-hint'; import { buy_sell } from 'Constants/buy-sell'; import { useStores } from 'Stores'; + import { localize } from './i18next'; const AppContent = ({ order_id }) => { diff --git a/packages/p2p/src/components/dp2p-blocked/dp2p-blocked-description.tsx b/packages/p2p/src/components/dp2p-blocked/dp2p-blocked-description.tsx index 079e629d1084..7703e4ee1959 100644 --- a/packages/p2p/src/components/dp2p-blocked/dp2p-blocked-description.tsx +++ b/packages/p2p/src/components/dp2p-blocked/dp2p-blocked-description.tsx @@ -1,6 +1,8 @@ import React from 'react'; import { observer } from 'mobx-react-lite'; + import { Text } from '@deriv/components'; + import { Localize } from 'Components/i18next'; import { useStores } from 'Stores/index'; //remove index when store migration to ts is done diff --git a/packages/p2p/src/components/dp2p-blocked/dp2p-blocked.tsx b/packages/p2p/src/components/dp2p-blocked/dp2p-blocked.tsx index ea5ff9cb47dc..9147f7545f1b 100644 --- a/packages/p2p/src/components/dp2p-blocked/dp2p-blocked.tsx +++ b/packages/p2p/src/components/dp2p-blocked/dp2p-blocked.tsx @@ -1,7 +1,10 @@ import React from 'react'; + import { Icon, Text } from '@deriv/components'; + import { Localize } from 'Components/i18next'; import { useStores } from 'Stores'; + import Dp2pBlockedChecklist from './dp2p-blocked-checklist'; import Dp2pBlockedDescription from './dp2p-blocked-description'; diff --git a/packages/p2p/src/pages/advertiser-page/advertiser-page.jsx b/packages/p2p/src/pages/advertiser-page/advertiser-page.jsx index 4f8fab7c004e..ac718b749aee 100644 --- a/packages/p2p/src/pages/advertiser-page/advertiser-page.jsx +++ b/packages/p2p/src/pages/advertiser-page/advertiser-page.jsx @@ -1,25 +1,28 @@ import React from 'react'; -import { DesktopWrapper, Loading, MobileWrapper, Text } from '@deriv/components'; -import { daysSince, isMobile } from '@deriv/shared'; +import { useHistory } from 'react-router-dom'; +import classNames from 'classnames'; import { reaction } from 'mobx'; import { observer } from 'mobx-react-lite'; -import { useHistory } from 'react-router-dom'; -import { useStores } from 'Stores'; + +import { DesktopWrapper, Loading, MobileWrapper, Text } from '@deriv/components'; +import { daysSince, isMobile } from '@deriv/shared'; + import { Localize, localize } from 'Components/i18next'; -import { my_profile_tabs } from 'Constants/my-profile-tabs'; -import { api_error_codes } from 'Constants/api-error-codes'; +import { useModalManagerContext } from 'Components/modal-manager/modal-manager-context'; +import { OnlineStatusIcon, OnlineStatusLabel } from 'Components/online-status'; import PageReturn from 'Components/page-return'; import RecommendedBy from 'Components/recommended-by'; -import UserAvatar from 'Components/user/user-avatar'; -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 'Components/trade-badge'; +import UserAvatar from 'Components/user/user-avatar'; +import { api_error_codes } from 'Constants/api-error-codes'; +import { my_profile_tabs } from 'Constants/my-profile-tabs'; +import { useStores } from 'Stores'; + import BlockUserOverlay from './block-user/block-user-overlay'; -import classNames from 'classnames'; -import { OnlineStatusIcon, OnlineStatusLabel } from 'Components/online-status'; -import { useModalManagerContext } from 'Components/modal-manager/modal-manager-context'; +import AdvertiserPageAdverts from './advertiser-page-adverts.jsx'; +import AdvertiserPageDropdownMenu from './advertiser-page-dropdown-menu.jsx'; +import AdvertiserPageStats from './advertiser-page-stats.jsx'; const AdvertiserPage = () => { const { advertiser_page_store, buy_sell_store, general_store, my_profile_store } = useStores(); diff --git a/packages/p2p/src/pages/buy-sell/buy-sell-row.jsx b/packages/p2p/src/pages/buy-sell/buy-sell-row.jsx index 40ee9e611ab5..1d924ea094ea 100644 --- a/packages/p2p/src/pages/buy-sell/buy-sell-row.jsx +++ b/packages/p2p/src/pages/buy-sell/buy-sell-row.jsx @@ -1,18 +1,21 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; -import { Table, Text, Button, Icon } from '@deriv/components'; +import classNames from 'classnames'; +import PropTypes from 'prop-types'; + +import { Button, Icon,Table, Text } from '@deriv/components'; +import { useExchangeRate } from '@deriv/hooks'; import { isMobile, routes } from '@deriv/shared'; import { observer, useStore } from '@deriv/stores'; -import { useExchangeRate } from '@deriv/hooks'; -import { buy_sell } from 'Constants/buy-sell'; + import { Localize, localize } from 'Components/i18next'; import { OnlineStatusAvatar } from 'Components/online-status'; -import { useStores } from 'Stores'; import StarRating from 'Components/star-rating'; import TradeBadge from 'Components/trade-badge'; +import { buy_sell } from 'Constants/buy-sell'; +import { useStores } from 'Stores'; import { generateEffectiveRate } from 'Utils/format-value'; + import './buy-sell-row.scss'; const BuySellRow = ({ row: advert }) => { diff --git a/packages/p2p/src/pages/my-profile/my-profile-stats/my-profile-name/my-profile-name.jsx b/packages/p2p/src/pages/my-profile/my-profile-stats/my-profile-name/my-profile-name.jsx index 01a46540ca73..b5e8fc79d058 100644 --- a/packages/p2p/src/pages/my-profile/my-profile-stats/my-profile-name/my-profile-name.jsx +++ b/packages/p2p/src/pages/my-profile/my-profile-stats/my-profile-name/my-profile-name.jsx @@ -1,15 +1,17 @@ import React from 'react'; -import { DesktopWrapper, MobileWrapper, Text } from '@deriv/components'; import { observer } from 'mobx-react-lite'; -import UserAvatar from 'Components/user/user-avatar'; -import { useStores } from 'Stores'; + +import { DesktopWrapper, MobileWrapper, Text } from '@deriv/components'; import { daysSince, isMobile } from '@deriv/shared'; + import { Localize } from 'Components/i18next'; -import TradeBadge from 'Components/trade-badge'; -import MyProfilePrivacy from 'Pages/my-profile/my-profile-stats/my-profile-privacy/my-profile-privacy'; -import StarRating from 'Components/star-rating'; import RecommendedBy from 'Components/recommended-by'; +import StarRating from 'Components/star-rating'; +import TradeBadge from 'Components/trade-badge'; +import UserAvatar from 'Components/user/user-avatar'; import BlockUserCount from 'Pages/advertiser-page/block-user/block-user-count'; +import MyProfilePrivacy from 'Pages/my-profile/my-profile-stats/my-profile-privacy/my-profile-privacy'; +import { useStores } from 'Stores'; const MyProfileName = () => { const { general_store } = useStores(); diff --git a/packages/p2p/src/stores/general-store.js b/packages/p2p/src/stores/general-store.js index 9bcbe7c24eb9..50b587ce8cd7 100644 --- a/packages/p2p/src/stores/general-store.js +++ b/packages/p2p/src/stores/general-store.js @@ -1,15 +1,18 @@ import React from 'react'; -import { action, computed, observable, reaction, makeObservable } from 'mobx'; -import { get, init, timePromise } from '../utils/server_time'; +import { action, computed, makeObservable,observable, reaction } from 'mobx'; + import { isEmptyObject, isMobile, routes, toMoment } from '@deriv/shared'; + +import { Localize,localize } from 'Components/i18next'; +import { api_error_codes } from 'Constants/api-error-codes'; +import { buy_sell } from 'Constants/buy-sell'; +import { order_list } from 'Constants/order-list'; import BaseStore from 'Stores/base_store'; -import { localize, Localize } from 'Components/i18next'; import { convertToMillis, getFormattedDateString } from 'Utils/date-time'; import { createExtendedOrderDetails } from 'Utils/orders'; import { init as WebsocketInit, requestWS, subscribeWS } from 'Utils/websocket'; -import { order_list } from 'Constants/order-list'; -import { buy_sell } from 'Constants/buy-sell'; -import { api_error_codes } from 'Constants/api-error-codes'; + +import { get, init, timePromise } from '../utils/server_time'; export default class GeneralStore extends BaseStore { active_index = 0; diff --git a/packages/p2p/src/stores/sendbird-store.ts b/packages/p2p/src/stores/sendbird-store.ts index 826be6ee7856..988c46b52c87 100644 --- a/packages/p2p/src/stores/sendbird-store.ts +++ b/packages/p2p/src/stores/sendbird-store.ts @@ -1,13 +1,15 @@ -import SendbirdChat, { BaseChannel } from '@sendbird/chat'; +import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; + +import { P2PAdvertiserCreate, P2PAdvertiserInfo } from '@deriv/api-types'; import { epochToMoment, toMoment } from '@deriv/shared'; -import { action, computed, observable, reaction, makeObservable, IReactionDisposer } from 'mobx'; -import BaseStore from 'Stores/base_store'; -import ChatMessage, { convertFromChannelMessage } from 'Utils/chat-message'; -import { requestWS } from 'Utils/websocket'; import { TCoreStores } from '@deriv/stores/types'; +import SendbirdChat, { BaseChannel } from '@sendbird/chat'; import { GroupChannel, GroupChannelHandler, GroupChannelModule } from '@sendbird/chat/groupChannel'; import { BaseMessage, FileMessage, MessageType, MessageTypeFilter, UserMessage } from '@sendbird/chat/message'; -import { P2PAdvertiserCreate, P2PAdvertiserInfo } from '@deriv/api-types'; + +import BaseStore from 'Stores/base_store'; +import ChatMessage, { convertFromChannelMessage } from 'Utils/chat-message'; +import { requestWS } from 'Utils/websocket'; type TChatInfo = { app_id: string; user_id: string; token?: string }; diff --git a/packages/reports/package.json b/packages/reports/package.json index 8dc811b193b6..0407572acfb6 100644 --- a/packages/reports/package.json +++ b/packages/reports/package.json @@ -76,6 +76,7 @@ }, "dependencies": { "@deriv/components": "^1.0.0", + "@deriv/deriv-api": "^1.0.13", "@deriv/shared": "^1.0.0", "@deriv/stores": "^1.0.0", "@deriv/translations": "^1.0.0", diff --git a/packages/reports/src/Stores/base-store.js b/packages/reports/src/Stores/base-store.js index 4e005596209a..9dd1131f5da7 100644 --- a/packages/reports/src/Stores/base-store.js +++ b/packages/reports/src/Stores/base-store.js @@ -1,5 +1,6 @@ -import { action, intercept, observable, reaction, toJS, when, makeObservable } from 'mobx'; -import { isProduction, isEmptyObject, Validator } from '@deriv/shared'; +import { action, intercept, makeObservable,observable, reaction, toJS, when } from 'mobx'; + +import { isEmptyObject, isProduction, Validator } from '@deriv/shared'; /** * BaseStore class is the base class for all defined stores in the application. It handles some stuff such as: diff --git a/packages/shared/src/styles/constants.scss b/packages/shared/src/styles/constants.scss index d9c22b41345b..5e480428a01a 100644 --- a/packages/shared/src/styles/constants.scss +++ b/packages/shared/src/styles/constants.scss @@ -121,6 +121,8 @@ $gradient-color-red-3: linear-gradient(90deg, #ff444f 0%, #211d1e 95.22%); $gradient-color-red-4: linear-gradient(90deg, #ff6444 0%, #ff444f 100%); $gradient-color-black-3: linear-gradient(58.51deg, #061828 28.06%, #1a3c60 93.51%); $gradient-color-black-4: linear-gradient(274.25deg, #333333 9.01%, #5c5b5b 103.31%); +$gradient-color-black-5: linear-gradient(180deg, rgba(0, 0, 0, 0) 50%, rgba(0, 0, 0, 0.16) 100%); +$gradient-color-white-2: linear-gradient(180deg, rgba(255, 255, 255, 0) 50%, rgba(255, 255, 255, 0.16) 100%); $gradient-color-blue-5: linear-gradient(90deg, #00a8af 0%, #04cfd8 104.41%); $gradient-color-gold: linear-gradient(90deg, #f7931a 0%, #ffc71b 104.41%); $gradient-color-green-4: linear-gradient(90deg, #1db193 0%, #09da7a 104.41%); @@ -182,6 +184,7 @@ $COLOR_DARK_GRAY_4: #999999; /* stylelint-enable color-no-hex */ $BORDER_RADIUS: 4px; +$BORDER_RADIUS_2: 8px; $MAX_CONTAINER_WIDTH: 1440px; @@ -220,3 +223,12 @@ $gradient-webmoney: linear-gradient(90deg, #1a77ac 0%, #0068a3 100%); /* App Cards gradient background */ $gradient-virtual: linear-gradient(274.25deg, #333333 9.01%, #5c5b5b 103.31%); $gradient-virtual-swap-free: linear-gradient(58.51deg, #061828 28.06%, #1a3c60 93.51%); + +/* Wallets */ +$ready-banner-bg-color: #e2f3f3; +$ready-banner-tick-bg-color: #4ab4b3; +$wallet-demo-bg-color: #fff8f9; +$wallet-dark-demo-bg-color: #140506; +$wallet-demo-divider-color: #fff0f1; +$wallet-box-shadow: 0px 12px 16px -4px rgba(14, 14, 14, 0.08), 0px 4px 6px -2px rgba(14, 14, 14, 0.03); +$btn-shadow: 0px 24px 24px 0px rgba(0, 0, 0, 0.08), 0px 0px 24px 0px rgba(0, 0, 0, 0.08); diff --git a/packages/shared/src/styles/themes.scss b/packages/shared/src/styles/themes.scss index 0c1cc6188795..47dfa3f7d690 100644 --- a/packages/shared/src/styles/themes.scss +++ b/packages/shared/src/styles/themes.scss @@ -85,6 +85,7 @@ --text-less-prominent: #{$color-grey-1}; --text-prominent: #{$color-black-1}; --text-disabled: #{$color-grey-1}; + --text-disabled-1: #{$color-grey-6}; --text-loss-danger: #{$color-red-1}; --text-profit-success: #{$color-green-1}; --text-warning: #{$color-yellow}; @@ -199,6 +200,20 @@ --badge-blue: #{$color-blue-4}; --badge-violet: #{$color-blue-2}; --badge-green: #{$color-green-3}; + // wallets + --wallets-banner-ready-bg-color: #{$ready-banner-bg-color}; + --wallets-banner-ready-tick-bg-color: #{$ready-banner-tick-bg-color}; + --wallets-banner-border-color: #{$color-grey-4}; + --wallets-banner-dot-color: #{$color-grey-6}; + --wallets-banner-active-dot-color: #{$color-red}; + --wallets-card-active-gradient-background: #{$gradient-color-black-5}; + --wallet-demo-bg-color: #{$wallet-demo-bg-color}; + --wallet-demo-divider-color: #{$wallet-demo-divider-color}; + --wallet-eu-disclaimer: #{$color-grey-4}; + --wallet-box-shadow: #{$wallet-box-shadow}; + // Demo view + --demo-text-color-1: #{$color-grey}; + --demo-text-color-2: #{$color-white}; // Header --header-background-mt5: #{$color-blue-8}; --header-background-others: #{$color-green-7}; @@ -225,6 +240,7 @@ --text-less-prominent: #{$color-grey-7}; --text-primary: #{$color-grey-1}; --text-disabled: #{$color-black-6}; + --text-disabled-1: #{$color-black-6}; --text-profit-success: #{$color-green-3}; --text-loss-danger: #{$color-red-2}; --text-red: #{$color-red}; @@ -318,6 +334,20 @@ --badge-blue: #{$color-blue-4}; --badge-violet: #{$color-blue-2}; --badge-green: #{$color-green-3}; + // wallets + --wallets-banner-ready-bg-color: #{$ready-banner-bg-color}; + --wallets-banner-ready-tick-bg-color: #{$ready-banner-tick-bg-color}; + --wallets-banner-border-color: #{$color-grey-4}; + --wallets-banner-dot-color: #{$color-grey-6}; + --wallets-banner-active-dot-color: #{$color-red}; + --wallets-card-active-gradient-background: #{$gradient-color-white-2}; + --wallet-demo-bg-color: #{$wallet-dark-demo-bg-color}; + --wallet-demo-divider-color: #{$color-black-8}; + --wallet-eu-disclaimer: #{$color-grey-4}; + --wallet-box-shadow: #{$wallet-box-shadow}; + // Demo view + --demo-text-color-1: #{$color-black-1}; + --demo-text-color-2: #{$color-black-1}; // Header --header-background-mt5: #{$color-blue-8}; --header-background-others: #{$color-green-7}; diff --git a/packages/shared/src/utils/constants/contract.ts b/packages/shared/src/utils/constants/contract.ts index c9dc9493fd48..4984193a4a8a 100644 --- a/packages/shared/src/utils/constants/contract.ts +++ b/packages/shared/src/utils/constants/contract.ts @@ -1,5 +1,7 @@ import React from 'react'; + import { localize } from '@deriv/translations'; + import { shouldShowCancellation, shouldShowExpiration, TURBOS, VANILLALONG } from '../contract'; export const getLocalizedBasis = () => diff --git a/packages/shared/src/utils/login/login.ts b/packages/shared/src/utils/login/login.ts index e8136a0aaad2..9a256d9da1ad 100644 --- a/packages/shared/src/utils/login/login.ts +++ b/packages/shared/src/utils/login/login.ts @@ -1,8 +1,8 @@ -import { deriv_urls } from '../url/constants'; import { website_name } from '../config/app-config'; +import { domain_app_ids, getAppId } from '../config/config'; import { CookieStorage, isStorageSupported, LocalStore } from '../storage/storage'; -import { getAppId, domain_app_ids } from '../config/config'; import { getStaticUrl, urlForCurrentDomain } from '../url'; +import { deriv_urls } from '../url/constants'; export const redirectToLogin = (is_logged_in: boolean, language: string, has_params = true, redirect_delay = 0) => { if (!is_logged_in && isStorageSupported(sessionStorage)) { diff --git a/packages/shared/src/utils/screen/responsive.ts b/packages/shared/src/utils/screen/responsive.ts index 562171dbf9a6..29a3008a24f7 100644 --- a/packages/shared/src/utils/screen/responsive.ts +++ b/packages/shared/src/utils/screen/responsive.ts @@ -17,6 +17,7 @@ export const isTouchDevice = () => (window.DocumentTouch && document instanceof window.DocumentTouch) || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0; +/** @deprecated Use `is_mobile` from ui-store instead. */ export const isMobile = () => window.innerWidth <= MAX_MOBILE_WIDTH; export const isDesktop = () => isTablet() || window.innerWidth > MAX_TABLET_WIDTH; // TODO: remove tablet once there is a design for the specific size. export const isTablet = () => MAX_MOBILE_WIDTH < window.innerWidth && window.innerWidth <= MAX_TABLET_WIDTH; diff --git a/packages/shared/src/utils/validator/validator.ts b/packages/shared/src/utils/validator/validator.ts index 9a6e3818b8a1..0e3c9c5d73ec 100644 --- a/packages/shared/src/utils/validator/validator.ts +++ b/packages/shared/src/utils/validator/validator.ts @@ -1,4 +1,5 @@ -import { TOptions, getPreBuildDVRs, TInitPreBuildDVRs } from '../validation/declarative-validation-rules'; +import { getPreBuildDVRs, TInitPreBuildDVRs, TOptions } from '../validation/declarative-validation-rules'; + import Error from './errors'; type TRuleOptions = { diff --git a/packages/stores/src/mockStore.ts b/packages/stores/src/mockStore.ts index 546f4bc0984b..87b8cbf31c14 100644 --- a/packages/stores/src/mockStore.ts +++ b/packages/stores/src/mockStore.ts @@ -152,6 +152,7 @@ const mock = (): TStores & { is_mock: boolean } => { is_single_currency: false, is_switching: false, is_tnc_needed: false, + is_high_risk: false, is_trading_experience_incomplete: false, is_unwelcome: false, is_virtual: false, @@ -218,7 +219,6 @@ const mock = (): TStores & { is_mock: boolean } => { isEligibleForMoreRealMt5: jest.fn(), isEligibleForMoreDemoMt5Svg: jest.fn(), updateMT5Status: jest.fn(), - is_high_risk: false, fetchResidenceList: jest.fn(), residence_list: [], should_restrict_bvi_account_creation: false, @@ -272,6 +272,10 @@ const mock = (): TStores & { is_mock: boolean } => { real_account_creation_unlock_date: '', has_any_real_account: false, setPrevAccountType: jest.fn(), + setLoginInformation: jest.fn(), + init: jest.fn(), + setLoginId: jest.fn(), + resetLocalStorageValues: jest.fn(), }, common: { error: common_store_error, @@ -309,6 +313,7 @@ const mock = (): TStores & { is_mock: boolean } => { is_advanced_duration: false, is_loading: false, is_cashier_visible: false, + is_wallet_modal_visible: false, is_chart_layout_default: false, is_closing_create_real_account_modal: false, is_dark_mode_on: false, @@ -347,6 +352,7 @@ const mock = (): TStores & { is_mock: boolean } => { notification_messages_ui: jest.fn(), openPositionsDrawer: jest.fn(), openRealAccountSignup: jest.fn(), + setIsWalletModalVisible: jest.fn(), setHasOnlyForwardingContracts: jest.fn(), setIsClosingCreateRealAccountModal: jest.fn(), setMobileLanguageMenuOpen: jest.fn(), @@ -410,11 +416,14 @@ const mock = (): TStores & { is_mock: boolean } => { account_id: '', }, is_eu_user: false, + is_demo: false, setIsOnboardingVisited: jest.fn(), is_real: false, selectRegion: jest.fn(), setSelectedAccount: jest.fn(), is_low_risk_cr_eu_real: false, + is_real_wallets_upgrade_on: false, + toggleWalletsUpgrade: jest.fn(), show_eu_related_content: false, platform_real_balance: { currency: '', @@ -434,8 +443,6 @@ const mock = (): TStores & { is_mock: boolean } => { }, closeAccountTransferModal: jest.fn(), toggleRegulatorsCompareModal: jest.fn(), - selected_region: '', - is_demo: false, financial_restricted_countries: false, selected_account_type: 'real', no_CR_account: false, @@ -449,7 +456,18 @@ const mock = (): TStores & { is_mock: boolean } => { setTogglePlatformType: jest.fn(), toggleAccountTransferModal: jest.fn(), selectAccountType: jest.fn(), + is_wallet_migration_failed: false, + setWalletsMigrationFailedPopup: jest.fn(), + selected_platform_type: '', + available_platforms: [], + selected_region: 'All', + getExistingAccounts: jest.fn(), available_dxtrade_accounts: [], + toggleAccountTypeModalVisibility: jest.fn(), + active_modal_tab: 'Deposit', + setWalletModalActiveTab: jest.fn(), + active_modal_wallet_id: '', + setWalletModalActiveWalletID: jest.fn(), available_ctrader_accounts: [], toggleIsTourOpen: jest.fn(), is_demo_low_risk: false, @@ -458,14 +476,8 @@ const mock = (): TStores & { is_mock: boolean } => { available_derivez_accounts: [], has_any_real_account: false, startTrade: jest.fn(), - getExistingAccounts: jest.fn(), - toggleAccountTypeModalVisibility: jest.fn(), showTopUpModal: jest.fn(), }, - menu: { - attach: jest.fn(), - update: jest.fn(), - }, notifications: { addNotificationMessage: jest.fn(), addNotificationMessageByKey: jest.fn(), @@ -475,13 +487,14 @@ const mock = (): TStores & { is_mock: boolean } => { filterNotificationMessages: jest.fn(), notifications: [], refreshNotifications: jest.fn(), + removeAllNotificationMessages: jest.fn(), removeNotifications: jest.fn(), removeNotificationByKey: jest.fn(), removeNotificationMessage: jest.fn(), removeNotificationMessageByKey: jest.fn(), setP2POrderProps: jest.fn(), - showAccountSwitchToRealNotification: jest.fn(), setP2PRedirectTo: jest.fn(), + showAccountSwitchToRealNotification: jest.fn(), setShouldShowPopups: jest.fn(), toggleNotificationsModal: jest.fn(), }, diff --git a/packages/stores/src/stores/FeatureFlagsStore.ts b/packages/stores/src/stores/FeatureFlagsStore.ts index 1258ac4ac389..e5aeecef3675 100644 --- a/packages/stores/src/stores/FeatureFlagsStore.ts +++ b/packages/stores/src/stores/FeatureFlagsStore.ts @@ -1,6 +1,7 @@ import BaseStore from './BaseStore'; const FLAGS = { + wallet: false, next_wallet: false, } satisfies Record; diff --git a/packages/stores/types.ts b/packages/stores/types.ts index 3b4c60be66d5..556de4fbc932 100644 --- a/packages/stores/types.ts +++ b/packages/stores/types.ts @@ -1,3 +1,6 @@ +import type { RouteComponentProps } from 'react-router'; +import type { Moment } from 'moment'; + import type { AccountLimitsResponse, Authorize, @@ -5,10 +8,10 @@ import type { DetailsOfEachMT5Loginid, GetAccountStatus, GetLimits, - Portfolio1, GetSettings, LandingCompany, LogOutResponse, + Portfolio1, ProposalOpenContract, ResidenceList, SetFinancialAssessmentRequest, @@ -19,8 +22,7 @@ import type { Transaction, ActiveSymbols, } from '@deriv/api-types'; -import type { Moment } from 'moment'; -import type { RouteComponentProps } from 'react-router'; + import type { ExchangeRatesStore, FeatureFlagsStore } from './src/stores'; type TRoutes = @@ -87,6 +89,69 @@ type TPopulateSettingsExtensionsMenuItem = { value: (props: T) => JSX.Element; }; +type TRegionAvailability = 'Non-EU' | 'EU' | 'All'; + +type TIconTypes = + | 'Derived' + | 'Financial' + | 'BinaryBot' + | 'BinaryBotBlue' + | 'DBot' + | 'Demo' + | 'DerivGo' + | 'DerivGoBlack' + | 'DerivLogo' + | 'DerivTradingLogo' + | 'DerivX' + | 'DropDown' + | 'DTrader' + | 'Options' + | 'SmartTrader' + | 'SmartTraderBlue' + | 'CFDs'; + +type AvailableAccount = { + name: string; + is_item_blurry?: boolean; + has_applauncher_account?: boolean; + sub_title?: string; + description?: string; + is_visible?: boolean; + is_disabled?: boolean; + platform?: string; + market_type?: 'all' | 'financial' | 'synthetic'; + icon: TIconTypes; + availability: TRegionAvailability; + short_code_and_region?: string; + login?: string; +}; + +type BrandConfig = { + name: string; + icon: TIconTypes; + availability: TRegionAvailability; + is_deriv_platform?: boolean; +}; + +type TPortfolioPosition = { + contract_info: ProposalOpenContract & + Portfolio1 & { + contract_update?: ContractUpdate; + }; + details?: string; + display_name: string; + id?: number; + indicative: number; + payout?: number; + purchase?: number; + reference: number; + type?: string; + is_unsupported: boolean; + contract_update: ProposalOpenContract['limit_order']; + is_sell_requested: boolean; + profit_loss: number; +}; + type TAppRoutingHistory = { action: string; hash: string; @@ -97,6 +162,9 @@ type TAppRoutingHistory = { type TAccount = NonNullable[0] & { balance?: number; + landing_company_shortcode?: 'svg' | 'costarica' | 'maltainvest' | 'malta' | 'iom'; + is_virtual: number; + account_category?: 'wallet' | 'trading'; }; type TCtraderAccountsList = DetailsOfEachMT5Loginid & { @@ -136,8 +204,12 @@ type TAccountsList = { // balance is missing in @deriv/api-types type TActiveAccount = TAccount & { + balance?: string | number; landing_company_shortcode: 'svg' | 'costarica' | 'maltainvest' | 'malta' | 'iom'; is_virtual: number; + account_category?: 'wallet' | 'trading'; + linked_to?: { loginid: string; platform: string }[]; + token: string; }; type TTradingPlatformAvailableAccount = { @@ -168,15 +240,6 @@ type TAvailableCFDAccounts = { type TAuthenticationStatus = { document_status: string; identity_status: string }; -type TMenuItem = { - icon: JSX.Element; - id: string; - link_to: string | boolean; - login_only: boolean; - onClick: boolean | (() => void); - text: () => string; -}; - type TAddToastProps = { key?: string; content: string | React.ReactNode; @@ -222,6 +285,18 @@ type TNotification = | ((withdrawal_locked: boolean, deposit_locked: boolean) => TNotificationMessage) | ((excluded_until: number) => TNotificationMessage); +type LoginParams = { + acct: string; + token: string; + curr: string; + lang: string; +}; + +type IncrementedProperties = { + [K in keyof LoginParams as `${string & K}${N}`]: string; +}; + +type LoginURLParams = LoginParams & IncrementedProperties; type TStandPoint = { financial_company: string; gaming_company: string; @@ -317,6 +392,7 @@ type TClientStore = { is_populating_dxtrade_account_list: boolean; is_switching: boolean; is_tnc_needed: boolean; + is_high_risk: boolean; is_trading_experience_incomplete: boolean; is_virtual: boolean; is_withdrawal_lock: boolean; @@ -349,6 +425,7 @@ type TClientStore = { setP2pAdvertiserInfo: () => void; setPreSwitchAccount: (status?: boolean) => void; switchAccount: (value?: string) => Promise; + setLoginInformation: (client_accounts: { [k: string]: TActiveAccount }, client_id: string) => void; social_identity_provider: string; switched: boolean; switch_broadcast: boolean; @@ -381,7 +458,6 @@ type TClientStore = { poi_state?: string; }; residence_list: ResidenceList; - is_high_risk: boolean; should_restrict_bvi_account_creation: boolean; should_restrict_vanuatu_account_creation: boolean; updateMT5Status: () => Promise; @@ -424,6 +500,9 @@ type TClientStore = { is_svg: boolean; real_account_creation_unlock_date: string; setPrevAccountType: (account_type: string) => void; + init: (login_new_user?: LoginURLParams<1>) => void; + setLoginId: (loginid: string) => void; + resetLocalStorageValues: (loginid: string) => void; setFinancialAndTradingAssessment: ( payload: SetFinancialAssessmentRequest ) => Promise; @@ -486,6 +565,7 @@ type TUiStore = { is_account_settings_visible: boolean; is_advanced_duration: boolean; is_cashier_visible: boolean; + is_wallet_modal_visible: boolean; is_chart_asset_info_visible?: boolean; is_chart_layout_default: boolean; is_closing_create_real_account_modal: boolean; @@ -523,6 +603,7 @@ type TUiStore = { setAppContentsScrollRef: (ref: React.MutableRefObject) => void; setCurrentFocus: (value: string | null) => void; setDarkMode: (is_dark_mode_on: boolean) => boolean; + setIsWalletModalVisible: (value: boolean) => void; setHasOnlyForwardingContracts: (has_only_forward_starting_contracts?: boolean) => void; setMobileLanguageMenuOpen: (is_mobile_language_menu_open: boolean) => void; setReportsTabIndex: (value: number) => void; @@ -577,25 +658,6 @@ type TUiStore = { vanilla_trade_type: 'VANILLALONGCALL' | 'VANILLALONGPUT'; }; -type TPortfolioPosition = { - contract_info: ProposalOpenContract & - Portfolio1 & { - contract_update?: ContractUpdate; - }; - details?: string; - display_name: string; - id?: number; - indicative: number; - payout?: number; - purchase?: number; - reference: number; - type?: string; - is_unsupported: boolean; - contract_update: ProposalOpenContract['limit_order']; - is_sell_requested: boolean; - profit_loss: number; -}; - type TPortfolioStore = { active_positions: TPortfolioPosition[]; active_positions_count: number; @@ -748,11 +810,6 @@ type TContractStore = { validation_errors: { contract_update_stop_loss: string[]; contract_update_take_profit: string[] }; }; -type TMenuStore = { - attach: (item: TMenuItem) => void; - update: (menu: TMenuItem, index: number) => void; -}; - type TNotificationStore = { addNotificationMessage: (message: TNotification) => void; addNotificationMessageByKey: (key: string) => void; @@ -762,13 +819,14 @@ type TNotificationStore = { filterNotificationMessages: () => void; notifications: TNotificationMessage[]; refreshNotifications: () => void; + removeAllNotificationMessages: (should_close_persistent: boolean) => void; removeNotifications: (should_close_persistent: boolean) => void; removeNotificationByKey: ({ key }: { key: string }) => void; removeNotificationMessage: ({ key, should_show_again }: { key: string; should_show_again?: boolean }) => void; removeNotificationMessageByKey: ({ key }: { key: string }) => void; setP2POrderProps: () => void; - showAccountSwitchToRealNotification: (loginid: string, currency: string) => void; setP2PRedirectTo: () => void; + showAccountSwitchToRealNotification: (loginid: string, currency: string) => void; setShouldShowPopups: (should_show_popups: boolean) => void; toggleNotificationsModal: () => void; }; @@ -797,6 +855,14 @@ type TTradersHubStore = { login: string; sub_title: string; icon: 'Derived' | 'Financial' | 'Options' | 'CFDs'; + status?: string; + action_type: 'get' | 'none' | 'trade' | 'dxtrade' | 'multi-action'; + key: string; + name: string; + landing_company_short?: 'bvi' | 'labuan' | 'svg' | 'vanuatu' | 'maltainvest'; + platform?: string; + description?: string; + market_type?: 'all' | 'financial' | 'synthetic'; }[]; openModal: (modal_id: string, props?: unknown) => void; selected_account: { @@ -808,27 +874,39 @@ type TTradersHubStore = { setIsOnboardingVisited: (is_visited: boolean) => void; show_eu_related_content: boolean; setTogglePlatformType: (platform_type: string) => void; + is_demo: boolean; is_real: boolean; selectRegion: (region: string) => void; closeAccountTransferModal: () => void; toggleRegulatorsCompareModal: () => void; - selected_region: string; - openFailedVerificationModal: (selected_account_type: string) => void; + openFailedVerificationModal: (selected_account_type: Record | string) => void; modal_data: TModalData; multipliers_account_status: string; financial_restricted_countries: boolean; selected_account_type: string; + selected_platform_type: string; setSelectedAccount: (account: { login?: string; account_id?: string }) => void; no_CR_account: boolean; no_MF_account: boolean; CFDs_restricted_countries: boolean; toggleAccountTransferModal: () => void; - is_demo: boolean; + is_real_wallets_upgrade_on: boolean; + toggleWalletsUpgrade: (value: boolean) => void; platform_real_balance: TBalance; cfd_demo_balance: TBalance; platform_demo_balance: TBalance; cfd_real_balance: TBalance; selectAccountType: (account_type: string) => void; + is_wallet_migration_failed: boolean; + setWalletsMigrationFailedPopup: (value: boolean) => void; + available_platforms: BrandConfig[]; + selected_region: TRegionAvailability; + getExistingAccounts: (platform: string, market_type: string) => AvailableAccount[]; + toggleAccountTypeModalVisibility: () => void; + active_modal_tab?: 'Deposit' | 'Withdraw' | 'Transfer' | 'Transactions'; + setWalletModalActiveTab: (tab?: 'Deposit' | 'Withdraw' | 'Transfer' | 'Transactions') => void; + active_modal_wallet_id?: string; + setWalletModalActiveWalletID: (wallet_id?: string) => void; available_cfd_accounts: TAvailableCFDAccounts[]; available_dxtrade_accounts: TAvailableCFDAccounts[]; available_ctrader_accounts: TAvailableCFDAccounts[]; @@ -839,9 +917,7 @@ type TTradersHubStore = { available_derivez_accounts: DetailsOfEachMT5Loginid[]; has_any_real_account: boolean; startTrade: () => void; - getExistingAccounts: () => void; getAccount: () => void; - toggleAccountTypeModalVisibility: () => void; showTopUpModal: () => void; }; @@ -880,7 +956,6 @@ type TGtmStore = { export type TCoreStores = { client: TClientStore; common: TCommonStore; - menu: TMenuStore; ui: TUiStore; portfolio: TPortfolioStore; contract_trade: TContractTradeStore; diff --git a/packages/trader/package.json b/packages/trader/package.json index baf6510d1b0e..dcb6682da3a1 100644 --- a/packages/trader/package.json +++ b/packages/trader/package.json @@ -86,7 +86,7 @@ "dependencies": { "@deriv/analytics": "^1.0.0", "@deriv/components": "^1.0.0", - "@deriv/deriv-api": "^1.0.11", + "@deriv/deriv-api": "^1.0.13", "@deriv/api-types": "^1.0.118", "@deriv/deriv-charts": "1.4.0", "@deriv/reports": "^1.0.0", diff --git a/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/__tests__/account-verification-required-modal.spec.tsx b/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/__tests__/account-verification-required-modal.spec.tsx index e8d4b3f23adc..5b078a65f760 100644 --- a/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/__tests__/account-verification-required-modal.spec.tsx +++ b/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/__tests__/account-verification-required-modal.spec.tsx @@ -1,10 +1,11 @@ import React from 'react'; -import { screen, render } from '@testing-library/react'; import { createBrowserHistory } from 'history'; -import AccountVerificationRequiredModal from '../account-verification-required-modal'; import { Router } from 'react-router-dom'; +import { screen, render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { routes, isMobile } from '@deriv/shared'; +import { routes } from '@deriv/shared'; +import { useStore } from '@deriv/stores'; +import AccountVerificationRequiredModal from '../account-verification-required-modal'; type TModal = React.FC<{ children: React.ReactNode; @@ -20,6 +21,16 @@ type TModal = React.FC<{ }>; }; +jest.mock('@deriv/stores', () => ({ + ...jest.requireActual('@deriv/stores'), + observer: jest.fn(x => x), + useStore: jest.fn(() => ({ + ui: { + is_mobile: true, + }, + })), +})); + jest.mock('@deriv/shared', () => ({ ...jest.requireActual('@deriv/shared'), isMobile: jest.fn(() => true), @@ -65,7 +76,11 @@ describe('', () => { expect(screen.getByText('auto')).toBeInTheDocument(); }); it('height should be 220px if isMobile is false', () => { - (isMobile as jest.Mock).mockReturnValue(false); + (useStore as jest.Mock).mockReturnValue({ + ui: { + is_mobile: false, + }, + }); render(); expect(screen.getByText('220px')).toBeInTheDocument(); }); diff --git a/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/__tests__/company-wide-limit-exceeded-modal.spec.tsx b/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/__tests__/company-wide-limit-exceeded-modal.spec.tsx index dba3b83f6656..8c839fe66352 100644 --- a/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/__tests__/company-wide-limit-exceeded-modal.spec.tsx +++ b/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/__tests__/company-wide-limit-exceeded-modal.spec.tsx @@ -37,6 +37,16 @@ jest.mock('@deriv/components', () => { }; }); +jest.mock('@deriv/stores', () => ({ + ...jest.requireActual('@deriv/stores'), + observer: jest.fn(x => x), + useStore: jest.fn(() => ({ + ui: { + is_mobile: false, + }, + })), +})); + describe('', () => { const mocked_props = { is_visible: true, diff --git a/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/__tests__/insufficient-balance-modal.spec.tsx b/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/__tests__/insufficient-balance-modal.spec.tsx index 997818dba918..fe4cdde3c975 100644 --- a/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/__tests__/insufficient-balance-modal.spec.tsx +++ b/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/__tests__/insufficient-balance-modal.spec.tsx @@ -19,6 +19,16 @@ type TModal = React.FC<{ }>; }; +jest.mock('@deriv/stores', () => ({ + ...jest.requireActual('@deriv/stores'), + observer: jest.fn(x => x), + useStore: jest.fn(() => ({ + ui: { + is_mobile: false, + }, + })), +})); + jest.mock('@deriv/components', () => { const original_module = jest.requireActual('@deriv/components'); const Modal: TModal = jest.fn(({ children, is_open, title }) => { diff --git a/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/account-verification-required-modal.tsx b/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/account-verification-required-modal.tsx index 58a5ed336f42..d04e470a271a 100644 --- a/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/account-verification-required-modal.tsx +++ b/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/account-verification-required-modal.tsx @@ -1,42 +1,48 @@ import React from 'react'; +import { useHistory } from 'react-router-dom'; import { Button, Modal } from '@deriv/components'; +import { routes } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; import { localize, Localize } from '@deriv/translations'; -import { isMobile, routes } from '@deriv/shared'; -import { useHistory } from 'react-router-dom'; type TAccountVerificationRequiredModalProps = { is_visible: boolean; onConfirm: () => void; }; -const AccountVerificationRequiredModal = ({ is_visible, onConfirm }: TAccountVerificationRequiredModalProps) => { - const history = useHistory(); - return ( - - - - +const AccountVerificationRequiredModal = observer( + ({ is_visible, onConfirm }: TAccountVerificationRequiredModalProps) => { + const history = useHistory(); + const { + ui: { is_mobile }, + } = useStore(); + return ( + + + + -
- -
-
- ); -}; +
+ +
+
+ ); + } +); export default AccountVerificationRequiredModal; diff --git a/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/company-wide-limit-exceeded-modal.tsx b/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/company-wide-limit-exceeded-modal.tsx index 2e8843804617..72b4ca5e2957 100644 --- a/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/company-wide-limit-exceeded-modal.tsx +++ b/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/company-wide-limit-exceeded-modal.tsx @@ -1,19 +1,22 @@ import React from 'react'; import { Button, Modal, StaticUrl } from '@deriv/components'; +import { observer, useStore } from '@deriv/stores'; import { localize, Localize } from '@deriv/translations'; -import { isMobile } from '@deriv/shared'; type TCompanyWideLimitExceededModal = { is_visible: boolean; onConfirm: () => void; }; -const CompanyWideLimitExceededModal = ({ is_visible, onConfirm }: TCompanyWideLimitExceededModal) => { +const CompanyWideLimitExceededModal = observer(({ is_visible, onConfirm }: TCompanyWideLimitExceededModal) => { + const { + ui: { is_mobile }, + } = useStore(); return ( @@ -28,6 +31,6 @@ const CompanyWideLimitExceededModal = ({ is_visible, onConfirm }: TCompanyWideLi ); -}; +}); export default CompanyWideLimitExceededModal; diff --git a/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/insufficient-balance-modal.tsx b/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/insufficient-balance-modal.tsx index ca272f97b474..f4fe48a7c416 100644 --- a/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/insufficient-balance-modal.tsx +++ b/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/insufficient-balance-modal.tsx @@ -1,7 +1,8 @@ import React from 'react'; import { RouteComponentProps, withRouter } from 'react-router-dom'; import { Button, Modal } from '@deriv/components'; -import { routes, isMobile } from '@deriv/shared'; +import { routes } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; import { localize } from '@deriv/translations'; type TInsufficientBalanceModal = RouteComponentProps & { @@ -11,38 +12,39 @@ type TInsufficientBalanceModal = RouteComponentProps & { toggleModal: () => void; }; -const InsufficientBalanceModal = ({ - history, - is_virtual, - is_visible, - message, - toggleModal, -}: TInsufficientBalanceModal) => ( - - {message} - - {/* TODO: Add topping up mechanism for demo accounts after confirmation */} -
); -}; +}); export default ContractTypeWidget; diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Multiplier/cancel-deal.jsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Multiplier/cancel-deal.jsx index cf59ea773a0b..42ffac9e2b05 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Multiplier/cancel-deal.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Multiplier/cancel-deal.jsx @@ -1,9 +1,11 @@ import React from 'react'; + import { Checkbox, Dropdown, Popover, PopoverMessageCheckbox } from '@deriv/components'; +import { observer, useStore } from '@deriv/stores'; import { localize } from '@deriv/translations'; + import Fieldset from 'App/Components/Form/fieldset'; -import { onToggleCancellation, onChangeCancellationDuration } from 'Stores/Modules/Trading/Helpers/multiplier'; -import { observer, useStore } from '@deriv/stores'; +import { onChangeCancellationDuration,onToggleCancellation } from 'Stores/Modules/Trading/Helpers/multiplier'; import { useTraderStore } from 'Stores/useTraderStores'; const CancelDeal = observer(() => { diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/__tests__/barriers-list.spec.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/__tests__/barriers-list.spec.tsx index 44907c267788..5a21254fa22a 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/__tests__/barriers-list.spec.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/__tests__/barriers-list.spec.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom'; import userEvent from '@testing-library/user-event'; +import { StoreProvider, mockStore } from '@deriv/stores'; import BarriersList from '../barriers-list'; const barrier_choices = ['16', '33', '40']; @@ -11,19 +12,26 @@ const mockHoverCallback = jest.fn(); const mockClickCrossCallback = jest.fn(); describe('', () => { + const mock_store = mockStore({ + ui: { + is_mobile: false, + }, + }); beforeEach(() => { render( - + + + ); }); it('all barrier options should be rendered', () => { diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/__tests__/min-max-stake-info.spec.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/__tests__/min-max-stake-info.spec.tsx index a79f3796ce1f..e685c3c1c40c 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/__tests__/min-max-stake-info.spec.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/__tests__/min-max-stake-info.spec.tsx @@ -2,8 +2,10 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom'; import MinMaxStakeInfo from '../min-max-stake-info'; +import { StoreProvider, mockStore } from '@deriv/stores'; describe('', () => { + const store = mockStore({}); const mock_props = { className: 'trade-container__stake-field', currency: 'USD', @@ -12,7 +14,11 @@ describe('', () => { }; it('should be rendered correctly with both Min. stake and Max. stake', () => { - render(); + render( + + + + ); [screen.getByText('Min. stake'), screen.getByText('Max. stake')].forEach(stake_text => { expect(stake_text).toBeInTheDocument(); diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/barriers-list-body.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/barriers-list-body.tsx index b109b2bf6d9b..a265bf645893 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/barriers-list-body.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/barriers-list-body.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import { isMobile } from '@deriv/shared'; -import { Text, ThemedScrollbars } from '@deriv/components'; import classNames from 'classnames'; +import { Text, ThemedScrollbars } from '@deriv/components'; +import { observer, useStore } from '@deriv/stores'; export type TBarriersListBody = { barriers_list: string[]; @@ -12,57 +12,55 @@ export type TBarriersListBody = { subheader?: string; }; -const BarriersListBody = ({ - barriers_list, - className, - onClick, - onHover, - selected_item, - subheader, -}: TBarriersListBody) => { - const onMouseEnter = (barrier: string) => { - if (selected_item !== barrier && typeof onHover === 'function') { - onHover(barrier); - } - }; - return ( - - {subheader && ( - - {subheader} - - )} - -
    - {barriers_list.map(barrier => ( - onClick(barrier)} - onMouseEnter={() => onMouseEnter(barrier)} - onMouseLeave={() => typeof onHover === 'function' && onHover('')} - > - {barrier} - - ))} -
-
-
- ); -}; +const BarriersListBody = observer( + ({ barriers_list, className, onClick, onHover, selected_item, subheader }: TBarriersListBody) => { + const { + ui: { is_mobile }, + } = useStore(); + const onMouseEnter = (barrier: string) => { + if (selected_item !== barrier && typeof onHover === 'function') { + onHover(barrier); + } + }; + return ( + + {subheader && ( + + {subheader} + + )} + +
    + {barriers_list.map(barrier => ( + onClick(barrier)} + onMouseEnter={() => onMouseEnter(barrier)} + onMouseLeave={() => typeof onHover === 'function' && onHover('')} + > + {barrier} + + ))} +
+
+
+ ); + } +); export default React.memo(BarriersListBody); diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/min-max-stake-info.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/min-max-stake-info.tsx index 6c74f6de5cce..24e3541fe651 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/min-max-stake-info.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/min-max-stake-info.tsx @@ -2,7 +2,7 @@ import classNames from 'classnames'; import React from 'react'; import { Money, Text } from '@deriv/components'; import { Localize } from '@deriv/translations'; -import { isMobile } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; type TMinMaxStakeInfo = { className?: string; @@ -11,7 +11,10 @@ type TMinMaxStakeInfo = { currency?: string; }; -const MinMaxStakeInfo = ({ className, currency, max_stake, min_stake }: TMinMaxStakeInfo) => { +const MinMaxStakeInfo = observer(({ className, currency, max_stake, min_stake }: TMinMaxStakeInfo) => { + const { + ui: { is_mobile }, + } = useStore(); return (
{!isNaN(Number(min_stake)) && @@ -21,7 +24,7 @@ const MinMaxStakeInfo = ({ className, currency, max_stake, min_stake }: TMinMaxS key={text} as='p' line_height='s' - size={isMobile() ? 'xxs' : 'xxxs'} + size={is_mobile ? 'xxs' : 'xxxs'} className={`trade-container__stake-field--${text.toLowerCase()}`} > ); -}; +}); export default MinMaxStakeInfo; diff --git a/packages/trader/src/Modules/Trading/Components/Form/__tests__/form-layout.spec.tsx b/packages/trader/src/Modules/Trading/Components/Form/__tests__/form-layout.spec.tsx index aac71ab5a862..ccd867780c23 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/__tests__/form-layout.spec.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/__tests__/form-layout.spec.tsx @@ -1,9 +1,11 @@ import React from 'react'; -import { render, screen } from '@testing-library/react'; -import FormLayout from '../form-layout'; import Loadable from 'react-loadable'; -import TraderProviders from '../../../../../trader-providers'; + import { mockStore } from '@deriv/stores'; +import { render, screen } from '@testing-library/react'; + +import TraderProviders from '../../../../../trader-providers'; +import FormLayout from '../form-layout'; Loadable.preloadAll(); diff --git a/packages/trader/src/Modules/Trading/Components/Form/__tests__/screen-large.spec.tsx b/packages/trader/src/Modules/Trading/Components/Form/__tests__/screen-large.spec.tsx index f824ccd64cf6..1e9b5aaceec2 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/__tests__/screen-large.spec.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/__tests__/screen-large.spec.tsx @@ -1,5 +1,7 @@ import React from 'react'; + import { render, screen } from '@testing-library/react'; + import ScreenLarge from '../screen-large'; jest.mock('App/Components/Elements/ContentLoader', () => ({ diff --git a/packages/trader/src/Modules/Trading/Containers/__tests__/purchase.spec.tsx b/packages/trader/src/Modules/Trading/Containers/__tests__/purchase.spec.tsx index 1e2a0d93dd38..a80a845810a4 100644 --- a/packages/trader/src/Modules/Trading/Containers/__tests__/purchase.spec.tsx +++ b/packages/trader/src/Modules/Trading/Containers/__tests__/purchase.spec.tsx @@ -1,8 +1,10 @@ import React from 'react'; -import { render, screen } from '@testing-library/react'; + import { mockStore } from '@deriv/stores'; -import TraderProviders from '../../../../trader-providers'; import { TCoreStores } from '@deriv/stores/types'; +import { render, screen } from '@testing-library/react'; + +import TraderProviders from '../../../../trader-providers'; import Purchase from '../purchase'; const default_mock_store = { diff --git a/packages/trader/src/Utils/Validator/validator.ts b/packages/trader/src/Utils/Validator/validator.ts index c51f76b38287..4adf2224591b 100644 --- a/packages/trader/src/Utils/Validator/validator.ts +++ b/packages/trader/src/Utils/Validator/validator.ts @@ -1,4 +1,5 @@ import { Errors, getPreBuildDVRs, template } from '@deriv/shared'; + import { getValidationRules } from 'Stores/Modules/Trading/Constants/validation-rules'; import { TTradeStore } from 'Types'; diff --git a/packages/utils/package.json b/packages/utils/package.json index 25092ee3c66f..990ad33a913a 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -3,9 +3,15 @@ "private": true, "version": "1.0.0", "main": "src/index.ts", - "dependencies": {}, + "dependencies": { + "lodash.groupby": "^4.6.0", + "lodash.pickby": "^4.6.0", + "moment": "^2.29.2" + }, "devDependencies": { - "typescript": "^4.6.3", - "@deriv/api-types": "^1.0.118" + "@deriv/api-types": "^1.0.118", + "@types/lodash.groupby": "^4.6.7", + "@types/lodash.pickby": "^4.6.7", + "typescript": "^4.6.3" } } diff --git a/packages/utils/src/__tests__/getLocalStorage.spec.ts b/packages/utils/src/__tests__/getLocalStorage.spec.ts new file mode 100644 index 000000000000..2791dd262e3f --- /dev/null +++ b/packages/utils/src/__tests__/getLocalStorage.spec.ts @@ -0,0 +1,47 @@ +import { getLocalStorage } from '../getLocalStorage'; + +describe('getLocalStorage', () => { + beforeEach(() => { + localStorage.clear(); + }); + + test('should retrieve the stored value from localStorage', () => { + const key = 'test_key'; + const value = 'Hello'; + + localStorage.setItem(key, JSON.stringify(value)); + + const result = getLocalStorage(key); + + expect(result).toBe(value); + }); + + test('should return null when localStorage key does not exist', () => { + const key = 'non_existent_key'; + + const result = getLocalStorage(key); + + expect(result).toBeNull(); + }); + + test('should return null when localStorage value is null', () => { + const key = 'test_key'; + + localStorage.setItem(key, JSON.stringify(null)); + + const result = getLocalStorage(key); + + expect(result).toBeNull(); + }); + + test('should parse and return the stored value as an object', () => { + const key = 'test_key'; + const value = { name: 'John', age: 30 }; + + localStorage.setItem(key, JSON.stringify(value)); + + const result = getLocalStorage(key); + + expect(result).toEqual(value); + }); +}); diff --git a/packages/utils/src/__tests__/getWalletCurrencyIcon.spec.ts b/packages/utils/src/__tests__/getWalletCurrencyIcon.spec.ts new file mode 100644 index 000000000000..f14534d0a2b1 --- /dev/null +++ b/packages/utils/src/__tests__/getWalletCurrencyIcon.spec.ts @@ -0,0 +1,95 @@ +import getWalletCurrencyIcon from '../getWalletCurrencyIcon'; + +describe('getWalletCurrencyIcon', () => { + describe('Should return proper icons for cashier modal', () => { + it('should return proper icon for demo currency', () => { + expect(getWalletCurrencyIcon('demo', false, true)).toBe('IcWalletDerivDemoLight'); + }); + + it('should return proper icon for USDT, eUSDT, tUSDT, UST currency in dark mode', () => { + const currencies = ['USDT', 'eUSDT', 'tUSDT', 'UST']; + currencies.forEach(currency => { + expect(getWalletCurrencyIcon(currency, true, true)).toBe('IcWalletModalTetherDark'); + }); + }); + + it('should return proper icon for USDT, eUSDT, tUSDT, UST currency in light mode', () => { + const currencies = ['USDT', 'eUSDT', 'tUSDT', 'UST']; + currencies.forEach(currency => { + expect(getWalletCurrencyIcon(currency, false, true)).toBe('IcWalletModalTetherLight'); + }); + }); + }); + + describe('Should return proper icons for other components', () => { + it('should return proper icon for demo currency in dark mode', () => { + expect(getWalletCurrencyIcon('demo', false)).toBe('IcWalletDerivDemoLight'); + }); + + it('should return proper icon for demo currency in light mode', () => { + expect(getWalletCurrencyIcon('demo', true)).toBe('IcWalletDerivDemoDark'); + }); + + it('should return proper icon for USD currency in dark/light mode', () => { + expect(getWalletCurrencyIcon('USD')).toBe('IcWalletCurrencyUsd'); + }); + + it('should return proper icon for EUR currency in dark/light mode', () => { + expect(getWalletCurrencyIcon('EUR')).toBe('IcWalletCurrencyEur'); + }); + + it('should return proper icon for AUD currency in dark/light mode', () => { + expect(getWalletCurrencyIcon('AUD')).toBe('IcWalletCurrencyAud'); + }); + + it('should return proper icon for GBP currency in dark/light mode', () => { + expect(getWalletCurrencyIcon('GBP')).toBe('IcWalletCurrencyGbp'); + }); + + it('should return proper icon for BTC currency in dark mode', () => { + expect(getWalletCurrencyIcon('BTC', false)).toBe('IcWalletBitcoinLight'); + }); + + it('should return proper icon for BTC currency in light mode', () => { + expect(getWalletCurrencyIcon('BTC', true)).toBe('IcWalletBitcoinDark'); + }); + + it('should return proper icon for ETH currency in dark mode', () => { + expect(getWalletCurrencyIcon('ETH', false)).toBe('IcWalletEthereumLight'); + }); + + it('should return proper icon for ETH currency in light mode', () => { + expect(getWalletCurrencyIcon('ETH', true)).toBe('IcWalletEthereumDark'); + }); + + it('should return proper icon for USDT, eUSDT, tUSDT, UST currency in dark mode', () => { + const currencies = ['USDT', 'eUSDT', 'tUSDT', 'UST']; + currencies.forEach(currency => { + expect(getWalletCurrencyIcon(currency, true)).toBe('IcWalletTetherDark'); + }); + }); + + it('should return proper icon for USDT, eUSDT, tUSDT, UST currency in light mode', () => { + const currencies = ['USDT', 'eUSDT', 'tUSDT', 'UST']; + currencies.forEach(currency => { + expect(getWalletCurrencyIcon(currency, false)).toBe('IcWalletTetherLight'); + }); + }); + + it('should return proper icon for LTC currency in dark mode', () => { + expect(getWalletCurrencyIcon('LTC', false)).toBe('IcWalletLiteCoinLight'); + }); + + it('should return proper icon for LTC currency in light mode', () => { + expect(getWalletCurrencyIcon('LTC', true)).toBe('IcWalletLiteCoinDark'); + }); + + it('should return proper icon for USDC currency in dark mode', () => { + expect(getWalletCurrencyIcon('USDC', false)).toBe('IcWalletUsdCoinLight'); + }); + + it('should return proper icon for USDC currency in light mode', () => { + expect(getWalletCurrencyIcon('USDC', true)).toBe('IcWalletUsdCoinDark'); + }); + }); +}); diff --git a/packages/utils/src/__tests__/groupTransactionsByDay.spec.ts b/packages/utils/src/__tests__/groupTransactionsByDay.spec.ts new file mode 100644 index 000000000000..d49958cecec8 --- /dev/null +++ b/packages/utils/src/__tests__/groupTransactionsByDay.spec.ts @@ -0,0 +1,33 @@ +import groupTransactionsByDay from '../groupTransactionsByDay'; +import { Statement } from '@deriv/api-types'; + +describe('groupTransactionsByDay', () => { + it('should group transactions by day', () => { + const transactions: Statement['transactions'] = [ + { + transaction_time: new Date('08 Jun 2023').getTime() / 1000, + }, + { + transaction_time: new Date('10 Jun 2023').getTime() / 1000, + }, + { + transaction_time: new Date('12 Jun 2023').getTime() / 1000, + }, + { + transaction_time: new Date('08 Jun 2023').getTime() / 1000, + }, + { + transaction_time: new Date('10 Jun 2023').getTime() / 1000, + }, + { + transaction_time: new Date('12 Jun 2023').getTime() / 1000, + }, + ]; + + const grouped_transactions = groupTransactionsByDay(transactions); + + expect(transactions).toHaveLength(6); + expect(Object.keys(grouped_transactions)).toHaveLength(3); + expect(Object.keys(grouped_transactions)).toStrictEqual(['08 Jun 2023', '10 Jun 2023', '12 Jun 2023']); + }); +}); diff --git a/packages/utils/src/getLocalStorage.ts b/packages/utils/src/getLocalStorage.ts new file mode 100644 index 000000000000..dfc8629515f7 --- /dev/null +++ b/packages/utils/src/getLocalStorage.ts @@ -0,0 +1,18 @@ +/** check is stringified object or not */ +const checkIsStringifiedObject = (s: string) => { + try { + return JSON.parse(s); + } catch (error) { + return s; + } +}; + +/** + * Retrieves the value stored in localStorage for the given key. + * @param {string} key - The localStorage key. + * @returns {any} - The value stored in localStorage for the given key, or null if the key does not exist or has no value. + */ +export const getLocalStorage = (key: string) => { + const data = localStorage.getItem(key); + return data ? checkIsStringifiedObject(data) : null; +}; diff --git a/packages/utils/src/getWalletCurrencyIcon.ts b/packages/utils/src/getWalletCurrencyIcon.ts new file mode 100644 index 000000000000..4c409738fa8d --- /dev/null +++ b/packages/utils/src/getWalletCurrencyIcon.ts @@ -0,0 +1,35 @@ +const getWalletCurrencyIcon = (currency: string, is_dark_mode_on = false, is_modal = false) => { + switch (currency) { + case 'demo': + if (is_modal) return 'IcWalletDerivDemoLight'; + return is_dark_mode_on ? 'IcWalletDerivDemoDark' : 'IcWalletDerivDemoLight'; + case 'USD': + return 'IcWalletCurrencyUsd'; + case 'EUR': + return 'IcWalletCurrencyEur'; + case 'AUD': + return 'IcWalletCurrencyAud'; + case 'GBP': + return 'IcWalletCurrencyGbp'; + case 'BTC': + return is_dark_mode_on ? 'IcWalletBitcoinDark' : 'IcWalletBitcoinLight'; + case 'ETH': + return is_dark_mode_on ? 'IcWalletEthereumDark' : 'IcWalletEthereumLight'; + case 'USDT': + case 'eUSDT': + case 'tUSDT': + case 'UST': + if (is_modal) { + return is_dark_mode_on ? 'IcWalletModalTetherDark' : 'IcWalletModalTetherLight'; + } + return is_dark_mode_on ? 'IcWalletTetherDark' : 'IcWalletTetherLight'; + case 'LTC': + return is_dark_mode_on ? 'IcWalletLiteCoinDark' : 'IcWalletLiteCoinLight'; + case 'USDC': + return is_dark_mode_on ? 'IcWalletUsdCoinDark' : 'IcWalletUsdCoinLight'; + default: + return ''; + } +}; + +export default getWalletCurrencyIcon; diff --git a/packages/utils/src/groupTransactionsByDay.ts b/packages/utils/src/groupTransactionsByDay.ts new file mode 100644 index 000000000000..e7379c15eb89 --- /dev/null +++ b/packages/utils/src/groupTransactionsByDay.ts @@ -0,0 +1,21 @@ +import groupBy from 'lodash.groupby'; +import pickBy from 'lodash.pickby'; +import moment from 'moment'; +import { Statement } from '@deriv/api-types'; + +const groupTransactionsByDay = (transactions: Statement['transactions']) => { + const grouped_transactions = pickBy( + groupBy(transactions, transaction => { + return transaction.transaction_time + ? moment(transaction.transaction_time * 1000) + .startOf('day') + .format('DD MMM YYYY') + : null; + }), + (value, key) => key !== null + ); + + return grouped_transactions; +}; + +export default groupTransactionsByDay; diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 5c70d1ebd2a0..34e53a1a1343 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,6 +1,9 @@ export { default as getAccountsFromLocalStorage } from './getAccountsFromLocalStorage'; export { default as getActiveAuthTokenIDFromLocalStorage } from './getActiveAuthTokenIDFromLocalStorage'; export { default as getActiveLoginIDFromLocalStorage } from './getActiveLoginIDFromLocalStorage'; +export { getLocalStorage } from './getLocalStorage'; +export { default as getWalletCurrencyIcon } from './getWalletCurrencyIcon'; +export { default as groupTransactionsByDay } from './groupTransactionsByDay'; export { default as getTruncatedString } from './getTruncatedString'; export { default as unFormatLocaleString } from './unFormatLocaleString'; export * from './parse-url'; diff --git a/packages/wallets/src/components/CreatePassword/CreatePassword.tsx b/packages/wallets/src/components/CreatePassword/CreatePassword.tsx index 48b90482326c..aab983bfa26a 100644 --- a/packages/wallets/src/components/CreatePassword/CreatePassword.tsx +++ b/packages/wallets/src/components/CreatePassword/CreatePassword.tsx @@ -1,6 +1,9 @@ import React from 'react'; + import { useAvailableMT5Accounts, useCreateOtherCFDAccount } from '@deriv/api'; + import PasswordShowIcon from '../../public/images/ic-password-show.svg'; + import './CreatePassword.scss'; type TPlatformMT5 = NonNullable['data']>[number]['platform']; diff --git a/packages/wallets/src/components/DesktopWalletsList/DesktopWalletsList.tsx b/packages/wallets/src/components/DesktopWalletsList/DesktopWalletsList.tsx index d84ef33bc816..b4f64c50c0e0 100644 --- a/packages/wallets/src/components/DesktopWalletsList/DesktopWalletsList.tsx +++ b/packages/wallets/src/components/DesktopWalletsList/DesktopWalletsList.tsx @@ -1,8 +1,11 @@ import React from 'react'; + import { useAuthorize, useWalletAccountsList } from '@deriv/api'; + import { AccountsList } from '../AccountsList'; import { WalletListCard } from '../WalletListCard'; import { WalletsAccordion } from '../WalletsAccordion'; + import './DesktopWalletsList.scss'; const DesktopWalletsList: React.FC = () => { diff --git a/packages/wallets/src/components/DxtradeEnterPasswordModal/DxtradeEnterPasswordModal.tsx b/packages/wallets/src/components/DxtradeEnterPasswordModal/DxtradeEnterPasswordModal.tsx index 542de3a944cc..f021ef9d4c20 100644 --- a/packages/wallets/src/components/DxtradeEnterPasswordModal/DxtradeEnterPasswordModal.tsx +++ b/packages/wallets/src/components/DxtradeEnterPasswordModal/DxtradeEnterPasswordModal.tsx @@ -1,9 +1,12 @@ import React, { useEffect, useState } from 'react'; + import { useActiveWalletAccount, useCreateOtherCFDAccount } from '@deriv/api'; + import DxTradePasswordIcon from '../../public/images/ic-dxtrade-password.svg'; import { CreatePassword } from '../CreatePassword'; import { useModal } from '../ModalProvider'; import { ModalWrapper } from '../ModalWrapper'; + import './DxtradeEnterPasswordModal.scss'; const DxtradeEnterPasswordModal = () => { diff --git a/packages/wallets/src/components/JurisdictionModal/JurisdictionCard.tsx b/packages/wallets/src/components/JurisdictionModal/JurisdictionCard.tsx index a264c5af14cc..7598ca1ff2f7 100644 --- a/packages/wallets/src/components/JurisdictionModal/JurisdictionCard.tsx +++ b/packages/wallets/src/components/JurisdictionModal/JurisdictionCard.tsx @@ -1,7 +1,9 @@ import React, { useState } from 'react'; import classNames from 'classnames'; + import JurisdictionCardRow from './JurisdictionCardRow'; import JurisdictionCardTag from './JurisdictionCardTag'; + import './JurisdictionCard.scss'; type TJurisdictionCardProps = { diff --git a/packages/wallets/src/components/JurisdictionModal/JurisdictionCardRow.tsx b/packages/wallets/src/components/JurisdictionModal/JurisdictionCardRow.tsx index 8e3a9b1e2e48..848a8d5c3441 100644 --- a/packages/wallets/src/components/JurisdictionModal/JurisdictionCardRow.tsx +++ b/packages/wallets/src/components/JurisdictionModal/JurisdictionCardRow.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import './JurisdictionCardRow.scss'; type TJurisdictionCardRowProps = { diff --git a/packages/wallets/src/components/JurisdictionModal/JurisdictionCardTag.tsx b/packages/wallets/src/components/JurisdictionModal/JurisdictionCardTag.tsx index 49f9b133f895..1afb3f80d555 100644 --- a/packages/wallets/src/components/JurisdictionModal/JurisdictionCardTag.tsx +++ b/packages/wallets/src/components/JurisdictionModal/JurisdictionCardTag.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import './JurisdictionCardTag.scss'; type TJurisdictionCardTagProps = { diff --git a/packages/wallets/src/components/MT5PasswordModal/MT5PasswordModal.tsx b/packages/wallets/src/components/MT5PasswordModal/MT5PasswordModal.tsx index 05006ae3e212..163f6cf8a0a6 100644 --- a/packages/wallets/src/components/MT5PasswordModal/MT5PasswordModal.tsx +++ b/packages/wallets/src/components/MT5PasswordModal/MT5PasswordModal.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from 'react'; + import { useActiveWalletAccount, useAvailableMT5Accounts, @@ -7,6 +8,7 @@ import { useSettings, useSortedMT5Accounts, } from '@deriv/api'; + import MT5PasswordIcon from '../../public/images/ic-mt5-password.svg'; import { CreatePassword } from '../CreatePassword'; import { EnterPassword } from '../EnterPassword'; diff --git a/packages/wallets/src/components/ModalWrapper/ModalWrapper.tsx b/packages/wallets/src/components/ModalWrapper/ModalWrapper.tsx index fde9dcb20171..30418c1f1607 100644 --- a/packages/wallets/src/components/ModalWrapper/ModalWrapper.tsx +++ b/packages/wallets/src/components/ModalWrapper/ModalWrapper.tsx @@ -1,6 +1,8 @@ import React, { FC } from 'react'; + import CloseIcon from '../../public/images/ic-close-dark.svg'; import { useModal } from '../ModalProvider'; + import './ModalWrapper.scss'; type TProps = { diff --git a/packages/wallets/src/components/WalletCard/WalletCard.tsx b/packages/wallets/src/components/WalletCard/WalletCard.tsx index e9343b691914..c68b2e2d96a6 100644 --- a/packages/wallets/src/components/WalletCard/WalletCard.tsx +++ b/packages/wallets/src/components/WalletCard/WalletCard.tsx @@ -1,8 +1,11 @@ import React from 'react'; + import { useWalletAccountsList } from '@deriv/api'; + import { WalletCardIcon } from '../WalletCardIcon'; import { WalletGradientBackground } from '../WalletGradientBackground'; import { WalletListCardBadge } from '../WalletListCardBadge'; + import './WalletCard.scss'; type TProps = { diff --git a/packages/wallets/src/components/WalletCardIcon/WalletCardIcon.tsx b/packages/wallets/src/components/WalletCardIcon/WalletCardIcon.tsx index 6d265f8927e8..3632c2fd5d30 100644 --- a/packages/wallets/src/components/WalletCardIcon/WalletCardIcon.tsx +++ b/packages/wallets/src/components/WalletCardIcon/WalletCardIcon.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import useDevice from '../../hooks/useDevice'; import Bitcoin from '../../public/images/bitcoin.svg'; import Demo from '../../public/images/demo.svg'; diff --git a/packages/wallets/src/components/WalletsAccordion/WalletsAccordion.tsx b/packages/wallets/src/components/WalletsAccordion/WalletsAccordion.tsx index f27fb2d59aa6..775a9ab22f94 100644 --- a/packages/wallets/src/components/WalletsAccordion/WalletsAccordion.tsx +++ b/packages/wallets/src/components/WalletsAccordion/WalletsAccordion.tsx @@ -1,5 +1,7 @@ import React, { useEffect, useRef } from 'react'; + import IcDropdown from '../../public/images/ic-dropdown.svg'; + import './WalletsAccordion.scss'; type TProps = {