From e49caa3932391de274b4dbc644c4ea49f3aba5a3 Mon Sep 17 00:00:00 2001 From: pietro-maximoff Date: Tue, 15 Dec 2020 11:44:27 +0300 Subject: [PATCH 1/4] add logic for pop-up tutorials, when should show --- src/app/containers/SandboxPage/index.tsx | 4 +- .../TutorialDialogModal/Loadable.tsx | 15 ++ .../TutorialDialogModal/component/index.tsx | 111 ++++++++++ .../component/screen1/index.tsx | 85 +++++++ .../component/screen2/index.tsx | 208 ++++++++++++++++++ .../component/screen3/index.tsx | 36 +++ .../component/screen4/index.tsx | 175 +++++++++++++++ .../componentMobile/index.tsx | 27 +++ .../componentMobile/screen1/index.tsx | 56 +++++ .../componentMobile/screen2/index.tsx | 5 + .../componentMobile/screen3/index.tsx | 162 ++++++++++++++ .../containers/TutorialDialogModal/index.tsx | 88 ++++++++ .../mobileNotReady/index.tsx | 100 +++++++++ .../TutorialDialogModal/selectors.ts | 11 + .../containers/TutorialDialogModal/slice.ts | 21 ++ .../containers/TutorialDialogModal/types.ts | 9 + src/app/containers/WalletConnector/index.tsx | 27 ++- src/app/index.tsx | 5 +- src/locales/en/translation.json | 32 --- src/styles/global-styles.ts | 1 - src/types/RootState.ts | 2 + src/utils/classifiers.ts | 2 + 22 files changed, 1136 insertions(+), 46 deletions(-) create mode 100644 src/app/containers/TutorialDialogModal/Loadable.tsx create mode 100644 src/app/containers/TutorialDialogModal/component/index.tsx create mode 100644 src/app/containers/TutorialDialogModal/component/screen1/index.tsx create mode 100644 src/app/containers/TutorialDialogModal/component/screen2/index.tsx create mode 100644 src/app/containers/TutorialDialogModal/component/screen3/index.tsx create mode 100644 src/app/containers/TutorialDialogModal/component/screen4/index.tsx create mode 100644 src/app/containers/TutorialDialogModal/componentMobile/index.tsx create mode 100644 src/app/containers/TutorialDialogModal/componentMobile/screen1/index.tsx create mode 100644 src/app/containers/TutorialDialogModal/componentMobile/screen2/index.tsx create mode 100644 src/app/containers/TutorialDialogModal/componentMobile/screen3/index.tsx create mode 100644 src/app/containers/TutorialDialogModal/index.tsx create mode 100644 src/app/containers/TutorialDialogModal/mobileNotReady/index.tsx create mode 100644 src/app/containers/TutorialDialogModal/selectors.ts create mode 100644 src/app/containers/TutorialDialogModal/slice.ts create mode 100644 src/app/containers/TutorialDialogModal/types.ts diff --git a/src/app/containers/SandboxPage/index.tsx b/src/app/containers/SandboxPage/index.tsx index 87679ef3f..82a856c45 100644 --- a/src/app/containers/SandboxPage/index.tsx +++ b/src/app/containers/SandboxPage/index.tsx @@ -5,14 +5,14 @@ */ import React from 'react'; -import { TutorialDialog } from '../../components/TutorialDialog/container'; +import { TutorialDialogModal } from '../../containers/TutorialDialogModal/Loadable'; interface Props {} export function SandboxPage(props: Props) { return (
- +
); } diff --git a/src/app/containers/TutorialDialogModal/Loadable.tsx b/src/app/containers/TutorialDialogModal/Loadable.tsx new file mode 100644 index 000000000..cc43db03a --- /dev/null +++ b/src/app/containers/TutorialDialogModal/Loadable.tsx @@ -0,0 +1,15 @@ +/** + * + * Asynchronously loads the component for TutorialDialogModal + * + */ + +import React from 'react'; +import { lazyLoad } from 'utils/loadable'; +import { PageSkeleton } from 'app/components/PageSkeleton'; + +export const TutorialDialogModal = lazyLoad( + () => import('./index'), + module => module.TutorialDialogModal, + { fallback: }, +); diff --git a/src/app/containers/TutorialDialogModal/component/index.tsx b/src/app/containers/TutorialDialogModal/component/index.tsx new file mode 100644 index 000000000..c3cb36666 --- /dev/null +++ b/src/app/containers/TutorialDialogModal/component/index.tsx @@ -0,0 +1,111 @@ +import React, { useState } from 'react'; +import { reactLocalStorage } from 'reactjs-localstorage'; +import { useTranslation } from 'react-i18next'; +import background from 'assets/images/tutorial/test.svg'; +import close from 'assets/images/tutorial/close.svg'; +import { translations } from 'locales/i18n'; +import { Screen1 } from './screen1'; +import { Screen2 } from './screen2'; +import { Screen3 } from './screen3'; +import { Screen4 } from './screen4'; + +export function TutorialDialogComponent(props) { + const { t } = useTranslation(); + const [mouseLeave, setMouseLeave] = useState(false); + const activeTutorial = + reactLocalStorage.get('tutorial_active') === 'true' && + props.onNetwork === true + ? true + : false; + const [screen, setScreen] = useState(activeTutorial ? 2 : 1); + + function changeScreen(num) { + setScreen(num); + } + + function back() { + screen === 3 ? setScreen(4) : setScreen(1); + } + + return ( + <> +
{ + console.log('Mouse out'); + if (screen === 2) { + setMouseLeave(true); + } else { + setMouseLeave(false); + } + }} + > +
+
+ +
+
+ close +
+ {screen !== 1 && ( +
back()}> + +
+ )} +
+

