Skip to content

Commit fd162a7

Browse files
callilianlapham
andauthored
feat(expert mode): Add expert mode (#828)
* Add expert mode scaffolding * move advanced settings to settings tab, add expert mode * update settings modal * update font weight * fix text * update with modal ; * add null checks * update with input checking * merge and add fixes * update language and bg Co-authored-by: ianlapham <ianlapham@gmail.com>
1 parent e209367 commit fd162a7

File tree

22 files changed

+599
-213
lines changed

22 files changed

+599
-213
lines changed

src/components/Button/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,9 @@ const ButtonErrorStyle = styled(Base)`
244244
&:disabled {
245245
opacity: 50%;
246246
cursor: auto;
247+
box-shadow: none;
248+
background-color: ${({ theme }) => theme.red1};
249+
border: 1px solid ${({ theme }) => theme.red1};
247250
}
248251
`
249252

src/components/Header/index.tsx

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { useTokenBalanceTreatingWETHasETH } from '../../state/wallet/hooks'
1717
import { ExternalLink, StyledInternalLink } from '../../theme'
1818
import { YellowCard } from '../Card'
1919
import { AutoColumn } from '../Column'
20+
import Settings from '../Settings'
2021
import Menu from '../Menu'
2122

2223
import Row, { RowBetween } from '../Row'
@@ -31,15 +32,12 @@ const HeaderFrame = styled.div`
3132
width: 100%;
3233
top: 0;
3334
position: absolute;
34-
35-
pointer-events: none;
36-
35+
z-index: 2;
3736
${({ theme }) => theme.mediaWidth.upToExtraSmall`
3837
padding: 12px 0 0 0;
3938
width: calc(100%);
4039
position: relative;
4140
`};
42-
z-index: 2;
4341
`
4442

4543
const HeaderElement = styled.div`
@@ -130,6 +128,12 @@ const NETWORK_LABELS: { [chainId in ChainId]: string | null } = {
130128
[ChainId.KOVAN]: 'Kovan'
131129
}
132130

131+
const BalanceWrapper = styled.div`
132+
${({ theme }) => theme.mediaWidth.upToExtraSmall`
133+
display: none;
134+
`};
135+
`
136+
133137
export default function Header() {
134138
const { account, chainId } = useActiveWeb3React()
135139

@@ -174,16 +178,17 @@ export default function Header() {
174178
{!isMobile && NETWORK_LABELS[chainId] && <NetworkCard>{NETWORK_LABELS[chainId]}</NetworkCard>}
175179
</TestnetWrapper>
176180
<AccountElement active={!!account} style={{ pointerEvents: 'auto' }}>
177-
{account && userEthBalance ? (
178-
<Text style={{ flexShrink: 0 }} pl="0.75rem" pr="0.5rem" fontWeight={500}>
179-
{userEthBalance?.toSignificant(4)} ETH
180-
</Text>
181-
) : null}
181+
<BalanceWrapper>
182+
{account && userEthBalance ? (
183+
<Text style={{ flexShrink: 0 }} pl="0.75rem" pr="0.5rem" fontWeight={500}>
184+
{userEthBalance?.toSignificant(4)} ETH
185+
</Text>
186+
) : null}
187+
</BalanceWrapper>
182188
<Web3Status />
183189
</AccountElement>
184-
<div style={{ pointerEvents: 'auto' }}>
185-
<Menu />
186-
</div>
190+
<Settings />
191+
<Menu />
187192
</HeaderElement>
188193
</RowBetween>
189194
</HeaderFrame>

src/components/Settings/index.tsx

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
import React, { useRef, useEffect, useContext, useState } from 'react'
2+
import { Settings, X } from 'react-feather'
3+
import styled from 'styled-components'
4+
5+
import {
6+
useUserSlippageTolerance,
7+
useExpertModeManager,
8+
useUserDeadline,
9+
useDarkModeManager
10+
} from '../../state/user/hooks'
11+
import SlippageTabs from '../SlippageTabs'
12+
import { RowFixed, RowBetween } from '../Row'
13+
import { TYPE } from '../../theme'
14+
import QuestionHelper from '../QuestionHelper'
15+
import Toggle from '../Toggle'
16+
import { ThemeContext } from 'styled-components'
17+
import { AutoColumn } from '../Column'
18+
import { ButtonError } from '../Button'
19+
import { useSettingsMenuOpen, useToggleSettingsMenu } from '../../state/application/hooks'
20+
import { Text } from 'rebass'
21+
import Modal from '../Modal'
22+
23+
const StyledMenuIcon = styled(Settings)`
24+
height: 20px;
25+
width: 20px;
26+
27+
> * {
28+
stroke: ${({ theme }) => theme.text1};
29+
}
30+
`
31+
32+
const StyledCloseIcon = styled(X)`
33+
height: 20px;
34+
width: 20px;
35+
:hover {
36+
cursor: pointer;
37+
}
38+
39+
> * {
40+
stroke: ${({ theme }) => theme.text1};
41+
}
42+
`
43+
44+
const StyledMenuButton = styled.button`
45+
position: relative;
46+
width: 100%;
47+
height: 100%;
48+
border: none;
49+
background-color: transparent;
50+
margin: 0;
51+
padding: 0;
52+
height: 35px;
53+
background-color: ${({ theme }) => theme.bg3};
54+
55+
padding: 0.15rem 0.5rem;
56+
border-radius: 0.5rem;
57+
58+
:hover,
59+
:focus {
60+
cursor: pointer;
61+
outline: none;
62+
background-color: ${({ theme }) => theme.bg4};
63+
}
64+
65+
svg {
66+
margin-top: 2px;
67+
}
68+
`
69+
const EmojiWrapper = styled.div`
70+
position: absolute;
71+
bottom: -6px;
72+
right: 0px;
73+
font-size: 14px;
74+
`
75+
76+
const StyledMenu = styled.div`
77+
margin-left: 0.5rem;
78+
display: flex;
79+
justify-content: center;
80+
align-items: center;
81+
position: relative;
82+
border: none;
83+
text-align: left;
84+
`
85+
86+
const MenuFlyout = styled.span`
87+
min-width: 20.125rem;
88+
background-color: ${({ theme }) => theme.bg1};
89+
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
90+
0px 24px 32px rgba(0, 0, 0, 0.01);
91+
border-radius: 0.5rem;
92+
display: flex;
93+
flex-direction: column;
94+
font-size: 1rem;
95+
position: absolute;
96+
top: 3rem;
97+
right: 0rem;
98+
z-index: 100;
99+
100+
${({ theme }) => theme.mediaWidth.upToExtraSmall`
101+
min-width: 18.125rem;
102+
right: -46px;
103+
`};
104+
`
105+
106+
const Break = styled.div`
107+
width: 100%;
108+
height: 1px;
109+
background-color: ${({ theme }) => theme.bg3};
110+
`
111+
112+
const ModalContentWrapper = styled.div`
113+
display: flex;
114+
align-items: center;
115+
justify-content: center;
116+
padding: 2rem 0;
117+
background-color: ${({ theme }) => theme.bg2};
118+
border-radius: 20px;
119+
`
120+
121+
export default function SettingsTab() {
122+
const node = useRef<HTMLDivElement>()
123+
const open = useSettingsMenuOpen()
124+
const toggle = useToggleSettingsMenu()
125+
126+
const theme = useContext(ThemeContext)
127+
const [userSlippageTolerance, setUserslippageTolerance] = useUserSlippageTolerance()
128+
129+
const [deadline, setDeadline] = useUserDeadline()
130+
131+
const [expertMode, toggleExpertMode] = useExpertModeManager()
132+
133+
const [darkMode, toggleDarkMode] = useDarkModeManager()
134+
135+
// show confirmation view before turning on
136+
const [showConfirmation, setShowConfirmation] = useState(false)
137+
138+
useEffect(() => {
139+
const handleClickOutside = e => {
140+
if (node.current?.contains(e.target) ?? false) {
141+
return
142+
}
143+
toggle()
144+
}
145+
146+
if (open) {
147+
document.addEventListener('mousedown', handleClickOutside)
148+
} else {
149+
document.removeEventListener('mousedown', handleClickOutside)
150+
}
151+
152+
return () => {
153+
document.removeEventListener('mousedown', handleClickOutside)
154+
}
155+
}, [open, toggle])
156+
157+
return (
158+
<StyledMenu ref={node}>
159+
<Modal isOpen={showConfirmation} onDismiss={() => setShowConfirmation(false)}>
160+
<ModalContentWrapper>
161+
<AutoColumn gap="lg">
162+
<RowBetween style={{ padding: '0 2rem' }}>
163+
<div />
164+
<Text fontWeight={500} fontSize={20}>
165+
Are you sure?
166+
</Text>
167+
<StyledCloseIcon onClick={() => setShowConfirmation(false)} />
168+
</RowBetween>
169+
<Break />
170+
<AutoColumn gap="lg" style={{ padding: '0 2rem' }}>
171+
<Text fontWeight={500} fontSize={20}>
172+
Expert mode turns off the confirm transaction prompt and allows high slippage trades that often result
173+
in bad rates and lost funds.
174+
</Text>
175+
<Text fontWeight={600} fontSize={20}>
176+
ONLY USE THIS MODE IF YOU KNOW WHAT YOU ARE DOING.
177+
</Text>
178+
<ButtonError
179+
error={true}
180+
padding={'12px'}
181+
onClick={() => {
182+
if (window.prompt(`Please type the word "confirm" to enable expert mode.`) === 'confirm') {
183+
toggleExpertMode()
184+
setShowConfirmation(false)
185+
}
186+
}}
187+
>
188+
<Text fontSize={20} fontWeight={500}>
189+
Turn On Expert Mode
190+
</Text>
191+
</ButtonError>
192+
</AutoColumn>
193+
</AutoColumn>
194+
</ModalContentWrapper>
195+
</Modal>
196+
<StyledMenuButton onClick={toggle}>
197+
<StyledMenuIcon />
198+
{expertMode && (
199+
<EmojiWrapper>
200+
<span role="img" aria-label="wizard-icon">
201+
🧙
202+
</span>
203+
</EmojiWrapper>
204+
)}
205+
</StyledMenuButton>
206+
{open && (
207+
<MenuFlyout>
208+
<AutoColumn gap="md" style={{ padding: '1rem' }}>
209+
<Text fontWeight={600} fontSize={14}>
210+
Transaction Settings
211+
</Text>
212+
<SlippageTabs
213+
rawSlippage={userSlippageTolerance}
214+
setRawSlippage={setUserslippageTolerance}
215+
deadline={deadline}
216+
setDeadline={setDeadline}
217+
/>
218+
<Text fontWeight={600} fontSize={14}>
219+
Interface Settings
220+
</Text>
221+
<RowBetween>
222+
<RowFixed>
223+
<TYPE.black fontWeight={400} fontSize={14} color={theme.text2}>
224+
Toggle Expert Mode
225+
</TYPE.black>
226+
<QuestionHelper text="Bypasses confirmation modals and allows high slippage trades. Use at your own risk." />
227+
</RowFixed>
228+
<Toggle
229+
isActive={expertMode}
230+
toggle={
231+
expertMode
232+
? () => {
233+
toggleExpertMode()
234+
setShowConfirmation(false)
235+
}
236+
: () => setShowConfirmation(true)
237+
}
238+
/>
239+
</RowBetween>
240+
<RowBetween>
241+
<RowFixed>
242+
<TYPE.black fontWeight={400} fontSize={14} color={theme.text2}>
243+
Toggle Dark Mode
244+
</TYPE.black>
245+
</RowFixed>
246+
<Toggle isActive={darkMode} toggle={toggleDarkMode} />
247+
</RowBetween>
248+
</AutoColumn>
249+
</MenuFlyout>
250+
)}
251+
</StyledMenu>
252+
)
253+
}

src/components/SlippageTabs/index.tsx

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,6 @@ const OptionCustom = styled(FancyButton)<{ active?: boolean; warning?: boolean }
7878
}
7979
`
8080

81-
const SlippageSelector = styled.div`
82-
padding: 0 20px;
83-
`
84-
8581
export interface SlippageTabsProps {
8682
rawSlippage: number
8783
setRawSlippage: (rawSlippage: number) => void
@@ -146,15 +142,14 @@ export default function SlippageTabs({ rawSlippage, setRawSlippage, deadline, se
146142
}
147143

148144
return (
149-
<>
150-
<RowFixed padding={'0 20px'}>
151-
<TYPE.black fontWeight={400} fontSize={14} color={theme.text2}>
152-
Set slippage tolerance
153-
</TYPE.black>
154-
<QuestionHelper text="Your transaction will revert if the price changes unfavorably by more than this percentage." />
155-
</RowFixed>
156-
157-
<SlippageSelector>
145+
<AutoColumn gap="md">
146+
<AutoColumn gap="sm">
147+
<RowFixed>
148+
<TYPE.black fontWeight={400} fontSize={14} color={theme.text2}>
149+
Slippage tolerance
150+
</TYPE.black>
151+
<QuestionHelper text="Your transaction will revert if the price changes unfavorably by more than this percentage." />
152+
</RowFixed>
158153
<RowBetween>
159154
<Option
160155
onClick={() => {
@@ -220,16 +215,16 @@ export default function SlippageTabs({ rawSlippage, setRawSlippage, deadline, se
220215
: 'Your transaction may be frontrun'}
221216
</RowBetween>
222217
)}
223-
</SlippageSelector>
218+
</AutoColumn>
224219

225220
<AutoColumn gap="sm">
226-
<RowFixed padding={'0 20px'}>
221+
<RowFixed>
227222
<TYPE.black fontSize={14} fontWeight={400} color={theme.text2}>
228-
Deadline
223+
Transaction deadline
229224
</TYPE.black>
230225
<QuestionHelper text="Your transaction will revert if it is pending for more than this long." />
231226
</RowFixed>
232-
<RowFixed padding={'0 20px'}>
227+
<RowFixed>
233228
<OptionCustom style={{ width: '80px' }} tabIndex={-1}>
234229
<Input
235230
color={!!deadlineError ? 'red' : undefined}
@@ -246,6 +241,6 @@ export default function SlippageTabs({ rawSlippage, setRawSlippage, deadline, se
246241
</TYPE.body>
247242
</RowFixed>
248243
</AutoColumn>
249-
</>
244+
</AutoColumn>
250245
)
251246
}

0 commit comments

Comments
 (0)