Skip to content
This repository has been archived by the owner on Jan 15, 2021. It is now read-only.

Commit

Permalink
Settings page (#1160)
Browse files Browse the repository at this point in the history
* give /setting basic style

* update hook-form devtools

* quick fix

* connect Settings page

* more settings logic

* better validation of formdata slice

* abstract away wc options logic

* tie wc options to connect/autoconnect/reconnect

* populate current  form data on mount

* add Reset button

* better prc option

* cleanup

* redirect to / on reconnect if needed

* allow to compose resolvers for different settings slices

* abstract away WCsettings

* Update src/components/Settings/WalletConnect.tsx

Co-authored-by: Leandro Boscariol <alfetopito@users.noreply.github.com>

* Update src/utils/walletconnectOptions.ts

Co-authored-by: Leandro Boscariol <alfetopito@users.noreply.github.com>

* more comments

* dynamic import Settings

* add a treeshakable component for @hookform/devtools

* improve styles

* Improve text

* useState -> useMemo

* fatter OR separator

* better size limit on mobile

* vertical align labels

Co-authored-by: Leandro Boscariol <alfetopito@users.noreply.github.com>
  • Loading branch information
Velenir and alfetopito authored Jul 1, 2020
1 parent ccc02c8 commit 88de89f
Show file tree
Hide file tree
Showing 13 changed files with 517 additions and 41 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"react": "^16.12.0",
"react-copy-to-clipboard": "^5.0.2",
"react-dom": "^16.12.0",
"react-hook-form": "^5.2.0",
"react-hook-form": "^5.7.2",
"react-hot-loader": "^4.12.19",
"react-router-dom": "^5.1.2",
"react-select": "^3.0.8",
Expand All @@ -70,6 +70,7 @@
"@babel/preset-react": "^7.9.4",
"@babel/register": "^7.8.3",
"@fortawesome/fontawesome-common-types": "^0.2.26",
"@hookform/devtools": "^1.2.1",
"@types/bn.js": "^4.11.6",
"@types/combine-reducers": "^1.0.0",
"@types/enzyme": "^3.10.4",
Expand Down Expand Up @@ -117,7 +118,6 @@
"preload-webpack-plugin": "^3.0.0-beta.4",
"prettier": "^1.19.1",
"react-dev-utils": "^10.0.0",
"react-hook-form-devtools": "^1.1.3",
"react-test-renderer": "^16.12.0",
"rimraf": "^3.0.1",
"style-loader": "^1.1.3",
Expand Down
7 changes: 7 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ const OrderBook = React.lazy(() =>
'pages/OrderBook'
),
)
const Settings = React.lazy(() =>
import(
/* webpackChunkName: "Settings_chunk"*/
'pages/Settings'
),
)