+ {t( + translations.rskConnectTutorial.screens[screen.toString()] + .title, + )} +

+
+
+

+ {t( + translations.rskConnectTutorial.screens[screen.toString()] + .banner, + )}{' '} + {screen === 1 && ( + + Metamask.io + + )} + {screen !== 1 && ( + + https://discord.com/invite/J22WS6z + + )} +

+
+ {/* */} + {screen === 1 && ( + + )} + {screen === 2 && ( + + )} + {screen === 3 && } + {screen === 4 && } +
+
+ + ); +} diff --git a/src/app/containers/TutorialDialogModal/component/screen1/index.tsx b/src/app/containers/TutorialDialogModal/component/screen1/index.tsx new file mode 100644 index 000000000..b48e68471 --- /dev/null +++ b/src/app/containers/TutorialDialogModal/component/screen1/index.tsx @@ -0,0 +1,85 @@ +import React from 'react'; +import rectangle from 'assets/images/tutorial/screen1_rectangle.svg'; +import browserIcon from 'assets/images/tutorial/brower_icon.svg'; +import mobileIcon from 'assets/images/tutorial/mobile_icon.svg'; +import hardwareIcon from 'assets/images/tutorial/hardware_icon.svg'; +import badger1 from 'assets/images/tutorial/badger_1.svg'; +import planet from 'assets/images/tutorial/planet.svg'; +import { translations } from 'locales/i18n'; +import { useTranslation } from 'react-i18next'; + +interface Props { + handleClick: (num: Number) => void; + onNetwork: boolean; + handleEngage: () => void; +} + +export function Screen1(props: Props) { + const { t } = useTranslation(); + + function handleBrowserClick() { + if (props.onNetwork === true) { + props.handleEngage(); + } else { + props.handleClick(2); + } + } + + return ( + <> +
+
+ +
+
+ +
+
+
+ +
+
+ browser wallet icon +
+
+

{t(translations.rskConnectTutorial.browser_wallet)}t

+
+
+
props.handleClick(4)}> +
+ +
+
+ browser wallet icon +
+
+

{t(translations.rskConnectTutorial.mobile_wallet)}

+
+
+
+
+ +
+
+ browser wallet icon +
+
+

{t(translations.rskConnectTutorial.hardware_wallet)}

+
+
+
+ + ); +} diff --git a/src/app/containers/TutorialDialogModal/component/screen2/index.tsx b/src/app/containers/TutorialDialogModal/component/screen2/index.tsx new file mode 100644 index 000000000..9a6b01fc3 --- /dev/null +++ b/src/app/containers/TutorialDialogModal/component/screen2/index.tsx @@ -0,0 +1,208 @@ +import React, { useState, useEffect } from 'react'; +import { translations } from 'locales/i18n'; +import { useTranslation } from 'react-i18next'; +import arm1 from 'assets/images/tutorial/arm_1.svg'; +import arm2 from 'assets/images/tutorial/arm_2.svg'; +import stepBox from 'assets/images/tutorial/step-box.svg'; +import leftArrow from 'assets/images/tutorial/left_arrow.svg'; +import rightArrow from 'assets/images/tutorial/right_arrow.svg'; +import engage from 'assets/images/tutorial/engage.svg'; +import speechBubble from 'assets/images/tutorial/speech_bubble.svg'; +import leftBox from 'assets/images/tutorial/left_box.svg'; +import rightBox from 'assets/images/tutorial/right_box.svg'; +import badgerBody from 'assets/images/tutorial/badger_body.svg'; +import crater from 'assets/images/tutorial/crater.svg'; +import { Icon } from '@blueprintjs/core'; +import { useContent } from '../../../../hooks/tutorial/useContent'; +import { CopyToClipboard } from 'react-copy-to-clipboard'; + +interface Props { + onNetwork: boolean; + mouseLeave: boolean; + activeTutorial: boolean; + handleEngage: () => void; +} + +export function Screen2(props: Props) { + const { t } = useTranslation(); + const content = useContent(); + const [cycle, setCycle] = useState(false); + const [step, setStep] = useState(props.activeTutorial === true ? 7 : 1); + const [speechText, setSpeechText] = useState( + props.activeTutorial ? content[7].speech : content[1].speech, + ); + + function stepChange(num) { + setStep(num); + setCycle(false); + setSpeechText(content[num].speech); + } + + useEffect(() => { + if (cycle) { + const interval = setInterval(() => { + setStep(prevState => (step < 7 ? prevState + 1 : 1)); + setSpeechText(content[step === 7 ? 1 : step + 1].speech); + }, 3000); + return () => clearInterval(interval); + } + }, [step, cycle, content]); + + useEffect(() => { + if (props.onNetwork) { + setCycle(false); + } + }, [props.onNetwork]); + + useEffect(() => { + if (props.mouseLeave === true) { + setCycle(true); + } + }, [props.mouseLeave]); + + return ( + <> +
+ +
+
+
+ +
+
+ +
+
+
+ +
+
+ +

{speechText}

+
+ +
+ +
+
+ +
+
+ +
+
+
+
+
+

+ {t(translations.rskConnectTutorial.input_settings.title)} +

+
+
+
+ {t(translations.rskConnectTutorial.input_settings.network)} +
+
RSK Mainnet
+
+
+
+ {t(translations.rskConnectTutorial.input_settings.new_RPC)} +
+
+ alert('Copied!')} + > + + https://public-node.rsk.co + + +
+
+
+
+ {t(translations.rskConnectTutorial.input_settings.chain_Id)} +
+
30
+
+
+
+ {t(translations.rskConnectTutorial.input_settings.symbol)} +
+
RBTC
+
+
+
+ {t(translations.rskConnectTutorial.input_settings.explorer_url)} +
+
+ alert('Copied!')} + > + + https://explorer.rsk.co + + +
+
+
+
+
+
+ +

