Skip to content

Commit 0a3a485

Browse files
authored
v1.1.0 (#12)
1 parent 56485b5 commit 0a3a485

23 files changed

+6097
-5164
lines changed

.vscode/settings.json

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
{
22
"typescript.tsdk": "node_modules/typescript/lib",
3-
"cSpell.words": [
4-
"chainlist"
5-
]
6-
}
3+
"cSpell.words": ["chainlist", "wagmi"]
4+
}

README.md

+3-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# EVM Box
22

33
<p align="center">
4-
<img src="https://user-images.githubusercontent.com/10740043/115120259-2f003c00-9fdf-11eb-9cc2-0f9991aa4873.png" width="400" />
4+
<img src="https://user-images.githubusercontent.com/10740043/167296346-78a0e82c-468f-425f-b637-e728fad820f4.png" />
55
</p>
66

77
EVM 网络切换工具 | EVM Box is a list of EVM networks. Helping users connect to EVM powered networks.
@@ -10,13 +10,9 @@ Add & Switch ETH Network as [EIP-3085](https://eips.ethereum.org/EIPS/eip-3085)
1010

1111
Search EVM compatible or L2 networks by name, symbol or chainId, networks info maintain by this [open source project](https://github.com/ethereum-lists/chains)
1212

13-
## Related EIP
13+
## EIP Spec
1414

1515
- [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193)
1616
- [EIP-2696](https://eips.ethereum.org/EIPS/eip-2696)
1717
- [EIP-3085](https://eips.ethereum.org/EIPS/eip-3085)
18-
- [EIP-3326](https://ethereum-magicians.org/t/eip-3326-wallet-switchethereumchain/5471) draft in progress.
19-
20-
## Known Issue
21-
22-
[EIP-3085](https://eips.ethereum.org/EIPS/eip-3085) defined `add` method not support mainnet for security reason,so there is no programming methods to switch to mainnet. I'll continue to focus this [EIP-3326](https://ethereum-magicians.org/t/eip-3326-wallet-switchethereumchain/5471) draft, which will resolve this problem.
18+
- [EIP-3326](https://ethereum-magicians.org/t/eip-3326-wallet-switchethereumchain/5471)

common/components/BackToTop.tsx

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { Button } from '@geist-ui/react'
2+
import { ArrowUp } from '@geist-ui/react-icons'
3+
import debounce from 'lodash/debounce'
4+
import { useCallback, useLayoutEffect, useState } from 'react'
5+
6+
const BackToTop = () => {
7+
const [show, setShow] = useState(false)
8+
const update = debounce(val => {
9+
setShow(val)
10+
}, 300)
11+
const handleScroll = useCallback(() => {
12+
let top = 0
13+
try {
14+
top = document.scrollingElement?.scrollTop || 0
15+
} catch (error) {
16+
console.error(error)
17+
}
18+
update(top > 200)
19+
}, [])
20+
21+
const backToTop = () => {
22+
try {
23+
document.scrollingElement?.scrollTo({ top: 0, behavior: 'smooth' })
24+
} catch (error) {
25+
console.error(error)
26+
}
27+
}
28+
29+
useLayoutEffect(() => {
30+
window.addEventListener('scroll', handleScroll)
31+
32+
return () => {
33+
window.removeEventListener('scroll', handleScroll)
34+
}
35+
}, [handleScroll])
36+
37+
return <>
38+
<div className={`to-top ${show ? 'show' : 'hide'}`}>
39+
<Button className="to-top-btn" type="secondary" icon={<ArrowUp />} auto onClick={backToTop}>Top</Button>
40+
</div>
41+
<style jsx>{`
42+
.to-top {
43+
position: fixed;
44+
bottom: 20px;
45+
right: 20px;
46+
background: #fff;
47+
z-index: 1;
48+
opacity: 0;
49+
transition: opacity 0.3s;
50+
}
51+
.to-top.show {
52+
opacity: 1;
53+
}
54+
:global(.to-top-btn) {
55+
border-radius: 20px !important;
56+
}
57+
`}</style>
58+
</>
59+
}
60+
61+
export default BackToTop

common/components/ChainItem.tsx

+48-27
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Fieldset, Tag, Row, Col, Button } from '@geist-ui/react'
1+
import { Fieldset, Grid, Button } from '@geist-ui/react'
22
import classnames from 'classnames'
33
import { useChain } from '../hooks/useChain'
44
import { useDApp } from '../hooks/useDApp'
@@ -10,10 +10,8 @@ interface IChainItemProps {
1010

1111
export const ChainItem: React.FC<IChainItemProps> = ({ chain }) => {
1212
const enable = useDApp()
13-
const [currentChainId, addEthChain] = useChain()
13+
const [currentChainId, switchEthChain] = useChain()
1414
const t = useLocale()
15-
const networkLabel =
16-
chain.faucets && chain.faucets.length ? 'Testnet' : 'Mainnet'
1715

1816
return (
1917
<>
@@ -24,48 +22,57 @@ export const ChainItem: React.FC<IChainItemProps> = ({ chain }) => {
2422
>
2523
<Fieldset.Title className="chain-title">
2624
{/* {chain.nativeCurrency?.symbol ?? ''} */}
27-
{chain.chain}
28-
<Tag type="lite" className="chain-tag">
29-
{networkLabel}
30-
</Tag>
25+
{chain.name}
3126
</Fieldset.Title>
3227
<Fieldset.Subtitle>
33-
<Row>
34-
<Col>
35-
<p style={{ whiteSpace: 'nowrap' }}>{chain.name}</p>
36-
</Col>
28+
<Grid.Container justify="space-between">
29+
<Grid>
30+
<p style={{ whiteSpace: 'nowrap' }}>{chain.chain}</p>
31+
</Grid>
3732

38-
<Col>
33+
<Grid>
3934
<p style={{ textAlign: 'right' }}>chainId: {chain.chainId}</p>
40-
</Col>
41-
</Row>
35+
</Grid>
36+
</Grid.Container>
4237
</Fieldset.Subtitle>
4338
<Fieldset.Footer>
44-
<Fieldset.Footer.Status>
45-
<a href={chain.infoURL} target="_blank" rel="noopener noreferrer">
46-
{t('OfficialSite')}
47-
</a>
48-
</Fieldset.Footer.Status>
49-
<Fieldset.Footer.Actions>
39+
<div className="status">
40+
<Grid.Container gap={2}>
41+
<Grid>
42+
<a href={chain.infoURL} target="_blank" rel="noopener noreferrer">
43+
{t('OfficialSite')}
44+
</a>
45+
</Grid>
46+
{
47+
chain.faucets.slice(0, 2).map((faucet) => <Grid key={faucet}><a href={faucet} target="_blank" rel="noopener noreferrer"> {t('Faucet')} </a></Grid>)
48+
}
49+
</Grid.Container>
50+
</div>
51+
<div className="actions">
5052
{currentChainId === chain.chainId
51-
? 'current network'
53+
? t('CurrentNetwork')
5254
: enable && (
5355
<Button
5456
type="secondary"
5557
ghost
56-
size="mini"
57-
onClick={() => addEthChain(chain)}
58+
scale={0.35}
59+
onClick={() => switchEthChain(chain)}
5860
>
59-
{t('Add')}
61+
{t('Switch')}
6062
</Button>
6163
)}
62-
</Fieldset.Footer.Actions>
64+
</div>
6365
</Fieldset.Footer>
6466
</Fieldset>
6567
<style jsx>
6668
{`
6769
:global(.chain) {
68-
width: 100%;
70+
display: block;
71+
width: 100% !important;
72+
}
73+
74+
:global(.chain:hover) {
75+
box-shadow: 0 5px 10px rgb(0 0 0 / 12%);
6976
}
7077
7178
:global(.current .content) {
@@ -77,6 +84,20 @@ export const ChainItem: React.FC<IChainItemProps> = ({ chain }) => {
7784
:global(.chain-tag) {
7885
margin-left: auto;
7986
}
87+
.status {
88+
font-size: 0.875rem;
89+
line-height: 1.2;
90+
margin: 0;
91+
display: inline-flex;
92+
word-break: break-word;
93+
}
94+
.status > :global(p) {
95+
margin: 0;
96+
}
97+
.actions {
98+
display: flex;
99+
justify-content: flex-end;
100+
}
80101
`}
81102
</style>
82103
</>

common/components/Header.tsx

+176
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import { Button, ButtonDropdown, Link, Loading, Spacer, useTheme, useToasts } from '@geist-ui/react'
2+
import { Moon, Sun } from '@geist-ui/react-icons'
3+
import NextLink from 'next/link'
4+
import { useEffect } from 'react'
5+
import { useAccount, useConnect, useDisconnect, useEnsName } from 'wagmi'
6+
7+
import { useLocale } from '../hooks/useLocale'
8+
import { useThemeSwitch } from '../hooks/useThemeContext'
9+
import { addColorAlpha, truncateAddress } from '../utils'
10+
11+
const Profile: React.FC = () => {
12+
const { data: account } = useAccount()
13+
const t = useLocale()
14+
const { data: ensName } = useEnsName({ address: account?.address })
15+
const { connect, connectors, error, isConnecting, pendingConnector } =
16+
useConnect()
17+
const { disconnect } = useDisconnect()
18+
const [, setToast] = useToasts()
19+
const wc = connectors?.find(connector => connector.name === 'WalletConnect')
20+
21+
useEffect(() => {
22+
if (error) {
23+
setToast({ type: 'error', text: error.message })
24+
}
25+
}, [error])
26+
27+
if (account) {
28+
return (
29+
<ButtonDropdown type="secondary" scale={0.5}>
30+
<ButtonDropdown.Item main>{ensName || truncateAddress(account.address)}</ButtonDropdown.Item>
31+
<ButtonDropdown.Item onClick={() => disconnect()} type="secondary">{t('disconnect')}</ButtonDropdown.Item>
32+
</ButtonDropdown>
33+
)
34+
}
35+
36+
if (!wc) return null
37+
38+
return (
39+
<Button
40+
auto
41+
disabled={!wc.ready}
42+
type="secondary-light"
43+
onClick={() => connect(wc)}
44+
scale={0.5}
45+
>
46+
{wc.name}
47+
{!wc.ready && ' (unsupported)'}
48+
{isConnecting &&
49+
wc.id === pendingConnector?.id &&
50+
<Loading />}
51+
</Button>
52+
)
53+
}
54+
55+
const Header: React.FC = () => {
56+
const t = useLocale()
57+
const theme = useTheme()
58+
const { switchTheme, themeType } = useThemeSwitch()
59+
return (
60+
<header>
61+
<div className="header">
62+
<NextLink href="/">
63+
<div className="logo">
64+
<img src="/images/logo.svg" />
65+
<span>{t('AppName')}</span>
66+
</div>
67+
</NextLink>
68+
<nav>
69+
<Link
70+
href="https://faucet.paradigm.xyz/"
71+
target="_blank"
72+
rel="noopener noreferrer"
73+
color={false}
74+
block
75+
>
76+
Faucet
77+
</Link>
78+
<Spacer w={2}/>
79+
<Link
80+
href="https://github.com/izayl/evm-box"
81+
target="_blank"
82+
rel="noopener noreferrer"
83+
color={false}
84+
block
85+
>
86+
GitHub
87+
</Link>
88+
<Spacer w={2}/>
89+
<Link block href="#">
90+
{themeType === 'dark'
91+
? <Moon
92+
size={18}
93+
onClick={() => switchTheme('light')}
94+
color={theme.palette.foreground}
95+
/>
96+
: <Sun
97+
size={18}
98+
onClick={() => switchTheme('dark')}
99+
color={theme.palette.foreground}
100+
/>
101+
}
102+
</Link>
103+
<Spacer w={2} />
104+
<Profile />
105+
</nav>
106+
</div>
107+
<style jsx>{`
108+
header {
109+
backdrop-filter: saturate(180%) blur(5px);
110+
background-color: ${addColorAlpha(theme.palette.background, 0.8)};
111+
box-shadow: ${theme.type === 'dark' ? '0 0 0 1px #333' : '0 0 15px 0 rgba(0, 0, 0, 0.1)'};
112+
width: 100%;
113+
height: 64px;
114+
display: flex;
115+
align-items: center;
116+
justify-content: center;
117+
position: fixed;
118+
top: 0;
119+
left: 0;
120+
right: 0;
121+
z-index: 12;
122+
}
123+
.header {
124+
max-width: 1000px;
125+
width: 100%;
126+
padding: 0 16pt;
127+
margin: 0 auto;
128+
display: flex;
129+
align-items: center;
130+
justify-content: space-between;
131+
box-sizing: border-box;
132+
}
133+
nav {
134+
display: flex;
135+
align-items: center;
136+
justify-content: space-between;
137+
margin-left: auto;
138+
}
139+
140+
nav a {
141+
color: ${theme.palette.foreground};
142+
cursor: pointer;
143+
}
144+
145+
:global(.header .link) {
146+
color: ${theme.palette.foreground} !important;
147+
}
148+
149+
.logo {
150+
display: flex;
151+
align-items: center;
152+
cursor: pointer;
153+
}
154+
155+
.logo img {
156+
width: 32px;
157+
height: 32px;
158+
}
159+
160+
.logo span {
161+
font-size: 1.125rem;
162+
font-weight: 500;
163+
margin-left: 1em;
164+
}
165+
166+
@media only screen and (max-width: ${theme.layout.breakpointMobile}) {
167+
nav {
168+
display: none;
169+
}
170+
}
171+
`}</style>
172+
</header>
173+
)
174+
}
175+
176+
export default Header

0 commit comments

Comments
 (0)