// Global State
import { withGlobalContext } from 'hooks/useGlobalState'
Expand All @@ -104,6 +110,7 @@ const App: React.FC = () => (
<Route path="/book" exact component={OrderBook} />
<Route path="/connect-wallet" exact component={ConnectWallet} />
<Route path="/trades" exact component={Trades} />
<Route path="/settings" exact component={Settings} />
<Redirect from="/" to="/trade/DAI-USDC?sell=0&price=0" />
<Route component={NotFound} />
</Switch>
Expand Down
1 change: 1 addition & 0 deletions src/HookFormDevtool.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { DevTool } from '@hookform/devtools'
27 changes: 17 additions & 10 deletions src/api/wallet/WalletApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import Web3Modal, { getProviderInfo, IProviderOptions, IProviderInfo, isMobile }
import Web3 from 'web3'
import { BlockHeader } from 'web3-eth'

import { logDebug, toBN, txDataEncoder } from 'utils'
import { INFURA_ID, WALLET_CONNECT_BRIDGE } from 'const'
import { logDebug, toBN, txDataEncoder, generateWCOptions } from 'utils'

import { subscribeToWeb3Event } from './subscriptionHelpers'
import { getMatchingScreenSize, subscribeToScreenSizeChange } from 'utils/mediaQueries'
Expand Down Expand Up @@ -44,6 +43,7 @@ export interface WalletApi {
isConnected(): Promise<boolean>
connect(givenProvider?: Provider): Promise<boolean>
disconnect(): Promise<void>
reconnectWC(): Promise<boolean>
getAddress(): Promise<string>
getBalance(): Promise<BN>
getNetworkId(): Promise<number>
Expand Down Expand Up @@ -221,13 +221,6 @@ const subscribeToBlockchainUpdate = async ({

type WalletConnectInits = IProviderOptions['walletconnect']

const wcOptions: Omit<WalletConnectInits, 'package'> = {
options: {
infuraId: INFURA_ID,
bridge: WALLET_CONNECT_BRIDGE,
},
}

// needed if Web3 was pre-instantiated with wss | WebsocketProvider
const closeOpenWebSocketConnection = (web3: Web3): void => {
if (
Expand Down Expand Up @@ -274,14 +267,28 @@ export class WalletApiImpl implements WalletApi {
return this._connected
}

public async reconnectWC(): Promise<boolean> {
// if connected to WC reconnect with new data
if (await this.isConnected()) {
if (isWalletConnectProvider(this._provider)) {
await this.disconnect()
return this.connect()
}
}

// if not don't do anything
return false
}

public async connect(givenProvider?: Provider): Promise<boolean> {
let provider: Provider

if (givenProvider) {
provider = givenProvider
} else {
const options = generateWCOptions()
const WCoptions: WalletConnectInits = {
...wcOptions,
options,
package: (
await import(
/* webpackChunkName: "@walletconnect"*/
Expand Down
5 changes: 5 additions & 0 deletions src/api/wallet/WalletApiMock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ export class WalletApiMock implements WalletApi {
await this._notifyListeners()
}

public async reconnectWC(): Promise<boolean> {
await this.disconnect()
return this.connect()
}

public async getAddress(): Promise<string> {
assert(this._connected, 'The wallet is not connected')

Expand Down
236 changes: 236 additions & 0 deletions src/components/Settings/WalletConnect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
import React from 'react'
import { FormContextValues, ErrorMessage, FieldErrors } from 'react-hook-form'
import styled from 'styled-components'
import { MEDIA } from 'const'

import Joi from '@hapi/joi'
import { Resolver, SettingsFormData } from 'pages/Settings'
import { WCOptions } from 'utils'

const URLSchema = Joi.string()
.empty('')
.optional()
.uri({ scheme: ['http', 'https'] })
const BridgeSchema = URLSchema.message('Bridge must be a valid URL')
const RPCSchema = URLSchema.message('RPC must be a valid URL')
const InfuraIdSchema = Joi.string()
.empty('')
.optional()
.length(32)
.message('Must be a valid id')

const WCSettingsSchema = Joi.object({
bridge: BridgeSchema,
infuraId: InfuraIdSchema,
rpc: Joi.object({
mainnet: RPCSchema,
rinkeby: RPCSchema,
}).empty({
mainnet: '',
rinkeby: '',
}),
})
.oxor('infuraId', 'rpc')
.messages({ 'object.oxor': 'InfuraId and RPC are mutually exclusive' })
// bridge is optional
// infuraId and rpc are optional and exclusive

// validates only walletconnect slice of form data
export const wcResolver: Resolver<SettingsFormData, 'walletconnect'> = (
data: WCOptions,
): {
values: WCOptions | null
errors: FieldErrors<WCOptions> | null
name: 'walletconnect'
} => {
const result = WCSettingsSchema.validate(data, {
abortEarly: false,
})

const { value: values, error } = result

return {
name: 'walletconnect',
values: error ? null : values,
errors: error
? error.details.reduce((previous, currentError) => {
// when exlusive fields are both present
if (currentError.path.length === 0 && currentError.type === 'object.oxor') {
return {
...previous,
infuraId: currentError,
rpc: currentError,
}
}
// any other error
return {
...previous,
[currentError.path[0]]: currentError,
}
}, {})
: null,
}
}

interface WCSettingsProps {
register: FormContextValues['register']
errors: FieldErrors<SettingsFormData>
}

const OuterFormSection = styled.div`
display: flex;
flex-direction: column;
font-size: 1.5em;
> div {
margin: 0.5em 0;
}
`

const AlternativesSection = styled.div`
display: grid;
grid-template-columns: 1fr auto 1fr;
@media ${MEDIA.mobile} {
grid-template-rows: 1fr auto 1fr;
grid-template-columns: 100%;
}
`

const OrSeparator = styled.div`
padding: 1.5em;
font-size: 0.8em;
display: flex;
justify-content: center;
align-items: center;
}
`

const ErrorWrapper = styled.p`
color: var(--color-error);
margin: 0;
padding: 0.5em;
font-size: 0.7em;
`

const InnerFormSection = styled.div`
padding: 0.5em;
padding-bottom: 1em;
box-shadow: 0 0 7px 0px grey;
border-radius: 0.5rem;
background: var(--color-background);
position: relative;
${ErrorWrapper} {
position: absolute;
bottom: 0;
}
`

const FormField = styled.label`
display: flex;
flex-direction: column;
> * {
padding-left: 0.65rem;
}
> span {
font-weight: bold;
}
> input {
width: auto;
font-size: 1em;
font-weight: normal;
::placeholder {
opacity: 0.2;
}
}
`

const Disclaimer = styled.div`
display: flex;
justify-content: center;
> p {
font-size: 1.4em;
max-width: 600px;
width: 100%;
padding: 0.5em;
border-radius: 0.8rem;
}
`

export const WCSettings: React.FC<WCSettingsProps> = ({ register, errors }) => {
return (
<div>
<Disclaimer>
<p>
Here you can set <strong>InfuraId</strong> or <strong>RPC URL</strong> that will be used for connecting
WalletConnect provider to mainnet and rinkeby. It is also possible to set WalletConnect{' '}
<strong>Bridge URL</strong> to use instead of the default one.
</p>
</Disclaimer>
<OuterFormSection>
<AlternativesSection>
<InnerFormSection>
<FormField>
<span>InfuraId</span>
<input type="text" name="walletconnect.infuraId" ref={register} />
</FormField>
<WCError errors={errors} name="infuraId" />
</InnerFormSection>
<OrSeparator>
<span>OR</span>
</OrSeparator>
<InnerFormSection>
<FormField>
<span>RPC URL</span>
<input
type="text"
name="walletconnect.rpc.mainnet"
ref={register}
placeholder="MAINNET: https://mainnet.node_url"
/>
<input
type="text"
name="walletconnect.rpc.rinkeby"
ref={register}
placeholder="RINKEBY: https://rinkeby.node_url"
/>
</FormField>
<WCError errors={errors} name="rpc" />
</InnerFormSection>
</AlternativesSection>

<InnerFormSection>
<FormField>
<span>Bridge URL</span>
<input
type="text"
name="walletconnect.bridge"
ref={register}
placeholder="https://bridge.walletconnect.org"
/>
</FormField>
<WCError errors={errors} name="bridge" />
</InnerFormSection>
</OuterFormSection>
</div>
)
}

interface WCErrorsProps {
errors: FieldErrors<SettingsFormData>
name: string
}

const WCError: React.FC<WCErrorsProps> = ({ errors, name }) => {
return (
<ErrorWrapper>
<ErrorMessage errors={errors} name={'walletconnect.' + name} />
</ErrorWrapper>
)
}
5 changes: 3 additions & 2 deletions src/components/TradeWidget/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ import validationSchema from './validationSchema'
import { useBetterAddTokenModal } from 'hooks/useBetterAddTokenModal'
import { encodeTokenSymbol, decodeSymbol } from '@gnosis.pm/dex-js'

import { DevTool } from 'HookFormDevtool'

const WrappedWidget = styled(Widget)`
overflow-x: visible;
min-width: 0;
Expand Down Expand Up @@ -1008,8 +1010,7 @@ const TradeWidget: React.FC = () => {
</div>
</OrdersPanel>
{/* React Forms DevTool debugger */}
{process.env.NODE_ENV === 'development' &&
React.createElement(require('react-hook-form-devtools').DevTool, { control: methods.control })}
{process.env.NODE_ENV === 'development' && <DevTool control={methods.control} />}
</WrappedWidget>
)
}
Expand Down
1 change: 1 addition & 0 deletions src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ if (process.env.INFURA_ID) {

export const INFURA_ID = infuraId
export const WALLET_CONNECT_BRIDGE = process.env.WALLET_CONNECT_BRIDGE || CONFIG.walletConnect.bridge
export const STORAGE_KEY_CUSTOM_WC_OPTIONS = 'CustomWCOptions'

let ethNodeUrl
if (process.env.ETH_NODE_URL) {
Expand Down
Loading

0 comments on commit 88de89f

Please sign in to comment.