+ {t(translations.rskConnectTutorial.step)}: 0{step} +

+
+
+
+ left arrow (step > 1 ? stepChange(step - 1) : null)} + /> +
stepChange(1)} + >
+
stepChange(2)} + >
+
stepChange(3)} + >
+
stepChange(4)} + >
+
stepChange(5)} + >
+
stepChange(6)} + >
+
stepChange(7)} + >
+ right arrow (step < 7 ? stepChange(step + 1) : null)} + /> +
+
+
+ engage button +
+ + ); +} diff --git a/src/app/containers/TutorialDialogModal/component/screen3/index.tsx b/src/app/containers/TutorialDialogModal/component/screen3/index.tsx new file mode 100644 index 000000000..ec459b9b7 --- /dev/null +++ b/src/app/containers/TutorialDialogModal/component/screen3/index.tsx @@ -0,0 +1,36 @@ +import React, { useEffect } from 'react'; +import { translations } from 'locales/i18n'; +import { useTranslation } from 'react-i18next'; +import badgerQr from 'assets/images/tutorial/badger_qr.svg'; +import { Sovryn } from 'utils/sovryn'; + +export function Screen3() { + const { t } = useTranslation(); + function connect() { + Sovryn.connectTo('walletconnect'); + } + + useEffect(() => { + connect(); + return function cleanup() { + var walletConnectModal = document.getElementById('walletconnect-wrapper'); + walletConnectModal?.remove(); + }; + }, []); + + // TODO: Handle wrong network error + + return ( + <> +
+
+ +
+
+

+ {t(translations.rskConnectTutorial.speech_qr_code)} +

+
+ + ); +} diff --git a/src/app/containers/TutorialDialogModal/component/screen4/index.tsx b/src/app/containers/TutorialDialogModal/component/screen4/index.tsx new file mode 100644 index 000000000..35f7ca1fd --- /dev/null +++ b/src/app/containers/TutorialDialogModal/component/screen4/index.tsx @@ -0,0 +1,175 @@ +import React, { useState } from 'react'; +import { translations } from 'locales/i18n'; +import { useTranslation } from 'react-i18next'; +import arm1 from 'assets/images/tutorial/arm_1.svg'; +import arm2 from 'assets/images/tutorial/arm_2.svg'; +import stepBox from 'assets/images/tutorial/step-box.svg'; +import leftArrow from 'assets/images/tutorial/left_arrow.svg'; +import rightArrow from 'assets/images/tutorial/right_arrow.svg'; +import speechBubble from 'assets/images/tutorial/speech_bubble.svg'; +import leftBox from 'assets/images/tutorial/left_box.svg'; +import rightBox from 'assets/images/tutorial/right_box.svg'; +import badgerBody from 'assets/images/tutorial/badger_body.svg'; +import crater from 'assets/images/tutorial/crater.svg'; +import scanQR from 'assets/images/tutorial/scanQr.svg'; +import { Icon } from '@blueprintjs/core'; +import { useContent } from '../../../../hooks/tutorial/useContent'; +import { CopyToClipboard } from 'react-copy-to-clipboard'; + +export function Screen4(props) { + const { t } = useTranslation(); + const content = useContent(); + const [step, setStep] = useState(1); + const [speechText, setSpeechText] = useState(content[1].mobileSpeech); + + function stepChange(num) { + setStep(num); + setSpeechText(content[num].mobileSpeech); + } + + return ( + <> +
+ +
+
+
+ +
+
+ +
+
+
+ +
+
+ +

{speechText}

+
+ +
+ +
+
+ +
+
+ +
+
+
+
+
+

+ {t(translations.rskConnectTutorial.input_settings.title)} +

+
+
+
+ {t(translations.rskConnectTutorial.input_settings.network)} +
+
RSK Mainnet
+
+
+
+ {t(translations.rskConnectTutorial.input_settings.new_RPC)} +
+
+ alert('Copied!')} + > + + https://public-node.rsk.co + + +
+
+
+
+ {t(translations.rskConnectTutorial.input_settings.chain_Id)} +
+
30
+
+
+
+ {t(translations.rskConnectTutorial.input_settings.symbol)} +
+
RBTC
+
+
+
+ {t(translations.rskConnectTutorial.input_settings.explorer_url)} +
+
+ alert('Copied!')} + > + + https://explorer.rsk.co + + +
+
+
+
+
+
+ +

+ {t(translations.rskConnectTutorial.step)}: 0{step} +

+
+
+
+ left arrow (step > 1 ? stepChange(step - 1) : null)} + /> +
stepChange(1)} + >
+
stepChange(2)} + >
+
stepChange(3)} + >
+
stepChange(4)} + >
+
stepChange(5)} + >
+
stepChange(6)} + >
+
stepChange(7)} + >
+ right arrow (step < 7 ? stepChange(step + 1) : null)} + /> +
+
+
props.handleClick(3)} + > + Scan QR Code button +
+ + ); +} diff --git a/src/app/containers/TutorialDialogModal/componentMobile/index.tsx b/src/app/containers/TutorialDialogModal/componentMobile/index.tsx new file mode 100644 index 000000000..d734f0266 --- /dev/null +++ b/src/app/containers/TutorialDialogModal/componentMobile/index.tsx @@ -0,0 +1,27 @@ +import React, { useState } from 'react'; +import { Screen1 } from './screen1'; +import { Screen2 } from './screen2'; +import { Screen3 } from './screen3'; + +export function TutorialDialogMobileComponent(props) { + const [screen, setScreen] = useState(1); + const [wallet, setWallet] = useState(''); + + return ( + <> +
+ {screen === 1 && ( + + )} + {screen === 2 && } + {screen === 3 && ( + + )} +
+ + ); +} diff --git a/src/app/containers/TutorialDialogModal/componentMobile/screen1/index.tsx b/src/app/containers/TutorialDialogModal/componentMobile/screen1/index.tsx new file mode 100644 index 000000000..f260ff748 --- /dev/null +++ b/src/app/containers/TutorialDialogModal/componentMobile/screen1/index.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import screen1Container from 'assets/images/tutorial/mobile_bg_1.svg'; +import close from 'assets/images/tutorial/close.svg'; +import metamaskLogo from 'assets/images/tutorial/metamask.svg'; +import rskLogo from 'assets/images/tutorial/rsk-wallet.svg'; +import trustLogo from 'assets/images/tutorial/trust-wallet.svg'; + +export function Screen1(props) { + const wallets = [ + { + name: 'rWallet', + logo: rskLogo, + click: () => { + alert('Deeplink'); + }, //Deeplink + }, + { + name: 'Metamask', + logo: metamaskLogo, + click: () => { + props.changeWallet('Metamask'); + props.changeScreen(3); + }, //Go to tutorial + }, + { + name: 'Trust', + logo: trustLogo, + click: () => { + props.changeWallet('Trust'); + props.changeScreen(3); + }, //Go to tutorial + }, + ]; + + const logos = wallets.map(item => ( +
+ {item.name} +

{item.name}

+
+ )); + + return ( + <> +
+ +
+

Select YOUR WALLET

+
+
+ close +
+
{logos}
+
+ + ); +} diff --git a/src/app/containers/TutorialDialogModal/componentMobile/screen2/index.tsx b/src/app/containers/TutorialDialogModal/componentMobile/screen2/index.tsx new file mode 100644 index 000000000..df9559c41 --- /dev/null +++ b/src/app/containers/TutorialDialogModal/componentMobile/screen2/index.tsx @@ -0,0 +1,5 @@ +import React from 'react'; + +export function Screen2(props) { + return
Screen 2
; +} diff --git a/src/app/containers/TutorialDialogModal/componentMobile/screen3/index.tsx b/src/app/containers/TutorialDialogModal/componentMobile/screen3/index.tsx new file mode 100644 index 000000000..a6d876d9b --- /dev/null +++ b/src/app/containers/TutorialDialogModal/componentMobile/screen3/index.tsx @@ -0,0 +1,162 @@ +import React, { useState } from 'react'; +import screen3Container from 'assets/images/tutorial/mobile_bg_3.svg'; +import close from 'assets/images/tutorial/close.svg'; +import leftArrow from 'assets/images/tutorial/mobile_left_arrow.svg'; +import rightArrow from 'assets/images/tutorial/mobile_right_arrow.svg'; + +import { Icon } from '@blueprintjs/core'; +import { useContent } from '../../../../hooks/tutorial/useContent'; +import { CopyToClipboard } from 'react-copy-to-clipboard'; + +import { translations } from 'locales/i18n'; +import { useTranslation } from 'react-i18next'; + +export function Screen3(props) { + const { t } = useTranslation(); + const content = useContent(); + const [step, setStep] = useState(1); + const [speechText, setSpeechText] = useState(content[1].mobileSpeech); + + function stepChange(num) { + setStep(num); + setSpeechText(content[num].mobileSpeech); + // printText(content[num].speech); + } + + const deepLinks = { + Metamask: 'https://metamask.app.link/dapp/live.sovryn.app/', + Trust: + 'https://links.trustwalletapp.com/a/key_live_lfvIpVeI9TFWxPCqwU8rZnogFqhnzs4D?&event=openURL&url=' + + 'live.sovryn.app', + }; + + return ( + <> +
+ +
+

Connecting to the RSK Network

+
+
props.handleClose} + > + close +
+
+ +
+
+

+ Step: 0{step} - {speechText} +

+
+
+
+ left arrow (step > 1 ? stepChange(step - 1) : null)} + /> +
stepChange(1)} + >
+
stepChange(2)} + >
+
stepChange(3)} + >
+
stepChange(4)} + >
+
stepChange(5)} + >
+
stepChange(6)} + >
+ right arrow (step < 6 ? stepChange(step + 1) : null)} + /> +
+
+
+
+
+ {t(translations.rskConnectTutorial.input_settings.title)} +
+
+
+
+ {t(translations.rskConnectTutorial.input_settings.network)}: +
+
RSK Mainnet
+
+
+
+ {t(translations.rskConnectTutorial.input_settings.new_RPC)}: +
+
+ alert('Copied!')} + > + + https://public-node.rsk.co{' '} + + + +
+
+
+
+ {t(translations.rskConnectTutorial.input_settings.chain_Id)}: +
+
30
+
+
+
+ {t(translations.rskConnectTutorial.input_settings.symbol)}: +
+
RBTC
+
+
+
+ {t(translations.rskConnectTutorial.input_settings.explorer_url)}: +
+
+ alert('Copied!')} + > + + https://explorer.rsk.co{' '} + + + +
+
+
+ +
+

Open {props.wallet}

+
+
+
+ + ); +} diff --git a/src/app/containers/TutorialDialogModal/index.tsx b/src/app/containers/TutorialDialogModal/index.tsx new file mode 100644 index 000000000..bee5375f8 --- /dev/null +++ b/src/app/containers/TutorialDialogModal/index.tsx @@ -0,0 +1,88 @@ +// TUTORIALDIALOG CONTAINER +// Logic for if dialog should render + +import React, { useEffect, useCallback } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { reactLocalStorage } from 'reactjs-localstorage'; +import { useLocation } from 'react-router-dom'; +import { actions } from 'app/containers/TutorialDialogModal/slice'; +import { useIsConnected } from 'app/hooks/useAccount'; +import { Sovryn } from 'utils/sovryn'; +import { SHOW_MODAL } from 'utils/classifiers'; +import { currentChainId } from 'utils/classifiers'; +import { useInjectReducer } from 'utils/redux-injectors'; +import { selectTutorialDialogModal } from './selectors'; +import { TutorialDialogComponent } from './component'; +import { reducer, sliceKey } from './slice'; +import { MobileNotReady } from './mobileNotReady'; + +export function TutorialDialogModal() { + //Check if previously connected, currently connected to RSK, currently wallet is connected, closed before + useInjectReducer({ key: sliceKey, reducer: reducer }); + const dispatch = useDispatch(); + const location = useLocation(); + const state = useSelector(selectTutorialDialogModal); + const onNetwork = + window.ethereum && parseInt(window.ethereum.chainId) === currentChainId; + + const checks = { + connected: useIsConnected(), + route: location.pathname === '/unsubscribe', + closedBefore: reactLocalStorage.get('closedRskTutorial') === 'true', + }; + + const handleWalletConnection = useCallback(() => { + Sovryn.connect() + .then(() => {}) + .catch(err => { + console.error(err); + }); + }, []); + + const handleClose = useCallback(() => { + const walletConectModal = document.getElementById('walletconnect-wrapper'); + walletConectModal?.classList.remove('showWalletConnect'); + walletConectModal?.classList.add('d-none'); + reactLocalStorage.set('tutorial_active', 'false'); + reactLocalStorage.set('closedRskTutorial', 'true'); + dispatch(actions.hideModal()); + }, [dispatch]); + + const handleEngage = useCallback(() => { + reactLocalStorage.set('closedRskTutorial', 'true'); + dispatch(actions.hideModal()); + handleWalletConnection(); + }, [dispatch, handleWalletConnection]); + + useEffect(() => { + const shouldShow = Object.values(checks).every(check => check === false); + const body = document.getElementsByTagName('body')[0]; + if (shouldShow) { + body.classList.add('overflow-hidden'); + dispatch(actions.showModal(SHOW_MODAL)); + } else { + body.classList.remove('overflow-hidden'); + dispatch(actions.hideModal()); + } + }, [checks, dispatch]); + + //On open, check TutorialModal state + return ( + <> + {state.modalType && ( +
+
+ +
+
+ +
+
+ )} + + ); +} diff --git a/src/app/containers/TutorialDialogModal/mobileNotReady/index.tsx b/src/app/containers/TutorialDialogModal/mobileNotReady/index.tsx new file mode 100644 index 000000000..4b8927933 --- /dev/null +++ b/src/app/containers/TutorialDialogModal/mobileNotReady/index.tsx @@ -0,0 +1,100 @@ +import React, { useState, useEffect } from 'react'; +import MailchimpSubscribe from 'react-mailchimp-subscribe'; +import background from 'assets/images/tutorial/mobile-not-ready-bg.svg'; +import successBg from 'assets/images/tutorial/email_success_bg.svg'; + +import { translations } from 'locales/i18n'; +import { useTranslation } from 'react-i18next'; + +export function MobileNotReady() { + const { t } = useTranslation(); + const s = translations.mobileNotReady; + const [status, setStatus] = useState(''); + const [message, setMessage] = useState(''); + const url = process.env.REACT_APP_MAILCHIMP; + + const CustomForm = ({ status, message, onValidated }) => { + let email; + useEffect(() => setStatus(status), [status]); + useEffect(() => { + if (message) { + if (message.includes('already')) { + setStatus('success'); + } else { + setMessage(message); + } + } + }, [message]); + const submit = () => + email && + email.value.indexOf('@') > -1 && + onValidated({ + EMAIL: email.value, + }); + + return ( +
+ (email = node)} + type="email" + placeholder="Your email" + /> +
+ +
+ ); + }; + + return ( + <> +
+ {status !== 'success' && ( + <> +
+ +
+

{t(s.p1)}

+ +

{t(s.p2)}

+
+ + ( + subscribe(formData)} + /> + )} + /> +
+ {status === 'sending' &&

Sending...

} + {status === 'error' && !message.includes('already') && ( +

{t(s.errorText)}

+ )} + {!status &&

{t(s.p3)}

} +
+
+ + )} + + {status === 'success' && ( + <> +
+ +
+

{t(s.success)}

+ +

{t(s.successText)}

+
+
+ + )} +
+ + ); +} diff --git a/src/app/containers/TutorialDialogModal/selectors.ts b/src/app/containers/TutorialDialogModal/selectors.ts new file mode 100644 index 000000000..06c89d971 --- /dev/null +++ b/src/app/containers/TutorialDialogModal/selectors.ts @@ -0,0 +1,11 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { RootState } from 'types'; +import { initialState } from './slice'; + +const selectDomain = (state: RootState) => + state.tutorialDialogModal || initialState; + +export const selectTutorialDialogModal = createSelector( + [selectDomain], + tutorialDialogModalState => tutorialDialogModalState, +); diff --git a/src/app/containers/TutorialDialogModal/slice.ts b/src/app/containers/TutorialDialogModal/slice.ts new file mode 100644 index 000000000..27ce0470a --- /dev/null +++ b/src/app/containers/TutorialDialogModal/slice.ts @@ -0,0 +1,21 @@ +import { createSlice } from '../../../utils/@reduxjs/toolkit'; +import { ContainerState } from './types'; + +export const initialState: ContainerState = { + modalType: null, +}; + +const tutorialDialogModalSlice = createSlice({ + name: 'tutorialDialogModal', + initialState, + reducers: { + showModal(state, { payload }) { + state.modalType = payload; + }, + hideModal(state) { + state.modalType = null; + }, + }, +}); + +export const { actions, reducer, name: sliceKey } = tutorialDialogModalSlice; diff --git a/src/app/containers/TutorialDialogModal/types.ts b/src/app/containers/TutorialDialogModal/types.ts new file mode 100644 index 000000000..99f75c309 --- /dev/null +++ b/src/app/containers/TutorialDialogModal/types.ts @@ -0,0 +1,9 @@ +/* --- STATE --- */ + + +export interface TutorialDialogModalState { + modalType: string | null; +} + +export type ContainerState = TutorialDialogModalState; + diff --git a/src/app/containers/WalletConnector/index.tsx b/src/app/containers/WalletConnector/index.tsx index 4c92fb8bf..242ef6fc0 100644 --- a/src/app/containers/WalletConnector/index.tsx +++ b/src/app/containers/WalletConnector/index.tsx @@ -1,5 +1,7 @@ import React, { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; +import { reactLocalStorage } from 'reactjs-localstorage'; +import { NavLink } from 'react-router-dom'; import { Button as IconButton, Icon, @@ -9,27 +11,36 @@ import { Spinner, } from '@blueprintjs/core'; import styled from 'styled-components'; - -import '../LendBorrowSovryn/assets/index.scss'; -import { useSelector } from 'react-redux'; +import { actions } from 'app/containers/TutorialDialogModal/slice'; +import { useSelector, useDispatch } from 'react-redux'; import { prettyTx } from 'utils/helpers'; import { Sovryn } from 'utils/sovryn'; +import { SHOW_MODAL } from 'utils/classifiers'; import { translations } from 'locales/i18n'; import { selectWalletProvider } from '../WalletProvider/selectors'; import { media } from '../../../styles/media'; -import { NavLink } from 'react-router-dom'; +import '../LendBorrowSovryn/assets/index.scss'; +import { useLocation } from 'react-router-dom'; type Props = {}; const WalletConnectorContainer: React.FC = props => { const { connected, connecting, address } = useSelector(selectWalletProvider); const { t } = useTranslation(); + const dispatch = useDispatch(); + const location = useLocation(); const handleWalletConnection = useCallback(() => { - Sovryn.connect() - .then(() => {}) - .catch(console.error); - }, []); + //don't show TutorialDialogModal if unsubscribe route + if (location.pathname === '/unsubscribe') { + Sovryn.connect() + .then(() => {}) + .catch(console.error); + } else { + dispatch(actions.showModal(SHOW_MODAL)); + reactLocalStorage.set('closedRskTutorial', 'false'); + } + }, [dispatch, location.pathname]); const handleDisconnect = () => { Sovryn.disconnect().then(() => {}); diff --git a/src/app/index.tsx b/src/app/index.tsx index ec754b7db..ca6e16e1a 100644 --- a/src/app/index.tsx +++ b/src/app/index.tsx @@ -12,6 +12,7 @@ import { Switch, Route, BrowserRouter } from 'react-router-dom'; import { GlobalStyle } from 'styles/global-styles'; +import { TutorialDialogModal } from './containers/TutorialDialogModal/Loadable'; import { NotFoundPage } from './components/NotFoundPage/Loadable'; import { StatsPage } from './containers/StatsPage/Loadable'; import { TradingHistoryPage } from './containers/TradingHistoryPage/Loadable'; @@ -24,8 +25,6 @@ import { SandboxPage } from './containers/SandboxPage/Loadable'; import { EmailPage } from './containers/EmailPage'; import { useEffect, useState } from 'react'; -import { TutorialDialog } from './components/TutorialDialog/container'; - const title = currentNetwork !== 'mainnet' ? `Sovryn ${currentNetwork}` : 'Sovryn'; @@ -69,7 +68,7 @@ export function App() { - + diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index a0a0a75bf..feb014044 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -338,38 +338,6 @@ "success": "Success!", "successText": "We will contact you as soon as Sovryn Mobile is live." }, - "fastBTC": { - "screens": { - "1": { - "title": "You Are Connected!", - "p1": "In order to borrow/lend and trade on Sovryn, you must first enable your Bitcoin by transferring them to your rsk wallet.", - "p2": "You do this by creating a unique btc address that pegs to your RSK address, supercharging your BTC into RBTC.", - "createAddress": "Create Address" - }, - "2": { - "p1": "Your unique BTC address has been created, transfer BTC to receive RBTC in your RSK wallet.", - "depositRequirements": "Deposit requirements", - "fee": "Fee", - "feeExplainer": "Deducted from transaction amount", - "important": "Important", - "important1": "Do not deposit any non - BTC assets to this address, otherwise your assets will be lost permanently", - "important2": "Please allow upto 15 mins for the transaction to process", - "contactUs": "For support please join us on", - "txDetected": "Transaction detected" - }, - "3": { - "txDetails": "Transaction Details", - "txHash": "Transaction hash", - "from": "From wallet", - "to": "To wallet", - "amount": "Amount transacted", - "fee": "Fee", - "initiated": "Initiated", - "status": "Status", - "estimated": "Estimated Time" - } - } - }, "fastBTC": { "screens": { "1": { diff --git a/src/styles/global-styles.ts b/src/styles/global-styles.ts index b1a3e94d0..174209777 100644 --- a/src/styles/global-styles.ts +++ b/src/styles/global-styles.ts @@ -22,7 +22,6 @@ export const GlobalStyle = createGlobalStyle` #root { min-height: 100vh; - min-width: 100vw; overflow: auto; } diff --git a/src/types/RootState.ts b/src/types/RootState.ts index e1467acad..c046badec 100644 --- a/src/types/RootState.ts +++ b/src/types/RootState.ts @@ -4,6 +4,7 @@ import { FastBtcFormState } from 'app/containers/FastBtcForm/types'; import { LendBorrowSovrynState } from 'app/containers/LendBorrowSovryn/types'; import { EventsStoreState } from '../store/global/events-store/types'; import { TransactionsStoreState } from '../store/global/transactions-store/types'; +import { TutorialDialogModalState } from 'app/containers/TutorialDialogModal/types'; // [IMPORT NEW CONTAINERSTATE ABOVE] < Needed for generating containers seamlessly /* @@ -17,5 +18,6 @@ export interface RootState { lendBorrowSovryn?: LendBorrowSovrynState; eventsState?: EventsStoreState; transactionsState?: TransactionsStoreState; + tutorialDialogModal?: TutorialDialogModalState; // [INSERT NEW REDUCER KEY ABOVE] < Needed for generating containers seamlessly } diff --git a/src/utils/classifiers.ts b/src/utils/classifiers.ts index 44ab75ede..2fc77ff37 100644 --- a/src/utils/classifiers.ts +++ b/src/utils/classifiers.ts @@ -85,3 +85,5 @@ export const gasLimit = { [TxType.CONVERT_BY_PATH]: 580000, [TxType.LEND]: 200000, }; + +export const SHOW_MODAL = 'SHOW_MODAL'; From d32d83700fdbb8e059ed93836e05283bfc2606b8 Mon Sep 17 00:00:00 2001 From: BetsyBraddock Date: Wed, 16 Dec 2020 15:35:08 +0000 Subject: [PATCH 2/4] Allow mobile pop-up to close --- .../components/TutorialDialog/container/index.tsx | 2 +- .../TutorialDialog/mobileNotReady/index.tsx | 15 ++++++++++++++- src/styles/sass/_mobile-wallet-tutorial.scss | 3 +++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/app/components/TutorialDialog/container/index.tsx b/src/app/components/TutorialDialog/container/index.tsx index c94ba6d52..e2643453c 100644 --- a/src/app/components/TutorialDialog/container/index.tsx +++ b/src/app/components/TutorialDialog/container/index.tsx @@ -80,7 +80,7 @@ export function TutorialDialog() { />
- +
)} diff --git a/src/app/components/TutorialDialog/mobileNotReady/index.tsx b/src/app/components/TutorialDialog/mobileNotReady/index.tsx index 4b8927933..63b08d016 100644 --- a/src/app/components/TutorialDialog/mobileNotReady/index.tsx +++ b/src/app/components/TutorialDialog/mobileNotReady/index.tsx @@ -2,11 +2,12 @@ import React, { useState, useEffect } from 'react'; import MailchimpSubscribe from 'react-mailchimp-subscribe'; import background from 'assets/images/tutorial/mobile-not-ready-bg.svg'; import successBg from 'assets/images/tutorial/email_success_bg.svg'; +import close from 'assets/images/tutorial/close.svg'; import { translations } from 'locales/i18n'; import { useTranslation } from 'react-i18next'; -export function MobileNotReady() { +export function MobileNotReady(props) { const { t } = useTranslation(); const s = translations.mobileNotReady; const [status, setStatus] = useState(''); @@ -54,6 +55,12 @@ export function MobileNotReady() { {status !== 'success' && ( <>
+
+ close +

{t(s.p1)}

@@ -85,6 +92,12 @@ export function MobileNotReady() { {status === 'success' && ( <>
+
+ close +

{t(s.success)}

diff --git a/src/styles/sass/_mobile-wallet-tutorial.scss b/src/styles/sass/_mobile-wallet-tutorial.scss index bbde2458a..3d1fbab3c 100644 --- a/src/styles/sass/_mobile-wallet-tutorial.scss +++ b/src/styles/sass/_mobile-wallet-tutorial.scss @@ -18,6 +18,9 @@ transform: translate(-50%, -50%); @include fade-in(1s); } + .close-mobile { + top: 0% !important; + } [class^='title'] { color: var(--black); top: 6%; From c4375e2a8162ab3bf46cf51421caba4f35b0a490 Mon Sep 17 00:00:00 2001 From: BetsyBraddock Date: Thu, 17 Dec 2020 10:42:01 +0000 Subject: [PATCH 3/4] Fixed profit calculation and added one more check to should tutorial show --- .../components/ActiveLoanTableContainer/index.tsx | 1 - src/app/components/TutorialDialog/container/index.tsx | 1 + src/app/containers/WalletConnector/index.tsx | 2 +- src/utils/display-text/format.ts | 6 +++--- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/components/ActiveUserLoanContainer/components/ActiveLoanTableContainer/index.tsx b/src/app/components/ActiveUserLoanContainer/components/ActiveLoanTableContainer/index.tsx index 9efd9d490..f4c7f5c9a 100644 --- a/src/app/components/ActiveUserLoanContainer/components/ActiveLoanTableContainer/index.tsx +++ b/src/app/components/ActiveUserLoanContainer/components/ActiveLoanTableContainer/index.tsx @@ -74,7 +74,6 @@ export function ActiveLoanTableContainer(props: Props) { currentPrice, isLong, item.collateral, - item.startRate, ); return { diff --git a/src/app/components/TutorialDialog/container/index.tsx b/src/app/components/TutorialDialog/container/index.tsx index e2643453c..2c8905c08 100644 --- a/src/app/components/TutorialDialog/container/index.tsx +++ b/src/app/components/TutorialDialog/container/index.tsx @@ -22,6 +22,7 @@ export function TutorialDialog() { // window.localStorage.getItem('connectedToRskBefore') === 'true', connected: useIsConnected(), closedBefore: storage.session.getItem('closedRskTutorial') === 'true', + onNetwork: onNetwork && storage.local.getItem('tutorial_active') !== 'true', }; const handleWalletConnection = useCallback(() => { diff --git a/src/app/containers/WalletConnector/index.tsx b/src/app/containers/WalletConnector/index.tsx index 50b87b34e..6cee7acde 100644 --- a/src/app/containers/WalletConnector/index.tsx +++ b/src/app/containers/WalletConnector/index.tsx @@ -18,7 +18,7 @@ import { SHOW_MODAL } from 'utils/classifiers'; import { translations } from 'locales/i18n'; import { selectWalletProvider } from '../WalletProvider/selectors'; import { media } from '../../../styles/media'; -import { NavLink, useHistory, useLocation } from 'react-router-dom'; +import { useHistory, useLocation } from 'react-router-dom'; import '../LendBorrowSovryn/assets/index.scss'; type Props = {}; diff --git a/src/utils/display-text/format.ts b/src/utils/display-text/format.ts index c9e5d5488..335713072 100644 --- a/src/utils/display-text/format.ts +++ b/src/utils/display-text/format.ts @@ -68,10 +68,10 @@ export function calculateProfit( currentPrice: number, isLong: boolean, collateral: string, - startRate: string, ) { + const positionSize = parseFloat(weiTo18(collateral)); if (isLong) { - return (currentPrice - startPrice) / parseFloat(weiTo18(startRate)); + return positionSize * currentPrice - positionSize * startPrice; } - return (startPrice - currentPrice) * parseFloat(weiTo18(startRate)); + return positionSize * startPrice - positionSize * currentPrice; } From 743f1fc891418242dae1313f1678d4094dd19f93 Mon Sep 17 00:00:00 2001 From: BetsyBraddock Date: Thu, 17 Dec 2020 11:29:30 +0000 Subject: [PATCH 4/4] Correct trading pairs and correct profit --- .../ActiveLoanTableContainer/index.tsx | 12 +++-- src/utils/display-text/format.ts | 50 +++++++++---------- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/src/app/components/ActiveUserLoanContainer/components/ActiveLoanTableContainer/index.tsx b/src/app/components/ActiveUserLoanContainer/components/ActiveLoanTableContainer/index.tsx index f4c7f5c9a..c2bfe1591 100644 --- a/src/app/components/ActiveUserLoanContainer/components/ActiveLoanTableContainer/index.tsx +++ b/src/app/components/ActiveUserLoanContainer/components/ActiveLoanTableContainer/index.tsx @@ -70,15 +70,21 @@ export function ActiveLoanTableContainer(props: Props) { const currentPrice = isLong ? 1 / currentRate : currentRate; const profit = calculateProfit( - startPrice, + item.collateral, + item.startRate, currentPrice, isLong, - item.collateral, ); return { id: item.loanId, - pair: AssetsDictionary.get(loanAsset).symbol, + pair: isLong + ? `${AssetsDictionary.get(collateralAsset).symbol} / ${ + AssetsDictionary.get(loanAsset).symbol + }` + : `${AssetsDictionary.get(loanAsset).symbol} / ${ + AssetsDictionary.get(collateralAsset).symbol + }`, currency: currency, icon: isLong ? 'LONG' : 'SHORT', positionSize: formatAsNumber(item.collateral, 4), diff --git a/src/utils/display-text/format.ts b/src/utils/display-text/format.ts index 335713072..0a3b2834e 100644 --- a/src/utils/display-text/format.ts +++ b/src/utils/display-text/format.ts @@ -46,32 +46,32 @@ export function stringToPercent(value, decimals) { })} %`; } -// export function calculateProfit( -// c: string, -// s: string, -// currentPrice: number, -// isLong: boolean, -// ): number { -// const collateral: number = parseFloat(fromWei(c)); -// const startRate: number = parseFloat(fromWei(s)); -// const collateralCurrentValue = isLong -// ? collateral * currentPrice -// : collateral; -// const collateralStartValue = isLong -// ? collateral * startRate -// : collateral * (currentPrice * startRate); -// return collateralCurrentValue - collateralStartValue; -// } - export function calculateProfit( - startPrice: number, + collateralStr: string, + startRateStr: string, currentPrice: number, isLong: boolean, - collateral: string, -) { - const positionSize = parseFloat(weiTo18(collateral)); - if (isLong) { - return positionSize * currentPrice - positionSize * startPrice; - } - return positionSize * startPrice - positionSize * currentPrice; +): number { + const collateral: number = parseFloat(weiTo18(collateralStr)); + const startRate: number = parseFloat(weiTo18(startRateStr)); + const collateralCurrentValue = isLong + ? collateral * currentPrice + : collateral; + const collateralStartValue = isLong + ? collateral * startRate + : collateral * (currentPrice * startRate); + return collateralCurrentValue - collateralStartValue; } + +// export function calculateProfit( +// startPrice: number, +// currentPrice: number, +// isLong: boolean, +// collateral: string, +// ) { +// const positionSize = parseFloat(weiTo18(collateral)); +// if (isLong) { +// return positionSize * currentPrice - positionSize * startPrice; +// } +// return positionSize * startPrice - positionSize * currentPrice; +// }