├── .env ├── .env.production ├── .eslintrc.json ├── .github └── workflows │ └── lint.yml ├── .gitignore ├── .nvmrc ├── .prettierrc ├── .vscode ├── launch.json └── settings.json ├── .yarnrc ├── LICENSE ├── README.md ├── package.json ├── public ├── favicon.ico ├── index.html ├── loading.gif └── manifest.json ├── src ├── App.js ├── Theme │ └── index.js ├── apollo │ ├── client.js │ └── queries.js ├── assets │ ├── arrows.svg │ ├── eth.png │ ├── logo.svg │ ├── logo2.svg │ ├── logo_white.svg │ ├── placeholder.png │ ├── unicorn.svg │ ├── wordmark.svg │ └── wordmark_white.svg ├── components │ ├── AccountSearch │ │ └── index.js │ ├── Attribution │ │ └── index.js │ ├── ButtonStyled │ │ └── index.js │ ├── CandleChart │ │ └── index.js │ ├── Chart │ │ └── index.js │ ├── Checkbox │ │ └── index.js │ ├── Column │ │ └── index.js │ ├── Copy │ │ └── index.js │ ├── CurrencySelect │ │ └── index.js │ ├── Dashboard │ │ └── index.js │ ├── DoubleLogo │ │ └── index.js │ ├── DropdownSelect │ │ └── index.js │ ├── Emoji │ │ └── index.js │ ├── Footer │ │ └── index.js │ ├── FormattedName │ │ └── index.js │ ├── GlobalChart │ │ └── index.js │ ├── GlobalStats │ │ └── index.js │ ├── HoverText │ │ └── index.tsx │ ├── LPList │ │ └── index.js │ ├── Link │ │ └── index.js │ ├── LocalLoader │ │ └── index.js │ ├── Meta.tsx │ ├── MiningPositionList │ │ └── index.js │ ├── PairChart │ │ └── index.js │ ├── PairList │ │ └── index.js │ ├── PairReturnsChart │ │ └── index.js │ ├── Panel │ │ └── index.js │ ├── PinnedData │ │ └── index.js │ ├── Popover │ │ └── index.tsx │ ├── PositionList │ │ └── index.js │ ├── QuestionHelper │ │ └── index.tsx │ ├── Row │ │ └── index.js │ ├── Search │ │ └── index.js │ ├── Select │ │ ├── index.js │ │ ├── popout.js │ │ └── styles.js │ ├── SideNav │ │ └── index.js │ ├── Title │ │ └── index.js │ ├── Toggle │ │ └── index.tsx │ ├── TokenChart │ │ └── index.js │ ├── TokenList │ │ └── index.js │ ├── TokenLogo │ │ └── index.js │ ├── TradingviewChart │ │ └── index.js │ ├── TxnList │ │ └── index.js │ ├── UniPrice │ │ └── index.js │ ├── UserChart │ │ └── index.js │ ├── Warning │ │ └── index.js │ ├── analytics │ │ └── GoogleAnalyticsReporter.jsx │ └── index.js ├── constants │ └── index.js ├── contexts │ ├── Application.js │ ├── GlobalData.js │ ├── LocalStorage.js │ ├── PairData.js │ ├── TokenData.js │ ├── User.js │ └── V1Data.js ├── hooks │ └── index.ts ├── index.js ├── pages │ ├── AccountLookup.js │ ├── AccountPage.js │ ├── AllPairsPage.js │ ├── AllTokensPage.js │ ├── GlobalPage.js │ ├── PairPage.js │ └── TokenPage.js ├── react-app-env.d.ts └── utils │ ├── data.ts │ ├── index.js │ ├── returns.ts │ └── tokenLists.ts ├── tsconfig.json ├── tsconfig.strict.json ├── webpack.config.js └── yarn.lock /.env: -------------------------------------------------------------------------------- 1 | REACT_APP_GOOGLE_ANALYTICS_ID="UA-128182339-5" -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | REACT_APP_GOOGLE_ANALYTICS_ID="UA-128182339-5" 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "ecmaVersion": 2020, 5 | "sourceType": "module", 6 | "ecmaFeatures": { 7 | // Allows for the parsing of JSX 8 | "jsx": true 9 | } 10 | }, 11 | "ignorePatterns": ["node_modules/**/*"], 12 | "settings": { 13 | "react": { 14 | "version": "detect" 15 | } 16 | }, 17 | "extends": [ 18 | "plugin:react/recommended", 19 | "plugin:@typescript-eslint/recommended", 20 | "plugin:react-hooks/recommended", 21 | "prettier/@typescript-eslint", 22 | "plugin:prettier/recommended" 23 | ], 24 | "rules": { 25 | "@typescript-eslint/explicit-function-return-type": "off", 26 | "prettier/prettier": "error", 27 | "@typescript-eslint/no-explicit-any": "off" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - v2 7 | pull_request: 8 | 9 | jobs: 10 | run-linters: 11 | name: Run linters 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Check out Git repository 16 | uses: actions/checkout@v2 17 | 18 | - name: Set up node 19 | uses: actions/setup-node@v1 20 | with: 21 | node-version: 16 22 | always-auth: true 23 | registry-url: https://registry.npmjs.org 24 | 25 | - name: Set output of cache 26 | id: yarn-cache 27 | run: echo "::set-output name=dir::$(yarn cache dir)" 28 | 29 | - name: Node dependency cache 30 | uses: actions/cache@v1 31 | with: 32 | path: ${{ steps.yarn-cache.outputs.dir }} 33 | key: yarn-${{ hashFiles('**/yarn.lock') }} 34 | restore-keys: | 35 | yarn- 36 | 37 | - name: Install dependencies 38 | run: yarn install --frozen-lockfile 39 | 40 | - name: Run linters 41 | uses: wearerequired/lint-action@77d70b9a07ecb93bc98dc46dc27d96c4f004d035 42 | with: 43 | github_token: ${{ secrets.github_token }} 44 | eslint: true 45 | eslint_extensions: js,jsx,ts,tsx,json 46 | auto_fix: true 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v16 -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "printWidth": 120 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "pwa-chrome", 9 | "request": "launch", 10 | "name": "Launch Chrome against localhost", 11 | "url": "http://localhost:8080", 12 | "webRoot": "${workspaceFolder}" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true 3 | } -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | ignore-scripts true 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Uniswap Info (V1 + V2) 2 | 3 | [![Lint](https://github.com/Uniswap/uniswap-info/workflows/Lint/badge.svg)](https://github.com/Uniswap/uniswap-info/actions?query=workflow%3ALint) 4 | [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) 5 | 6 | Analytics site for the [Uniswap Protocol](https://uniswap.org). 7 | 8 | Includes support for Uniswap V1 and V2. For Uniswap V3 info see https://github.com/Uniswap/v3-info 9 | 10 | ### To Start Development 11 | 12 | ###### Installing dependencies 13 | ```bash 14 | yarn 15 | ``` 16 | 17 | ###### Running locally 18 | ```bash 19 | yarn start 20 | ``` 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@uniswap/info", 3 | "private": true, 4 | "homepage": "https://info.uniswap.org", 5 | "devDependencies": { 6 | "@popperjs/core": "^2.4.2", 7 | "@reach/portal": "^0.10.3", 8 | "@reach/router": "^1.2.1", 9 | "@types/jest": "^26.0.0", 10 | "@types/node": "^16.x", 11 | "@types/react": "^16.9.36", 12 | "@types/react-dom": "^16.9.8", 13 | "@types/styled-components": "^5.1.0", 14 | "@typescript-eslint/eslint-plugin": "^4.4.0", 15 | "@typescript-eslint/parser": "^4.4.0", 16 | "@uniswap/token-lists": "^1.0.0-beta.15", 17 | "ajv": "^6.12.4", 18 | "animated-number-react": "^0.1.1", 19 | "apollo-cache-inmemory": "^1.6.3", 20 | "apollo-client": "^2.6.4", 21 | "apollo-link-http": "^1.5.16", 22 | "bignumber.js": "^9.0.0", 23 | "color-contrast-checker": "^1.5.0", 24 | "copy-to-clipboard": "^3.3.1", 25 | "dayjs": "^1.8.16", 26 | "decimal.js-light": "^2.5.0", 27 | "es-abstract": "^1.14.2", 28 | "eslint": "^6.8.0", 29 | "eslint-config-prettier": "^6.12.0", 30 | "eslint-plugin-prettier": "^3.1.4", 31 | "eslint-plugin-react": "^7.21.3", 32 | "eslint-plugin-react-hooks": "^4.1.2", 33 | "ethers": "^4.0.39", 34 | "export-to-csv": "^0.2.1", 35 | "feather-icons": "^4.24.1", 36 | "graphql": "^14.5.6", 37 | "graphql-tag": "^2.10.1", 38 | "jazzicon": "^1.5.0", 39 | "lightweight-charts": "^3.1.3", 40 | "node-vibrant": "^3.1.5", 41 | "numeral": "^2.0.6", 42 | "polished": "^3.4.4", 43 | "prettier": "^2.1.2", 44 | "react": "^16.9.0", 45 | "react-apollo": "^3.1.1", 46 | "react-device-detect": "^1.9.10", 47 | "react-dom": "^16.9.0", 48 | "react-feather": "^2.0.3", 49 | "react-ga": "^3.1.2", 50 | "react-iframe": "^1.8.0", 51 | "react-parallax": "^2.2.4", 52 | "react-popper": "^2.2.3", 53 | "react-resize-observer": "^1.1.1", 54 | "react-router-dom": "^5.2.0", 55 | "react-scripts": "^3.1.1", 56 | "react-scroll": "^1.8.0", 57 | "react-scroll-parallax": "^2.2.0", 58 | "react-select": "^3.0.7", 59 | "react-spring": "^8.0.27", 60 | "react-springy-parallax": "^1.3.0", 61 | "react-switch": "^5.0.1", 62 | "react-use": "^12.2.0", 63 | "rebass": "^4.0.7", 64 | "recharts": "^1.7.1", 65 | "recharts-fork": "^1.7.2", 66 | "styled-components": "^4.3.2", 67 | "toformat": "^2.0.0", 68 | "typeface-inter": "^3.10.0", 69 | "typescript": "^3.9.5", 70 | "unstated": "^2.1.1", 71 | "wcag-contrast": "^3.0.0" 72 | }, 73 | "scripts": { 74 | "start": "react-scripts start", 75 | "build": "react-scripts build", 76 | "test": "react-scripts test", 77 | "eject": "react-scripts eject" 78 | }, 79 | "browserslist": { 80 | "production": [ 81 | ">0.2%", 82 | "not dead", 83 | "not op_mini all" 84 | ], 85 | "development": [ 86 | "last 1 chrome version", 87 | "last 1 firefox version", 88 | "last 1 safari version" 89 | ] 90 | }, 91 | "license": "GPL-3.0-or-later", 92 | "dependencies": { 93 | "react-helmet": "^6.1.0" 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uniswap/info/a668245cfcb786f57af67fb5e6d999d7b11b1f05/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 27 | Uniswap Info 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uniswap/info/a668245cfcb786f57af67fb5e6d999d7b11b1f05/public/loading.gif -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Uniswap Info", 3 | "name": "View statistics for Uniswap exchanges.", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import styled from 'styled-components' 3 | import { ApolloProvider } from 'react-apollo' 4 | import { client } from './apollo/client' 5 | import { Route, Switch, BrowserRouter, Redirect } from 'react-router-dom' 6 | import GlobalPage from './pages/GlobalPage' 7 | import TokenPage from './pages/TokenPage' 8 | import PairPage from './pages/PairPage' 9 | import Link from './components/Link' 10 | import { useGlobalData, useGlobalChartData } from './contexts/GlobalData' 11 | import { isAddress } from './utils' 12 | import AccountPage from './pages/AccountPage' 13 | import AllTokensPage from './pages/AllTokensPage' 14 | import AllPairsPage from './pages/AllPairsPage' 15 | import PinnedData from './components/PinnedData' 16 | 17 | import SideNav from './components/SideNav' 18 | import AccountLookup from './pages/AccountLookup' 19 | import Meta from './components/Meta' 20 | import LocalLoader from './components/LocalLoader' 21 | import { useLatestBlocks } from './contexts/Application' 22 | import GoogleAnalyticsReporter from './components/analytics/GoogleAnalyticsReporter' 23 | import { PAIR_BLACKLIST, TOKEN_BLACKLIST } from './constants' 24 | 25 | const AppWrapper = styled.div` 26 | position: relative; 27 | width: 100%; 28 | ` 29 | const ContentWrapper = styled.div` 30 | display: grid; 31 | grid-template-columns: ${({ open }) => (open ? '220px 1fr 200px' : '220px 1fr 64px')}; 32 | 33 | @media screen and (max-width: 1400px) { 34 | grid-template-columns: 220px 1fr; 35 | } 36 | 37 | @media screen and (max-width: 1080px) { 38 | grid-template-columns: 1fr; 39 | max-width: 100vw; 40 | overflow: hidden; 41 | grid-gap: 0; 42 | } 43 | ` 44 | 45 | const Right = styled.div` 46 | position: fixed; 47 | right: 0; 48 | bottom: 0rem; 49 | z-index: 99; 50 | width: ${({ open }) => (open ? '220px' : '64px')}; 51 | height: ${({ open }) => (open ? 'fit-content' : '64px')}; 52 | overflow: auto; 53 | background-color: ${({ theme }) => theme.bg1}; 54 | @media screen and (max-width: 1400px) { 55 | display: none; 56 | } 57 | ` 58 | 59 | const Center = styled.div` 60 | height: 100%; 61 | z-index: 9999; 62 | transition: width 0.25s ease; 63 | background-color: ${({ theme }) => theme.onlyLight}; 64 | ` 65 | 66 | const BannerWrapper = styled.div` 67 | width: 100%; 68 | display: flex; 69 | justify-content: center; 70 | ` 71 | 72 | const WarningBanner = styled.div` 73 | background-color: #ff6871; 74 | padding: 1.5rem; 75 | color: white; 76 | width: 100%; 77 | text-align: center; 78 | font-weight: 500; 79 | ` 80 | 81 | const UrlBanner = styled.div` 82 | background-color: #ff007a; 83 | padding: 1rem; 84 | color: white; 85 | width: 100%; 86 | text-align: center; 87 | font-weight: 500; 88 | ` 89 | 90 | const Decorator = styled.span` 91 | text-decoration: underline; 92 | ` 93 | 94 | /** 95 | * Wrap the component with the header and sidebar pinned tab 96 | */ 97 | const LayoutWrapper = ({ children, savedOpen, setSavedOpen }) => { 98 | return ( 99 | <> 100 | 101 | 102 | 103 |
{children}
104 | 105 | 106 | 107 |
108 | 109 | ) 110 | } 111 | 112 | const BLOCK_DIFFERENCE_THRESHOLD = 30 113 | 114 | function App() { 115 | const [savedOpen, setSavedOpen] = useState(false) 116 | 117 | const globalData = useGlobalData() 118 | const globalChartData = useGlobalChartData() 119 | const [latestBlock, headBlock] = useLatestBlocks() 120 | 121 | // show warning 122 | const showWarning = headBlock && latestBlock ? headBlock - latestBlock > BLOCK_DIFFERENCE_THRESHOLD : false 123 | 124 | return ( 125 | 126 | 127 | 128 | 129 | {`info.uniswap.org is being deprecated on June 11th. Explore the new combined V2 and V3 analytics at `} 130 | 131 | app.uniswap.org 132 | {' '} 133 | 134 | 135 | {showWarning && ( 136 | 137 | 138 | {`Warning: The data on this site has only synced to Ethereum block ${latestBlock} (out of ${headBlock}). Please check back soon.`} 139 | 140 | 141 | )} 142 | {globalData && 143 | Object.keys(globalData).length > 0 && 144 | globalChartData && 145 | Object.keys(globalChartData).length > 0 ? ( 146 | 147 | 148 | 149 | { 154 | if ( 155 | isAddress(match.params.tokenAddress.toLowerCase()) && 156 | !Object.keys(TOKEN_BLACKLIST).includes(match.params.tokenAddress.toLowerCase()) 157 | ) { 158 | return ( 159 | 160 | 161 | 162 | ) 163 | } else { 164 | return 165 | } 166 | }} 167 | /> 168 | { 173 | if ( 174 | isAddress(match.params.pairAddress.toLowerCase()) && 175 | !Object.keys(PAIR_BLACKLIST).includes(match.params.pairAddress.toLowerCase()) 176 | ) { 177 | return ( 178 | 179 | 180 | 181 | ) 182 | } else { 183 | return 184 | } 185 | }} 186 | /> 187 | { 192 | if (isAddress(match.params.accountAddress.toLowerCase())) { 193 | return ( 194 | 195 | 196 | 197 | ) 198 | } else { 199 | return 200 | } 201 | }} 202 | /> 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | ) : ( 232 | 233 | )} 234 | 235 | 236 | ) 237 | } 238 | 239 | export default App 240 | -------------------------------------------------------------------------------- /src/Theme/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { ThemeProvider as StyledComponentsThemeProvider, createGlobalStyle } from 'styled-components' 3 | import { useDarkModeManager } from '../contexts/LocalStorage' 4 | import styled from 'styled-components' 5 | import { Text } from 'rebass' 6 | 7 | export default function ThemeProvider({ children }) { 8 | const [darkMode] = useDarkModeManager() 9 | 10 | return {children} 11 | } 12 | 13 | const theme = (darkMode, color) => ({ 14 | customColor: color, 15 | textColor: darkMode ? color : 'black', 16 | 17 | panelColor: darkMode ? 'rgba(255, 255, 255, 0)' : 'rgba(255, 255, 255, 0)', 18 | backgroundColor: darkMode ? '#212429' : '#F7F8FA', 19 | 20 | uniswapPink: darkMode ? '#ff007a' : 'black', 21 | 22 | concreteGray: darkMode ? '#292C2F' : '#FAFAFA', 23 | inputBackground: darkMode ? '#1F1F1F' : '#FAFAFA', 24 | shadowColor: darkMode ? '#000' : '#2F80ED', 25 | mercuryGray: darkMode ? '#333333' : '#E1E1E1', 26 | 27 | text1: darkMode ? '#FAFAFA' : '#1F1F1F', 28 | text2: darkMode ? '#C3C5CB' : '#565A69', 29 | text3: darkMode ? '#6C7284' : '#888D9B', 30 | text4: darkMode ? '#565A69' : '#C3C5CB', 31 | text5: darkMode ? '#2C2F36' : '#EDEEF2', 32 | 33 | // special case text types 34 | white: '#FFFFFF', 35 | 36 | // backgrounds / greys 37 | bg1: darkMode ? '#212429' : '#FAFAFA', 38 | bg2: darkMode ? '#2C2F36' : '#F7F8FA', 39 | bg3: darkMode ? '#40444F' : '#EDEEF2', 40 | bg4: darkMode ? '#565A69' : '#CED0D9', 41 | bg5: darkMode ? '#565A69' : '#888D9B', 42 | bg6: darkMode ? '#000' : '#FFFFFF', 43 | 44 | //specialty colors 45 | modalBG: darkMode ? 'rgba(0,0,0,0.85)' : 'rgba(0,0,0,0.6)', 46 | advancedBG: darkMode ? 'rgba(0,0,0,0.1)' : 'rgba(255,255,255,0.4)', 47 | onlyLight: darkMode ? '#22242a' : 'transparent', 48 | divider: darkMode ? 'rgba(43, 43, 43, 0.435)' : 'rgba(43, 43, 43, 0.035)', 49 | 50 | //primary colors 51 | primary1: darkMode ? '#2172E5' : '#ff007a', 52 | primary2: darkMode ? '#3680E7' : '#FF8CC3', 53 | primary3: darkMode ? '#4D8FEA' : '#FF99C9', 54 | primary4: darkMode ? '#376bad70' : '#F6DDE8', 55 | primary5: darkMode ? '#153d6f70' : '#FDEAF1', 56 | 57 | // color text 58 | primaryText1: darkMode ? '#6da8ff' : '#ff007a', 59 | 60 | // secondary colors 61 | secondary1: darkMode ? '#2172E5' : '#ff007a', 62 | secondary2: darkMode ? '#17000b26' : '#F6DDE8', 63 | secondary3: darkMode ? '#17000b26' : '#FDEAF1', 64 | 65 | shadow1: darkMode ? '#000' : '#2F80ED', 66 | 67 | // other 68 | red1: '#FF6871', 69 | green1: '#27AE60', 70 | yellow1: '#FFE270', 71 | yellow2: '#F3841E', 72 | link: '#2172E5', 73 | blue: '2f80ed', 74 | 75 | background: darkMode ? 'black' : `radial-gradient(50% 50% at 50% 50%, #ff007a30 0%, #fff 0%)`, 76 | }) 77 | 78 | const TextWrapper = styled(Text)` 79 | color: ${({ color, theme }) => theme[color]}; 80 | ` 81 | 82 | export const TYPE = { 83 | main(props) { 84 | return 85 | }, 86 | 87 | body(props) { 88 | return 89 | }, 90 | 91 | small(props) { 92 | return 93 | }, 94 | 95 | header(props) { 96 | return 97 | }, 98 | 99 | largeHeader(props) { 100 | return 101 | }, 102 | 103 | light(props) { 104 | return 105 | }, 106 | 107 | pink(props) { 108 | return 109 | }, 110 | } 111 | 112 | export const Hover = styled.div` 113 | :hover { 114 | cursor: pointer; 115 | } 116 | ` 117 | 118 | export const Link = styled.a.attrs({ 119 | target: '_blank', 120 | rel: 'noopener noreferrer', 121 | })` 122 | text-decoration: none; 123 | cursor: pointer; 124 | color: ${({ theme }) => theme.primary1}; 125 | font-weight: 500; 126 | :hover { 127 | text-decoration: underline; 128 | } 129 | :focus { 130 | outline: none; 131 | text-decoration: underline; 132 | } 133 | :active { 134 | text-decoration: none; 135 | } 136 | ` 137 | 138 | export const ThemedBackground = styled.div` 139 | position: absolute; 140 | top: 0; 141 | left: 0; 142 | right: 0; 143 | pointer-events: none; 144 | max-width: 100vw !important; 145 | height: 200vh; 146 | mix-blend-mode: color; 147 | background: ${({ backgroundColor }) => 148 | `radial-gradient(50% 50% at 50% 50%, ${backgroundColor} 0%, rgba(255, 255, 255, 0) 100%)`}; 149 | position: absolute; 150 | top: 0px; 151 | left: 0px; 152 | /* z-index: ; */ 153 | 154 | transform: translateY(-110vh); 155 | ` 156 | 157 | export const GlobalStyle = createGlobalStyle` 158 | @import url('https://rsms.me/inter/inter.css'); 159 | html { font-family: 'Inter', sans-serif; } 160 | @supports (font-variation-settings: normal) { 161 | html { font-family: 'Inter var', sans-serif; } 162 | } 163 | 164 | html, 165 | body { 166 | margin: 0; 167 | padding: 0; 168 | width: 100%; 169 | height: 100%; 170 | font-size: 14px; 171 | background-color: ${({ theme }) => theme.bg6}; 172 | } 173 | 174 | a { 175 | text-decoration: none; 176 | 177 | :hover { 178 | text-decoration: none 179 | } 180 | } 181 | 182 | 183 | .three-line-legend { 184 | width: 100%; 185 | height: 70px; 186 | position: absolute; 187 | padding: 8px; 188 | font-size: 12px; 189 | color: #20262E; 190 | background-color: rgba(255, 255, 255, 0.23); 191 | text-align: left; 192 | z-index: 10; 193 | pointer-events: none; 194 | } 195 | 196 | .three-line-legend-dark { 197 | width: 100%; 198 | height: 70px; 199 | position: absolute; 200 | padding: 8px; 201 | font-size: 12px; 202 | color: white; 203 | background-color: rgba(255, 255, 255, 0.23); 204 | text-align: left; 205 | z-index: 10; 206 | pointer-events: none; 207 | } 208 | 209 | @media screen and (max-width: 800px) { 210 | .three-line-legend { 211 | display: none !important; 212 | } 213 | } 214 | 215 | .tv-lightweight-charts{ 216 | width: 100% !important; 217 | 218 | 219 | & > * { 220 | width: 100% !important; 221 | } 222 | } 223 | 224 | 225 | html { 226 | font-size: 1rem; 227 | font-variant: none; 228 | color: 'black'; 229 | -webkit-font-smoothing: antialiased; 230 | -moz-osx-font-smoothing: grayscale; 231 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 232 | height: 100%; 233 | } 234 | ` 235 | -------------------------------------------------------------------------------- /src/apollo/client.js: -------------------------------------------------------------------------------- 1 | import { ApolloClient } from 'apollo-client' 2 | import { InMemoryCache } from 'apollo-cache-inmemory' 3 | import { HttpLink } from 'apollo-link-http' 4 | 5 | export const client = new ApolloClient({ 6 | link: new HttpLink({ 7 | uri: 'https://api.thegraph.com/subgraphs/name/ianlapham/uniswap-v2-dev', 8 | }), 9 | cache: new InMemoryCache(), 10 | shouldBatch: true, 11 | }) 12 | 13 | export const healthClient = new ApolloClient({ 14 | link: new HttpLink({ 15 | uri: 'https://api.thegraph.com/index-node/graphql', 16 | }), 17 | cache: new InMemoryCache(), 18 | shouldBatch: true, 19 | }) 20 | 21 | export const v1Client = new ApolloClient({ 22 | link: new HttpLink({ 23 | uri: 'https://api.thegraph.com/subgraphs/name/ianlapham/uniswap', 24 | }), 25 | cache: new InMemoryCache(), 26 | shouldBatch: true, 27 | }) 28 | 29 | export const stakingClient = new ApolloClient({ 30 | link: new HttpLink({ 31 | uri: 'https://api.thegraph.com/subgraphs/name/way2rach/talisman', 32 | }), 33 | cache: new InMemoryCache(), 34 | shouldBatch: true, 35 | }) 36 | 37 | export const blockClient = new ApolloClient({ 38 | link: new HttpLink({ 39 | uri: 'https://api.thegraph.com/subgraphs/name/blocklytics/ethereum-blocks', 40 | }), 41 | cache: new InMemoryCache(), 42 | }) 43 | -------------------------------------------------------------------------------- /src/assets/eth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uniswap/info/a668245cfcb786f57af67fb5e6d999d7b11b1f05/src/assets/eth.png -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/assets/logo_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/assets/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uniswap/info/a668245cfcb786f57af67fb5e6d999d7b11b1f05/src/assets/placeholder.png -------------------------------------------------------------------------------- /src/assets/unicorn.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/components/AccountSearch/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import 'feather-icons' 3 | import { withRouter } from 'react-router-dom' 4 | import styled from 'styled-components' 5 | import { ButtonLight, ButtonFaded } from '../ButtonStyled' 6 | import { AutoRow, RowBetween } from '../Row' 7 | import { isAddress } from '../../utils' 8 | import { useSavedAccounts } from '../../contexts/LocalStorage' 9 | import { AutoColumn } from '../Column' 10 | import { TYPE } from '../../Theme' 11 | import { Hover, StyledIcon } from '..' 12 | import Panel from '../Panel' 13 | import { Divider } from '..' 14 | import { Flex } from 'rebass' 15 | 16 | import { X } from 'react-feather' 17 | 18 | const Wrapper = styled.div` 19 | display: flex; 20 | flex-direction: row; 21 | align-items: center; 22 | justify-content: flex-end; 23 | width: 100%; 24 | border-radius: 12px; 25 | ` 26 | 27 | const Input = styled.input` 28 | position: relative; 29 | display: flex; 30 | align-items: center; 31 | width: 100%; 32 | white-space: nowrap; 33 | background: none; 34 | border: none; 35 | outline: none; 36 | padding: 12px 16px; 37 | border-radius: 12px; 38 | color: ${({ theme }) => theme.text1}; 39 | background-color: ${({ theme }) => theme.bg1}; 40 | font-size: 16px; 41 | margin-right: 1rem; 42 | border: 1px solid ${({ theme }) => theme.bg3}; 43 | 44 | ::placeholder { 45 | color: ${({ theme }) => theme.text3}; 46 | font-size: 14px; 47 | } 48 | 49 | @media screen and (max-width: 640px) { 50 | ::placeholder { 51 | font-size: 1rem; 52 | } 53 | } 54 | ` 55 | 56 | const AccountLink = styled.span` 57 | display: flex; 58 | cursor: pointer; 59 | color: ${({ theme }) => theme.link}; 60 | font-size: 14px; 61 | font-weight: 500; 62 | ` 63 | 64 | const DashGrid = styled.div` 65 | display: grid; 66 | grid-gap: 1em; 67 | grid-template-columns: 1fr; 68 | grid-template-areas: 'account'; 69 | padding: 0 4px; 70 | 71 | > * { 72 | justify-content: flex-end; 73 | } 74 | ` 75 | 76 | function AccountSearch({ history, small }) { 77 | const [accountValue, setAccountValue] = useState() 78 | const [savedAccounts, addAccount, removeAccount] = useSavedAccounts() 79 | 80 | function handleAccountSearch() { 81 | if (isAddress(accountValue)) { 82 | history.push('/account/' + accountValue) 83 | if (!savedAccounts.includes(accountValue)) { 84 | addAccount(accountValue) 85 | } 86 | } 87 | } 88 | 89 | return ( 90 | 91 | {!small && ( 92 | <> 93 | 94 | 95 | { 98 | setAccountValue(e.target.value) 99 | }} 100 | /> 101 | 102 | Load Account Details 103 | 104 | 105 | )} 106 | 107 | 108 | {!small && ( 109 | 110 | 111 | Saved Accounts 112 | 113 | 114 | {savedAccounts?.length > 0 ? ( 115 | savedAccounts.map((account) => { 116 | return ( 117 | 118 | history.push('/account/' + account)} 122 | > 123 | {account?.slice(0, 42)} 124 | { 126 | e.stopPropagation() 127 | removeAccount(account) 128 | }} 129 | > 130 | 131 | 132 | 133 | 134 | 135 | 136 | ) 137 | }) 138 | ) : ( 139 | No saved accounts 140 | )} 141 | 142 | )} 143 | 144 | {small && ( 145 | <> 146 | {'Accounts'} 147 | {savedAccounts?.length > 0 ? ( 148 | savedAccounts.map((account) => { 149 | return ( 150 | 151 | history.push('/account/' + account)}> 152 | {small ? ( 153 | {account?.slice(0, 6) + '...' + account?.slice(38, 42)} 154 | ) : ( 155 | {account?.slice(0, 42)} 156 | )} 157 | 158 | removeAccount(account)}> 159 | 160 | 161 | 162 | 163 | 164 | ) 165 | }) 166 | ) : ( 167 | No pinned wallets 168 | )} 169 | 170 | )} 171 | 172 | 173 | ) 174 | } 175 | 176 | export default withRouter(AccountSearch) 177 | -------------------------------------------------------------------------------- /src/components/Attribution/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Attribution = () => ( 4 |

5 | 6 | Github 7 | {' '} 8 | |{' '} 9 | 10 | Uniswap 11 | {' '} 12 | |{' '} 13 | 14 | GIF 15 | 16 |

17 | ) 18 | 19 | export default Attribution 20 | -------------------------------------------------------------------------------- /src/components/ButtonStyled/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Button as RebassButton } from 'rebass/styled-components' 3 | import styled from 'styled-components' 4 | import { Plus, ChevronDown, ChevronUp } from 'react-feather' 5 | import { darken, transparentize } from 'polished' 6 | import { RowBetween } from '../Row' 7 | import { StyledIcon } from '..' 8 | 9 | const Base = styled(RebassButton)` 10 | padding: 8px 12px; 11 | font-size: 0.825rem; 12 | font-weight: 600; 13 | border-radius: 12px; 14 | cursor: pointer; 15 | outline: none; 16 | border: 1px solid transparent; 17 | outline: none; 18 | border-bottom-right-radius: ${({ open }) => open && '0'}; 19 | border-bottom-left-radius: ${({ open }) => open && '0'}; 20 | ` 21 | 22 | const BaseCustom = styled(RebassButton)` 23 | padding: 16px 12px; 24 | font-size: 0.825rem; 25 | font-weight: 400; 26 | border-radius: 12px; 27 | cursor: pointer; 28 | outline: none; 29 | ` 30 | 31 | const Dull = styled(Base)` 32 | background-color: rgba(255, 255, 255, 0.15); 33 | border: 1px solid rgba(255, 255, 255, 0.15); 34 | color: black; 35 | height: 100%; 36 | font-weight: 400; 37 | &:hover, 38 | :focus { 39 | background-color: rgba(255, 255, 255, 0.25); 40 | border-color: rgba(255, 255, 255, 0.25); 41 | } 42 | &:focus { 43 | box-shadow: 0 0 0 1pt rgba(255, 255, 255, 0.25); 44 | } 45 | &:active { 46 | background-color: rgba(255, 255, 255, 0.25); 47 | border-color: rgba(255, 255, 255, 0.25); 48 | } 49 | ` 50 | 51 | export default function ButtonStyled({ children, ...rest }) { 52 | return {children} 53 | } 54 | 55 | const ContentWrapper = styled.div` 56 | display: flex; 57 | flex-direction: row; 58 | align-items: center; 59 | justify-content: space-between; 60 | ` 61 | 62 | export const ButtonLight = styled(Base)` 63 | background-color: ${({ color, theme }) => (color ? transparentize(0.9, color) : transparentize(0.9, theme.primary1))}; 64 | color: ${({ color, theme }) => (color ? darken(0.1, color) : theme.primary1)}; 65 | 66 | min-width: fit-content; 67 | border-radius: 12px; 68 | white-space: nowrap; 69 | 70 | a { 71 | color: ${({ color, theme }) => (color ? darken(0.1, color) : theme.primary1)}; 72 | } 73 | 74 | :hover { 75 | background-color: ${({ color, theme }) => 76 | color ? transparentize(0.8, color) : transparentize(0.8, theme.primary1)}; 77 | } 78 | ` 79 | 80 | export function ButtonDropdown({ disabled = false, children, open, ...rest }) { 81 | return ( 82 | 83 | 84 |
{children}
85 | {open ? ( 86 | 87 | 88 | 89 | ) : ( 90 | 91 | 92 | 93 | )} 94 |
95 |
96 | ) 97 | } 98 | 99 | export const ButtonDark = styled(Base)` 100 | background-color: ${({ color, theme }) => (color ? color : theme.primary1)}; 101 | color: white; 102 | width: fit-content; 103 | border-radius: 12px; 104 | white-space: nowrap; 105 | 106 | :hover { 107 | background-color: ${({ color, theme }) => (color ? darken(0.1, color) : darken(0.1, theme.primary1))}; 108 | } 109 | ` 110 | 111 | export const ButtonFaded = styled(Base)` 112 | background-color: ${({ theme }) => theme.bg2}; 113 | color: (255, 255, 255, 0.5); 114 | white-space: nowrap; 115 | 116 | :hover { 117 | opacity: 0.5; 118 | } 119 | ` 120 | 121 | export function ButtonPlusDull({ disabled, children, ...rest }) { 122 | return ( 123 | 124 | 125 | 126 |
{children}
127 |
128 |
129 | ) 130 | } 131 | 132 | export function ButtonCustom({ children, bgColor, color, ...rest }) { 133 | return ( 134 | 135 | {children} 136 | 137 | ) 138 | } 139 | 140 | export const OptionButton = styled.div` 141 | font-weight: 500; 142 | width: fit-content; 143 | white-space: nowrap; 144 | padding: 6px; 145 | border-radius: 6px; 146 | border: 1px solid ${({ theme }) => theme.bg4}; 147 | background-color: ${({ active, theme }) => active && theme.bg3}; 148 | color: ${({ theme }) => theme.text1}; 149 | 150 | :hover { 151 | cursor: ${({ disabled }) => !disabled && 'pointer'}; 152 | } 153 | ` 154 | -------------------------------------------------------------------------------- /src/components/CandleChart/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react' 2 | import { createChart, CrosshairMode } from 'lightweight-charts' 3 | import dayjs from 'dayjs' 4 | import { formattedNum } from '../../utils' 5 | import { usePrevious } from 'react-use' 6 | import styled from 'styled-components' 7 | import { Play } from 'react-feather' 8 | import { useDarkModeManager } from '../../contexts/LocalStorage' 9 | 10 | const IconWrapper = styled.div` 11 | position: absolute; 12 | right: 10px; 13 | color: ${({ theme }) => theme.text1} 14 | border-radius: 3px; 15 | height: 16px; 16 | width: 16px; 17 | padding: 0px; 18 | bottom: 10px; 19 | display: flex; 20 | align-items: center; 21 | justify-content: center; 22 | :hover { 23 | cursor: pointer; 24 | opacity: 0.7; 25 | } 26 | ` 27 | 28 | const CandleStickChart = ({ 29 | data, 30 | width, 31 | height = 300, 32 | base, 33 | margin = true, 34 | valueFormatter = (val) => formattedNum(val, true), 35 | }) => { 36 | // reference for DOM element to create with chart 37 | const ref = useRef() 38 | 39 | const formattedData = data?.map((entry) => { 40 | return { 41 | time: parseFloat(entry.timestamp), 42 | open: parseFloat(entry.open), 43 | low: parseFloat(entry.open), 44 | close: parseFloat(entry.close), 45 | high: parseFloat(entry.close), 46 | } 47 | }) 48 | 49 | if (formattedData && formattedData.length > 0) { 50 | formattedData.push({ 51 | time: dayjs().unix(), 52 | open: parseFloat(formattedData[formattedData.length - 1].close), 53 | close: parseFloat(base), 54 | low: Math.min(parseFloat(base), parseFloat(formattedData[formattedData.length - 1].close)), 55 | high: Math.max(parseFloat(base), parseFloat(formattedData[formattedData.length - 1].close)), 56 | }) 57 | } 58 | 59 | // pointer to the chart object 60 | const [chartCreated, setChartCreated] = useState(false) 61 | const dataPrev = usePrevious(data) 62 | 63 | const [darkMode] = useDarkModeManager() 64 | const textColor = darkMode ? 'white' : 'black' 65 | const previousTheme = usePrevious(darkMode) 66 | 67 | // reset the chart if theme switches 68 | useEffect(() => { 69 | if (chartCreated && previousTheme !== darkMode) { 70 | // remove the tooltip element 71 | let tooltip = document.getElementById('tooltip-id') 72 | let node = document.getElementById('test-id') 73 | node.removeChild(tooltip) 74 | chartCreated.resize(0, 0) 75 | setChartCreated() 76 | } 77 | }, [chartCreated, darkMode, previousTheme]) 78 | 79 | useEffect(() => { 80 | if (data !== dataPrev && chartCreated) { 81 | // remove the tooltip element 82 | let tooltip = document.getElementById('tooltip-id') 83 | let node = document.getElementById('test-id') 84 | node.removeChild(tooltip) 85 | chartCreated.resize(0, 0) 86 | setChartCreated() 87 | } 88 | }, [chartCreated, data, dataPrev]) 89 | 90 | // if no chart created yet, create one with options and add to DOM manually 91 | useEffect(() => { 92 | if (!chartCreated) { 93 | const chart = createChart(ref.current, { 94 | width: width, 95 | height: height, 96 | layout: { 97 | backgroundColor: 'transparent', 98 | textColor: textColor, 99 | }, 100 | grid: { 101 | vertLines: { 102 | color: 'rgba(197, 203, 206, 0.5)', 103 | }, 104 | horzLines: { 105 | color: 'rgba(197, 203, 206, 0.5)', 106 | }, 107 | }, 108 | crosshair: { 109 | mode: CrosshairMode.Normal, 110 | }, 111 | rightPriceScale: { 112 | borderColor: 'rgba(197, 203, 206, 0.8)', 113 | visible: true, 114 | }, 115 | timeScale: { 116 | borderColor: 'rgba(197, 203, 206, 0.8)', 117 | }, 118 | localization: { 119 | priceFormatter: (val) => formattedNum(val), 120 | }, 121 | }) 122 | 123 | var candleSeries = chart.addCandlestickSeries({ 124 | upColor: 'green', 125 | downColor: 'red', 126 | borderDownColor: 'red', 127 | borderUpColor: 'green', 128 | wickDownColor: 'red', 129 | wickUpColor: 'green', 130 | }) 131 | 132 | candleSeries.setData(formattedData) 133 | 134 | var toolTip = document.createElement('div') 135 | toolTip.setAttribute('id', 'tooltip-id') 136 | toolTip.className = 'three-line-legend' 137 | ref.current.appendChild(toolTip) 138 | toolTip.style.display = 'block' 139 | toolTip.style.left = (margin ? 116 : 10) + 'px' 140 | toolTip.style.top = 50 + 'px' 141 | toolTip.style.backgroundColor = 'transparent' 142 | 143 | // get the title of the chart 144 | function setLastBarText() { 145 | toolTip.innerHTML = base 146 | ? `
` + valueFormatter(base) + '
' 147 | : '' 148 | } 149 | setLastBarText() 150 | 151 | // update the title when hovering on the chart 152 | chart.subscribeCrosshairMove(function (param) { 153 | if ( 154 | param === undefined || 155 | param.time === undefined || 156 | param.point.x < 0 || 157 | param.point.x > width || 158 | param.point.y < 0 || 159 | param.point.y > height 160 | ) { 161 | setLastBarText() 162 | } else { 163 | var price = param.seriesPrices.get(candleSeries).close 164 | const time = dayjs.unix(param.time).format('MM/DD h:mm A') 165 | toolTip.innerHTML = 166 | `
` + 167 | valueFormatter(price) + 168 | `` + 169 | time + 170 | ' UTC' + 171 | '' + 172 | '
' 173 | } 174 | }) 175 | 176 | chart.timeScale().fitContent() 177 | 178 | setChartCreated(chart) 179 | } 180 | }, [chartCreated, formattedData, width, height, valueFormatter, base, margin, textColor]) 181 | 182 | // responsiveness 183 | useEffect(() => { 184 | if (width) { 185 | chartCreated && chartCreated.resize(width, height) 186 | chartCreated && chartCreated.timeScale().scrollToPosition(0) 187 | } 188 | }, [chartCreated, height, width]) 189 | 190 | return ( 191 |
192 |
193 | 194 | { 196 | chartCreated && chartCreated.timeScale().fitContent() 197 | }} 198 | /> 199 | 200 |
201 | ) 202 | } 203 | 204 | export default CandleStickChart 205 | -------------------------------------------------------------------------------- /src/components/Chart/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import { Area, XAxis, YAxis, ResponsiveContainer, Bar, BarChart, CartesianGrid, Tooltip, AreaChart } from 'recharts' 3 | import styled from 'styled-components' 4 | import { useMedia } from 'react-use' 5 | import { toK, toNiceDate, toNiceDateYear } from '../../utils' 6 | 7 | const ChartWrapper = styled.div` 8 | padding-top: 1em; 9 | margin-left: -1.5em; 10 | @media (max-width: 40em) { 11 | margin-left: -1em; 12 | } 13 | ` 14 | 15 | const Chart = ({ data, chartOption, currencyUnit, symbol }) => { 16 | const [chartData, setChartData] = useState([]) 17 | useEffect(() => { 18 | setChartData([]) 19 | setChartData(data) 20 | }, [data, chartOption, currencyUnit]) 21 | 22 | const isMobile = useMedia('(max-width: 40em)') 23 | if (chartOption === 'price' && chartData && data) { 24 | return ( 25 | 26 | 27 | 28 | 29 | toNiceDate(tick)} 36 | dataKey="dayString" 37 | /> 38 | toK(tick)} 44 | axisLine={false} 45 | tickLine={false} 46 | interval="preserveEnd" 47 | minTickGap={80} 48 | yAxisId={2} 49 | /> 50 | toK(tick)} 56 | axisLine={false} 57 | tickLine={false} 58 | interval="preserveEnd" 59 | minTickGap={80} 60 | yAxisId={3} 61 | /> 62 | 73 | 84 | toK(val, true)} 87 | labelFormatter={(label) => toNiceDateYear(label)} 88 | labelStyle={{ paddingTop: 4 }} 89 | contentStyle={{ 90 | padding: '10px 14px', 91 | borderRadius: 10, 92 | borderColor: 'var(--c-zircon)', 93 | }} 94 | wrapperStyle={{ top: -70, left: -10 }} 95 | /> 96 | 97 | 98 | 99 | ) 100 | } 101 | if (chartOption !== 'volume' && chartData && data) { 102 | return ( 103 | 104 | 105 | 106 | 107 | toNiceDate(tick)} 114 | dataKey="dayString" 115 | /> 116 | toK(tick)} 122 | axisLine={false} 123 | tickLine={false} 124 | interval="preserveEnd" 125 | minTickGap={80} 126 | yAxisId={0} 127 | /> 128 | toK(tick)} 134 | axisLine={false} 135 | tickLine={false} 136 | interval="preserveEnd" 137 | minTickGap={80} 138 | yAxisId={1} 139 | /> 140 | toK(val, true)} 143 | labelFormatter={(label) => toNiceDateYear(label)} 144 | labelStyle={{ paddingTop: 4 }} 145 | contentStyle={{ 146 | padding: '10px 14px', 147 | borderRadius: 10, 148 | borderColor: 'var(--c-zircon)', 149 | }} 150 | wrapperStyle={{ top: -70, left: -10 }} 151 | /> 152 | 163 | 171 | 180 | 181 | 182 | 183 | ) 184 | } else { 185 | // volume 186 | return ( 187 | 188 | 189 | 190 | 191 | toNiceDate(tick)} 198 | dataKey="dayString" 199 | /> 200 | toK(tick)} 206 | tickLine={false} 207 | interval="preserveEnd" 208 | minTickGap={80} 209 | yAxisId={0} 210 | /> 211 | toK(val, true)} 214 | labelFormatter={(label) => toNiceDateYear(label)} 215 | labelStyle={{ paddingTop: 4 }} 216 | contentStyle={{ 217 | padding: '10px 14px', 218 | borderRadius: 10, 219 | borderColor: 'var(--c-zircon)', 220 | }} 221 | wrapperStyle={{ top: -70, left: -10 }} 222 | /> 223 | 232 | 233 | 234 | 235 | ) 236 | } 237 | } 238 | 239 | export default Chart 240 | -------------------------------------------------------------------------------- /src/components/Checkbox/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { TYPE } from '../../Theme' 4 | import { RowFixed } from '../Row' 5 | 6 | const StyleCheckbox = styled.input` 7 | background: ${({ theme }) => theme.bg2}; 8 | 9 | :before { 10 | background: #f35429; 11 | } 12 | 13 | :hover { 14 | cursor: pointer; 15 | } 16 | ` 17 | 18 | const ButtonText = styled(TYPE.main)` 19 | cursor: pointer; 20 | :hover { 21 | opacity: 0.6; 22 | } 23 | ` 24 | 25 | const CheckBox = ({ checked, setChecked, text }) => { 26 | return ( 27 | 28 | 29 | 30 | {text} 31 | 32 | 33 | ) 34 | } 35 | 36 | export default CheckBox 37 | -------------------------------------------------------------------------------- /src/components/Column/index.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | const Column = styled.div` 4 | display: flex; 5 | flex-direction: column; 6 | justify-content: flex-start; 7 | ` 8 | export const ColumnCenter = styled(Column)` 9 | width: 100%; 10 | align-items: center; 11 | ` 12 | 13 | export const AutoColumn = styled.div` 14 | display: grid; 15 | grid-auto-rows: auto; 16 | grid-row-gap: ${({ gap }) => (gap === 'sm' && '8px') || (gap === 'md' && '12px') || (gap === 'lg' && '24px') || gap}; 17 | justify-items: ${({ justify }) => justify && justify}; 18 | ` 19 | 20 | export default Column 21 | -------------------------------------------------------------------------------- /src/components/Copy/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { useCopyClipboard } from '../../hooks' 4 | import { CheckCircle, Copy } from 'react-feather' 5 | import { StyledIcon } from '..' 6 | 7 | const CopyIcon = styled.div` 8 | color: #aeaeae; 9 | flex-shrink: 0; 10 | margin-right: 1rem; 11 | margin-left: 0.5rem; 12 | text-decoration: none; 13 | :hover, 14 | :active, 15 | :focus { 16 | text-decoration: none; 17 | opacity: 0.8; 18 | cursor: pointer; 19 | } 20 | ` 21 | const TransactionStatusText = styled.span` 22 | margin-left: 0.25rem; 23 | ${({ theme }) => theme.flexRowNoWrap}; 24 | align-items: center; 25 | color: black; 26 | ` 27 | 28 | export default function CopyHelper({ toCopy }) { 29 | const [isCopied, setCopied] = useCopyClipboard() 30 | 31 | return ( 32 | setCopied(toCopy)}> 33 | {isCopied ? ( 34 | 35 | 36 | 37 | 38 | 39 | ) : ( 40 | 41 | 42 | 43 | 44 | 45 | )} 46 | 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /src/components/CurrencySelect/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import styled from 'styled-components' 3 | 4 | import { useCurrentCurrency } from '../../contexts/Application' 5 | 6 | import Row from '../Row' 7 | import { ChevronDown as Arrow } from 'react-feather' 8 | 9 | const Select = styled.div` 10 | position: relative; 11 | display: flex; 12 | flex-direction: column; 13 | align-items: center; 14 | justify-content: center; 15 | 16 | width: fit-content; 17 | height: 38px; 18 | border-radius: 20px; 19 | font-weight: 500; 20 | font-size: 1rem; 21 | color: ${({ theme }) => theme.textColor}; 22 | 23 | :hover { 24 | cursor: pointer; 25 | } 26 | 27 | @media screen and (max-width: 40em) { 28 | display: none; 29 | } 30 | ` 31 | 32 | const ArrowStyled = styled(Arrow)` 33 | height: 20px; 34 | width: 20px; 35 | margin-left: 6px; 36 | ` 37 | 38 | const Option = styled(Row)` 39 | position: absolute; 40 | top: 40px; 41 | ` 42 | 43 | const CurrencySelect = () => { 44 | const [showDropdown, toggleDropdown] = useState(false) 45 | const [currency, toggleCurrency] = useCurrentCurrency() 46 | 47 | const getOther = () => { 48 | if (currency === 'USD') { 49 | return 'ETH' 50 | } else { 51 | return 'USD' 52 | } 53 | } 54 | 55 | return ( 56 | <> 57 | 72 | 73 | ) 74 | } 75 | 76 | export default CurrencySelect 77 | -------------------------------------------------------------------------------- /src/components/Dashboard/index.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { Box } from 'rebass' 3 | 4 | const Dashboard = styled(Box)` 5 | width: 100%; 6 | display: grid; 7 | grid-template-columns: 100%; 8 | grid-template-areas: 9 | 'volume' 10 | 'liquidity' 11 | 'shares' 12 | 'statistics' 13 | 'exchange' 14 | 'transactions'; 15 | 16 | @media screen and (min-width: 64em) { 17 | max-width: 1320px; 18 | grid-gap: 24px; 19 | width: 100%; 20 | grid-template-columns: 1fr 1fr 1fr; 21 | grid-template-areas: 22 | /* "statsHeader statsHeader statsHeader" */ 23 | 'fill fill fill' 24 | 'pairHeader pairHeader pairHeader' 25 | 'transactions2 transactions2 transactions2' 26 | 'listOptions listOptions listOptions' 27 | 'transactions transactions transactions'; 28 | } 29 | ` 30 | 31 | export default Dashboard 32 | -------------------------------------------------------------------------------- /src/components/DoubleLogo/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import TokenLogo from '../TokenLogo' 4 | 5 | export default function DoubleTokenLogo({ a0, a1, size = 24, margin = false }) { 6 | const TokenWrapper = styled.div` 7 | position: relative; 8 | display: flex; 9 | flex-direction: row; 10 | margin-right: ${({ sizeraw, margin }) => margin && (sizeraw / 3 + 8).toString() + 'px'}; 11 | ` 12 | 13 | const HigherLogo = styled(TokenLogo)` 14 | z-index: 2; 15 | // background-color: white; 16 | border-radius: 50%; 17 | ` 18 | 19 | const CoveredLogo = styled(TokenLogo)` 20 | position: absolute; 21 | left: ${({ sizeraw }) => (sizeraw / 2).toString() + 'px'}; 22 | // background-color: white; 23 | border-radius: 50%; 24 | ` 25 | 26 | return ( 27 | 28 | 29 | 30 | 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /src/components/DropdownSelect/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import styled from 'styled-components' 3 | 4 | import Row, { RowBetween } from '../Row' 5 | import { AutoColumn } from '../Column' 6 | import { ChevronDown as Arrow } from 'react-feather' 7 | import { TYPE } from '../../Theme' 8 | import { StyledIcon } from '..' 9 | 10 | const Wrapper = styled.div` 11 | z-index: 20; 12 | position: relative; 13 | background-color: ${({ theme }) => theme.panelColor}; 14 | border: 1px solid ${({ open, color }) => (open ? color : 'rgba(0, 0, 0, 0.15);')} 15 | width: 100px; 16 | padding: 4px 10px; 17 | padding-right: 6px; 18 | border-radius: 8px; 19 | display: flex; 20 | align-items: center; 21 | justify-content: center; 22 | 23 | :hover { 24 | cursor: pointer; 25 | } 26 | ` 27 | 28 | const Dropdown = styled.div` 29 | position: absolute; 30 | top: 34px; 31 | padding-top: 40px; 32 | width: calc(100% - 40px); 33 | background-color: ${({ theme }) => theme.bg1}; 34 | border: 1px solid rgba(0, 0, 0, 0.15); 35 | padding: 10px 10px; 36 | border-radius: 8px; 37 | width: calc(100% - 20px); 38 | font-weight: 500; 39 | font-size: 1rem; 40 | color: black; 41 | :hover { 42 | cursor: pointer; 43 | } 44 | ` 45 | 46 | const ArrowStyled = styled(Arrow)` 47 | height: 20px; 48 | width: 20px; 49 | margin-left: 6px; 50 | ` 51 | 52 | const DropdownSelect = ({ options, active, setActive, color }) => { 53 | const [showDropdown, toggleDropdown] = useState(false) 54 | 55 | return ( 56 | 57 | toggleDropdown(!showDropdown)} justify="center"> 58 | {active} 59 | 60 | 61 | 62 | 63 | {showDropdown && ( 64 | 65 | 66 | {Object.keys(options).map((key, index) => { 67 | let option = options[key] 68 | return ( 69 | option !== active && ( 70 | { 72 | toggleDropdown(!showDropdown) 73 | setActive(option) 74 | }} 75 | key={index} 76 | > 77 | {option} 78 | 79 | ) 80 | ) 81 | })} 82 | 83 | 84 | )} 85 | 86 | ) 87 | } 88 | 89 | export default DropdownSelect 90 | -------------------------------------------------------------------------------- /src/components/Emoji/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Emoji = (props) => ( 4 | 10 | {props.symbol} 11 | 12 | ) 13 | 14 | export default Emoji 15 | -------------------------------------------------------------------------------- /src/components/Footer/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Flex } from 'rebass' 3 | 4 | import Link from '../Link' 5 | 6 | const links = [ 7 | { url: 'https://uniswap.io', text: 'About' }, 8 | { url: 'https://docs.uniswap.io/', text: 'Docs' }, 9 | { url: 'https://github.com/Uniswap/uniswap-info', text: 'Code' }, 10 | ] 11 | 12 | const FooterLink = ({ children, ...rest }) => ( 13 | 14 | {children} 15 | 16 | ) 17 | 18 | const Footer = () => ( 19 | 20 | {links.map((link, index) => ( 21 | 22 | {link.text} 23 | 24 | ))} 25 | 26 | ) 27 | 28 | export default Footer 29 | -------------------------------------------------------------------------------- /src/components/FormattedName/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import styled from 'styled-components' 3 | import { Tooltip } from '../QuestionHelper' 4 | 5 | const TextWrapper = styled.div` 6 | position: relative; 7 | margin-left: ${({ margin }) => margin && '4px'}; 8 | color: ${({ theme, link }) => (link ? theme.blue : theme.text1)}; 9 | font-size: ${({ fontSize }) => fontSize ?? 'inherit'}; 10 | 11 | :hover { 12 | cursor: pointer; 13 | } 14 | 15 | @media screen and (max-width: 600px) { 16 | font-size: ${({ adjustSize }) => adjustSize && '12px'}; 17 | } 18 | ` 19 | 20 | const FormattedName = ({ text, maxCharacters, margin = false, adjustSize = false, fontSize, link, ...rest }) => { 21 | const [showHover, setShowHover] = useState(false) 22 | 23 | if (!text) { 24 | return '' 25 | } 26 | 27 | if (text.length > maxCharacters) { 28 | return ( 29 | 30 | setShowHover(true)} 32 | onMouseLeave={() => setShowHover(false)} 33 | margin={margin} 34 | adjustSize={adjustSize} 35 | link={link} 36 | fontSize={fontSize} 37 | {...rest} 38 | > 39 | {' ' + text.slice(0, maxCharacters - 1) + '...'} 40 | 41 | 42 | ) 43 | } 44 | 45 | return ( 46 | 47 | {text} 48 | 49 | ) 50 | } 51 | 52 | export default FormattedName 53 | -------------------------------------------------------------------------------- /src/components/GlobalChart/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useMemo, useEffect, useRef } from 'react' 2 | import { ResponsiveContainer } from 'recharts' 3 | import { timeframeOptions } from '../../constants' 4 | import { useGlobalChartData, useGlobalData } from '../../contexts/GlobalData' 5 | import { useMedia } from 'react-use' 6 | import DropdownSelect from '../DropdownSelect' 7 | import TradingViewChart, { CHART_TYPES } from '../TradingviewChart' 8 | import { RowFixed } from '../Row' 9 | import { OptionButton } from '../ButtonStyled' 10 | import { getTimeframe } from '../../utils' 11 | import { TYPE } from '../../Theme' 12 | 13 | const CHART_VIEW = { 14 | VOLUME: 'Volume', 15 | LIQUIDITY: 'Liquidity', 16 | } 17 | 18 | const VOLUME_WINDOW = { 19 | WEEKLY: 'WEEKLY', 20 | DAYS: 'DAYS', 21 | } 22 | const GlobalChart = ({ display }) => { 23 | // chart options 24 | const [chartView, setChartView] = useState(display === 'volume' ? CHART_VIEW.VOLUME : CHART_VIEW.LIQUIDITY) 25 | 26 | // time window and window size for chart 27 | const timeWindow = timeframeOptions.ALL_TIME 28 | const [volumeWindow, setVolumeWindow] = useState(VOLUME_WINDOW.DAYS) 29 | 30 | // global historical data 31 | const [dailyData, weeklyData] = useGlobalChartData() 32 | const { totalLiquidityUSD, oneDayVolumeUSD, volumeChangeUSD, liquidityChangeUSD, oneWeekVolume, weeklyVolumeChange } = 33 | useGlobalData() 34 | 35 | // based on window, get starttim 36 | let utcStartTime = getTimeframe(timeWindow) 37 | 38 | const chartDataFiltered = useMemo(() => { 39 | let currentData = volumeWindow === VOLUME_WINDOW.DAYS ? dailyData : weeklyData 40 | return ( 41 | currentData && 42 | Object.keys(currentData) 43 | ?.map((key) => { 44 | let item = currentData[key] 45 | if (item.date > utcStartTime) { 46 | return item 47 | } else { 48 | return true 49 | } 50 | }) 51 | .filter((item) => { 52 | return !!item 53 | }) 54 | ) 55 | }, [dailyData, utcStartTime, volumeWindow, weeklyData]) 56 | const below800 = useMedia('(max-width: 800px)') 57 | 58 | // update the width on a window resize 59 | const ref = useRef() 60 | const isClient = typeof window === 'object' 61 | const [width, setWidth] = useState(ref?.current?.container?.clientWidth) 62 | useEffect(() => { 63 | if (!isClient) { 64 | return false 65 | } 66 | function handleResize() { 67 | setWidth(ref?.current?.container?.clientWidth ?? width) 68 | } 69 | window.addEventListener('resize', handleResize) 70 | return () => window.removeEventListener('resize', handleResize) 71 | }, [isClient, width]) // Empty array ensures that effect is only run on mount and unmount 72 | 73 | return chartDataFiltered ? ( 74 | <> 75 | {below800 && ( 76 | 77 | )} 78 | 79 | {chartDataFiltered && chartView === CHART_VIEW.LIQUIDITY && ( 80 | 81 | 90 | 91 | )} 92 | {chartDataFiltered && chartView === CHART_VIEW.VOLUME && ( 93 | 94 | 104 | 105 | )} 106 | {display === 'volume' && ( 107 | 115 | setVolumeWindow(VOLUME_WINDOW.DAYS)} 118 | > 119 | D 120 | 121 | setVolumeWindow(VOLUME_WINDOW.WEEKLY)} 125 | > 126 | W 127 | 128 | 129 | )} 130 | 131 | ) : ( 132 | '' 133 | ) 134 | } 135 | 136 | export default GlobalChart 137 | -------------------------------------------------------------------------------- /src/components/GlobalStats/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import styled from 'styled-components' 3 | import { RowFixed, RowBetween } from '../Row' 4 | import { useMedia } from 'react-use' 5 | import { useGlobalData, useEthPrice } from '../../contexts/GlobalData' 6 | import { formattedNum, localNumber } from '../../utils' 7 | 8 | import UniPrice from '../UniPrice' 9 | import { TYPE } from '../../Theme' 10 | 11 | const Header = styled.div` 12 | width: 100%; 13 | position: sticky; 14 | top: 0; 15 | ` 16 | 17 | const Medium = styled.span` 18 | font-weight: 500; 19 | ` 20 | 21 | export default function GlobalStats() { 22 | const below1295 = useMedia('(max-width: 1295px)') 23 | const below1180 = useMedia('(max-width: 1180px)') 24 | const below1024 = useMedia('(max-width: 1024px)') 25 | const below400 = useMedia('(max-width: 400px)') 26 | const below816 = useMedia('(max-width: 816px)') 27 | 28 | const [showPriceCard, setShowPriceCard] = useState(false) 29 | 30 | const { oneDayVolumeUSD, oneDayTxns, pairCount } = useGlobalData() 31 | const [ethPrice] = useEthPrice() 32 | const formattedEthPrice = ethPrice ? formattedNum(ethPrice, true) : '-' 33 | const oneDayFees = oneDayVolumeUSD ? formattedNum(oneDayVolumeUSD * 0.003, true) : '' 34 | 35 | return ( 36 |
37 | 38 | 39 | {!below400 && ( 40 | { 43 | setShowPriceCard(true) 44 | }} 45 | onMouseLeave={() => { 46 | setShowPriceCard(false) 47 | }} 48 | style={{ position: 'relative' }} 49 | > 50 | ETH Price: {formattedEthPrice} 51 | {showPriceCard && } 52 | 53 | )} 54 | 55 | {!below1180 && ( 56 | 57 | Transactions (24H): {localNumber(oneDayTxns)} 58 | 59 | )} 60 | {!below1024 && ( 61 | 62 | Pairs: {localNumber(pairCount)} 63 | 64 | )} 65 | {!below1295 && ( 66 | 67 | Fees (24H): {oneDayFees}  68 | 69 | )} 70 | 71 | 72 |
73 | ) 74 | } 75 | -------------------------------------------------------------------------------- /src/components/HoverText/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useState } from 'react' 2 | import styled from 'styled-components' 3 | import Popover, { PopoverProps } from '../Popover' 4 | 5 | const Wrapper = styled.span` 6 | display: flex; 7 | justify-content: center; 8 | align-items: center; 9 | ` 10 | 11 | const TooltipContainer = styled.div` 12 | width: 228px; 13 | padding: 0.6rem 1rem; 14 | line-height: 150%; 15 | font-weight: 400; 16 | ` 17 | 18 | interface TooltipProps extends Omit { 19 | text: string 20 | } 21 | 22 | export function Tooltip({ text, ...rest }: TooltipProps) { 23 | return {text}} {...rest} /> 24 | } 25 | 26 | export default function HoverText({ text, children }: { text: string; children: any }) { 27 | const [show, setShow] = useState(false) 28 | const open = useCallback(() => setShow(true), [setShow]) 29 | const close = useCallback(() => setShow(false), [setShow]) 30 | 31 | return ( 32 | 33 | 34 | 35 | {children} 36 | 37 | 38 | 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /src/components/LPList/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import { useMedia } from 'react-use' 3 | import dayjs from 'dayjs' 4 | import LocalLoader from '../LocalLoader' 5 | import utc from 'dayjs/plugin/utc' 6 | import { Box, Flex } from 'rebass' 7 | import styled from 'styled-components' 8 | 9 | import { CustomLink } from '../Link' 10 | import { Divider } from '..' 11 | import { withRouter } from 'react-router-dom' 12 | import { formattedNum } from '../../utils' 13 | import { TYPE } from '../../Theme' 14 | import DoubleTokenLogo from '../DoubleLogo' 15 | import { RowFixed } from '../Row' 16 | 17 | dayjs.extend(utc) 18 | 19 | const PageButtons = styled.div` 20 | width: 100%; 21 | display: flex; 22 | justify-content: center; 23 | margin-top: 2em; 24 | margin-bottom: 0.5em; 25 | ` 26 | 27 | const Arrow = styled.div` 28 | color: ${({ theme }) => theme.primary1}; 29 | opacity: ${(props) => (props.faded ? 0.3 : 1)}; 30 | padding: 0 20px; 31 | user-select: none; 32 | :hover { 33 | cursor: pointer; 34 | } 35 | ` 36 | 37 | const List = styled(Box)` 38 | -webkit-overflow-scrolling: touch; 39 | ` 40 | 41 | const DashGrid = styled.div` 42 | display: grid; 43 | grid-gap: 1em; 44 | grid-template-columns: 10px 1.5fr 1fr 1fr; 45 | grid-template-areas: 'number name pair value'; 46 | padding: 0 4px; 47 | 48 | > * { 49 | justify-content: flex-end; 50 | } 51 | 52 | @media screen and (max-width: 1080px) { 53 | grid-template-columns: 10px 1.5fr 1fr 1fr; 54 | grid-template-areas: 'number name pair value'; 55 | } 56 | 57 | @media screen and (max-width: 600px) { 58 | grid-template-columns: 1fr 1fr 1fr; 59 | grid-template-areas: 'name pair value'; 60 | } 61 | ` 62 | 63 | const ListWrapper = styled.div`` 64 | 65 | const DataText = styled(Flex)` 66 | align-items: center; 67 | text-align: center; 68 | color: ${({ theme }) => theme.text1}; 69 | & > * { 70 | font-size: 14px; 71 | } 72 | 73 | @media screen and (max-width: 600px) { 74 | font-size: 13px; 75 | } 76 | ` 77 | 78 | function LPList({ lps, disbaleLinks, maxItems = 10 }) { 79 | const below600 = useMedia('(max-width: 600px)') 80 | const below800 = useMedia('(max-width: 800px)') 81 | 82 | // pagination 83 | const [page, setPage] = useState(1) 84 | const [maxPage, setMaxPage] = useState(1) 85 | const ITEMS_PER_PAGE = maxItems 86 | 87 | useEffect(() => { 88 | setMaxPage(1) // edit this to do modular 89 | setPage(1) 90 | }, [lps]) 91 | 92 | useEffect(() => { 93 | if (lps) { 94 | let extraPages = 1 95 | if (Object.keys(lps).length % ITEMS_PER_PAGE === 0) { 96 | extraPages = 0 97 | } 98 | setMaxPage(Math.floor(Object.keys(lps).length / ITEMS_PER_PAGE) + extraPages) 99 | } 100 | }, [ITEMS_PER_PAGE, lps]) 101 | 102 | const ListItem = ({ lp, index }) => { 103 | return ( 104 | 105 | {!below600 && ( 106 | 107 | {index} 108 | 109 | )} 110 | 111 | 112 | {below800 ? lp.user.id.slice(0, 4) + '...' + lp.user.id.slice(38, 42) : lp.user.id} 113 | 114 | 115 | 116 | {/* {!below1080 && ( 117 | 118 | {lp.type} 119 | 120 | )} */} 121 | 122 | 123 | 124 | 125 | {!below600 && } 126 | {lp.pairName} 127 | 128 | 129 | 130 | {formattedNum(lp.usd, true)} 131 | 132 | ) 133 | } 134 | 135 | const lpList = 136 | lps && 137 | lps.slice(ITEMS_PER_PAGE * (page - 1), page * ITEMS_PER_PAGE).map((lp, index) => { 138 | return ( 139 |
140 | 141 | 142 |
143 | ) 144 | }) 145 | 146 | return ( 147 | 148 | 149 | {!below600 && ( 150 | 151 | # 152 | 153 | )} 154 | 155 | Account 156 | 157 | {/* {!below1080 && ( 158 | 159 | Type 160 | 161 | )} */} 162 | 163 | Pair 164 | 165 | 166 | Value 167 | 168 | 169 | 170 | {!lpList ? : lpList} 171 | 172 |
setPage(page === 1 ? page : page - 1)}> 173 | 174 |
175 | {'Page ' + page + ' of ' + maxPage} 176 |
setPage(page === maxPage ? page : page + 1)}> 177 | 178 |
179 |
180 |
181 | ) 182 | } 183 | 184 | export default withRouter(LPList) 185 | -------------------------------------------------------------------------------- /src/components/Link/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link as RebassLink } from 'rebass' 3 | import { Link as RouterLink } from 'react-router-dom' 4 | import PropTypes from 'prop-types' 5 | import styled from 'styled-components' 6 | import { lighten, darken } from 'polished' 7 | 8 | const WrappedLink = ({ external, children, ...rest }) => ( 9 | 15 | {children} 16 | 17 | ) 18 | 19 | WrappedLink.propTypes = { 20 | external: PropTypes.bool, 21 | } 22 | 23 | const Link = styled(WrappedLink)` 24 | color: ${({ color, theme }) => (color ? color : theme.link)}; 25 | ` 26 | 27 | export default Link 28 | 29 | export const CustomLink = styled(RouterLink)` 30 | text-decoration: none; 31 | font-size: 14px; 32 | font-weight: 500; 33 | color: ${({ color, theme }) => (color ? color : theme.link)}; 34 | 35 | &:visited { 36 | color: ${({ color, theme }) => (color ? lighten(0.1, color) : lighten(0.1, theme.link))}; 37 | } 38 | 39 | &:hover { 40 | cursor: pointer; 41 | text-decoration: none; 42 | underline: none; 43 | color: ${({ color, theme }) => (color ? darken(0.1, color) : darken(0.1, theme.link))}; 44 | } 45 | ` 46 | 47 | export const BasicLink = styled(RouterLink)` 48 | text-decoration: none; 49 | color: inherit; 50 | &:hover { 51 | cursor: pointer; 52 | text-decoration: none; 53 | underline: none; 54 | } 55 | ` 56 | -------------------------------------------------------------------------------- /src/components/LocalLoader/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled, { css, keyframes } from 'styled-components' 3 | import { useDarkModeManager } from '../../contexts/LocalStorage' 4 | 5 | const pulse = keyframes` 6 | 0% { transform: scale(1); } 7 | 60% { transform: scale(1.1); } 8 | 100% { transform: scale(1); } 9 | ` 10 | 11 | const Wrapper = styled.div` 12 | pointer-events: none; 13 | display: flex; 14 | align-items: center; 15 | justify-content: center; 16 | height: 100%; 17 | width: 100%; 18 | 19 | ${(props) => 20 | props.fill && !props.height 21 | ? css` 22 | height: 100vh; 23 | ` 24 | : css` 25 | height: 180px; 26 | `} 27 | ` 28 | 29 | const AnimatedImg = styled.div` 30 | animation: ${pulse} 800ms linear infinite; 31 | & > * { 32 | width: 72px; 33 | } 34 | ` 35 | 36 | const LocalLoader = ({ fill }) => { 37 | const [darkMode] = useDarkModeManager() 38 | 39 | return ( 40 | 41 | 42 | loading-icon 43 | 44 | 45 | ) 46 | } 47 | 48 | export default LocalLoader 49 | -------------------------------------------------------------------------------- /src/components/Meta.tsx: -------------------------------------------------------------------------------- 1 | import { Helmet } from 'react-helmet' 2 | import { withRouter, RouteComponentProps } from 'react-router-dom' 3 | import React from 'react' 4 | 5 | const WEBSITE_HOST_URL = 'https://v2.info.uniswap.org' 6 | 7 | const Meta = ({ location }: RouteComponentProps): JSX.Element => { 8 | return ( 9 | 10 | 11 | 12 | ) 13 | } 14 | 15 | export default withRouter(Meta) 16 | -------------------------------------------------------------------------------- /src/components/PairReturnsChart/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import styled from 'styled-components' 3 | import { XAxis, YAxis, ResponsiveContainer, Tooltip, LineChart, Line, CartesianGrid } from 'recharts' 4 | import { AutoRow, RowBetween } from '../Row' 5 | 6 | import { toK, toNiceDate, toNiceDateYear, formattedNum, getTimeframe } from '../../utils' 7 | import { OptionButton } from '../ButtonStyled' 8 | import { useMedia } from 'react-use' 9 | import { timeframeOptions } from '../../constants' 10 | import DropdownSelect from '../DropdownSelect' 11 | import { useUserPositionChart } from '../../contexts/User' 12 | import { useTimeframe } from '../../contexts/Application' 13 | import LocalLoader from '../LocalLoader' 14 | import { useColor } from '../../hooks' 15 | import { useDarkModeManager } from '../../contexts/LocalStorage' 16 | 17 | const ChartWrapper = styled.div` 18 | max-height: 420px; 19 | 20 | @media screen and (max-width: 600px) { 21 | min-height: 200px; 22 | } 23 | ` 24 | 25 | const OptionsRow = styled.div` 26 | display: flex; 27 | flex-direction: row; 28 | width: 100%; 29 | margin-bottom: 40px; 30 | ` 31 | 32 | const CHART_VIEW = { 33 | VALUE: 'Value', 34 | FEES: 'Fees', 35 | } 36 | 37 | const PairReturnsChart = ({ account, position }) => { 38 | let data = useUserPositionChart(position, account) 39 | 40 | const [timeWindow, setTimeWindow] = useTimeframe() 41 | 42 | const below600 = useMedia('(max-width: 600px)') 43 | 44 | const color = useColor(position?.pair.token0.id) 45 | 46 | const [chartView, setChartView] = useState(CHART_VIEW.VALUE) 47 | 48 | // based on window, get starttime 49 | let utcStartTime = getTimeframe(timeWindow) 50 | data = data?.filter((entry) => entry.date >= utcStartTime) 51 | 52 | const aspect = below600 ? 60 / 42 : 60 / 16 53 | 54 | const [darkMode] = useDarkModeManager() 55 | const textColor = darkMode ? 'white' : 'black' 56 | 57 | return ( 58 | 59 | {below600 ? ( 60 | 61 |
62 | 63 | 64 | ) : ( 65 | 66 | 67 | setChartView(CHART_VIEW.VALUE)}> 68 | Liquidity 69 | 70 | setChartView(CHART_VIEW.FEES)}> 71 | Fees 72 | 73 | 74 | 75 | setTimeWindow(timeframeOptions.WEEK)} 78 | > 79 | 1W 80 | 81 | setTimeWindow(timeframeOptions.MONTH)} 84 | > 85 | 1M 86 | 87 | setTimeWindow(timeframeOptions.ALL_TIME)} 90 | > 91 | All 92 | 93 | 94 | 95 | )} 96 | 97 | {data ? ( 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | toNiceDate(tick)} 112 | dataKey="date" 113 | tick={{ fill: textColor }} 114 | type={'number'} 115 | domain={['dataMin', 'dataMax']} 116 | /> 117 | '$' + toK(tick)} 121 | axisLine={false} 122 | tickLine={false} 123 | interval="preserveStartEnd" 124 | minTickGap={0} 125 | yAxisId={0} 126 | tick={{ fill: textColor }} 127 | /> 128 | formattedNum(val, true)} 131 | labelFormatter={(label) => toNiceDateYear(label)} 132 | labelStyle={{ paddingTop: 4 }} 133 | contentStyle={{ 134 | padding: '10px 14px', 135 | borderRadius: 10, 136 | borderColor: color, 137 | color: 'black', 138 | }} 139 | wrapperStyle={{ top: -70, left: -10 }} 140 | /> 141 | 142 | 149 | 150 | ) : ( 151 | 152 | )} 153 | 154 | 155 | ) 156 | } 157 | 158 | export default PairReturnsChart 159 | -------------------------------------------------------------------------------- /src/components/Panel/index.js: -------------------------------------------------------------------------------- 1 | import { Box as RebassBox } from 'rebass' 2 | import styled, { css } from 'styled-components' 3 | 4 | const panelPseudo = css` 5 | :after { 6 | content: ''; 7 | position: absolute; 8 | left: 0; 9 | right: 0; 10 | height: 10px; 11 | } 12 | 13 | @media only screen and (min-width: 40em) { 14 | :after { 15 | content: unset; 16 | } 17 | } 18 | ` 19 | 20 | const Panel = styled(RebassBox)` 21 | position: relative; 22 | background-color: ${({ theme }) => theme.advancedBG}; 23 | padding: 1.25rem; 24 | width: 100%; 25 | height: 100%; 26 | display: flex; 27 | flex-direction: column; 28 | justify-content: flex-start; 29 | border-radius: 8px; 30 | border: 1px solid ${({ theme }) => theme.bg3}; 31 | box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.05); /* box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.01), 0px 16px 24px rgba(0, 0, 0, 0.01), 0px 24px 32px rgba(0, 0, 0, 0.01); */ 32 | :hover { 33 | cursor: ${({ hover }) => hover && 'pointer'}; 34 | border: ${({ hover, theme }) => hover && '1px solid' + theme.bg5}; 35 | } 36 | 37 | ${(props) => props.background && `background-color: ${props.theme.advancedBG};`} 38 | 39 | ${(props) => (props.area ? `grid-area: ${props.area};` : null)} 40 | 41 | ${(props) => 42 | props.grouped && 43 | css` 44 | @media only screen and (min-width: 40em) { 45 | &:first-of-type { 46 | border-radius: 20px 20px 0 0; 47 | } 48 | &:last-of-type { 49 | border-radius: 0 0 20px 20px; 50 | } 51 | } 52 | `} 53 | 54 | ${(props) => 55 | props.rounded && 56 | css` 57 | border-radius: 8px; 58 | @media only screen and (min-width: 40em) { 59 | border-radius: 10px; 60 | } 61 | `}; 62 | 63 | ${(props) => !props.last && panelPseudo} 64 | ` 65 | 66 | export default Panel 67 | 68 | // const Panel = styled.div` 69 | // width: 100%; 70 | // height: 100%; 71 | // display: flex; 72 | // flex-direction: column; 73 | // justify-content: flex-start; 74 | // border-radius: 12px; 75 | // background-color: ${({ theme }) => theme.advancedBG}; 76 | // padding: 1.25rem; 77 | // box-sizing: border-box; 78 | // box-shadow: 0 1.1px 2.8px -9px rgba(0, 0, 0, 0.008), 0 2.7px 6.7px -9px rgba(0, 0, 0, 0.012), 79 | // 0 5px 12.6px -9px rgba(0, 0, 0, 0.015), 0 8.9px 22.6px -9px rgba(0, 0, 0, 0.018), 80 | // 0 16.7px 42.2px -9px rgba(0, 0, 0, 0.022), 0 40px 101px -9px rgba(0, 0, 0, 0.03); 81 | // ` 82 | -------------------------------------------------------------------------------- /src/components/PinnedData/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { withRouter } from 'react-router-dom' 3 | import styled from 'styled-components' 4 | import { RowBetween, RowFixed } from '../Row' 5 | import { AutoColumn } from '../Column' 6 | import { TYPE } from '../../Theme' 7 | import { useSavedTokens, useSavedPairs } from '../../contexts/LocalStorage' 8 | import { Hover } from '..' 9 | import TokenLogo from '../TokenLogo' 10 | import AccountSearch from '../AccountSearch' 11 | import { Bookmark, ChevronRight, X } from 'react-feather' 12 | import { ButtonFaded } from '../ButtonStyled' 13 | import FormattedName from '../FormattedName' 14 | 15 | const RightColumn = styled.div` 16 | position: fixed; 17 | right: 0; 18 | top: 0px; 19 | height: 100vh; 20 | width: ${({ open }) => (open ? '160px' : '23px')}; 21 | padding: 1.25rem; 22 | border-left: ${({ theme, open }) => '1px solid' + theme.bg3}; 23 | background-color: ${({ theme }) => theme.bg1}; 24 | z-index: 9999; 25 | overflow: auto; 26 | :hover { 27 | cursor: pointer; 28 | } 29 | ` 30 | 31 | const SavedButton = styled(RowBetween)` 32 | padding-bottom: ${({ open }) => open && '20px'}; 33 | border-bottom: ${({ theme, open }) => open && '1px solid' + theme.bg3}; 34 | margin-bottom: ${({ open }) => open && '1.25rem'}; 35 | 36 | :hover { 37 | cursor: pointer; 38 | } 39 | ` 40 | 41 | const ScrollableDiv = styled(AutoColumn)` 42 | overflow: auto; 43 | padding-bottom: 60px; 44 | ` 45 | 46 | const StyledIcon = styled.div` 47 | color: ${({ theme }) => theme.text2}; 48 | ` 49 | 50 | function PinnedData({ history, open, setSavedOpen }) { 51 | const [savedPairs, , removePair] = useSavedPairs() 52 | const [savedTokens, , removeToken] = useSavedTokens() 53 | 54 | return !open ? ( 55 | setSavedOpen(true)}> 56 | 57 | 58 | 59 | 60 | 61 | 62 | ) : ( 63 | 64 | setSavedOpen(false)} open={open}> 65 | 66 | 67 | 68 | 69 | Saved 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | Pinned Pairs 79 | {Object.keys(savedPairs).filter((key) => { 80 | return !!savedPairs[key] 81 | }).length > 0 ? ( 82 | Object.keys(savedPairs) 83 | .filter((address) => { 84 | return !!savedPairs[address] 85 | }) 86 | .map((address) => { 87 | const pair = savedPairs[address] 88 | return ( 89 | 90 | history.push('/pair/' + address)}> 91 | 92 | 93 | 98 | 99 | 100 | 101 | removePair(pair.address)}> 102 | 103 | 104 | 105 | 106 | 107 | ) 108 | }) 109 | ) : ( 110 | Pinned pairs will appear here. 111 | )} 112 | 113 | 114 | Pinned Tokens 115 | {Object.keys(savedTokens).filter((key) => { 116 | return !!savedTokens[key] 117 | }).length > 0 ? ( 118 | Object.keys(savedTokens) 119 | .filter((address) => { 120 | return !!savedTokens[address] 121 | }) 122 | .map((address) => { 123 | const token = savedTokens[address] 124 | return ( 125 | 126 | history.push('/token/' + address)}> 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | removeToken(address)}> 135 | 136 | 137 | 138 | 139 | 140 | ) 141 | }) 142 | ) : ( 143 | Pinned tokens will appear here. 144 | )} 145 | 146 | 147 | 148 | ) 149 | } 150 | 151 | export default withRouter(PinnedData) 152 | -------------------------------------------------------------------------------- /src/components/Popover/index.tsx: -------------------------------------------------------------------------------- 1 | import { Placement } from '@popperjs/core' 2 | import { transparentize } from 'polished' 3 | import React, { useState } from 'react' 4 | import { usePopper } from 'react-popper' 5 | import styled from 'styled-components' 6 | import Portal from '@reach/portal' 7 | import useInterval from '../../hooks' 8 | 9 | const PopoverContainer = styled.div<{ show: boolean }>` 10 | z-index: 9999; 11 | 12 | visibility: ${(props) => (props.show ? 'visible' : 'hidden')}; 13 | opacity: ${(props) => (props.show ? 1 : 0)}; 14 | transition: visibility 150ms linear, opacity 150ms linear; 15 | 16 | background: ${({ theme }) => theme.bg2}; 17 | border: 1px solid ${({ theme }) => theme.bg3}; 18 | box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.9, theme.shadow1)}; 19 | color: ${({ theme }) => theme.text2}; 20 | border-radius: 8px; 21 | ` 22 | 23 | const ReferenceElement = styled.div` 24 | display: inline-block; 25 | ` 26 | 27 | export interface PopoverProps { 28 | content: React.ReactNode 29 | show: boolean 30 | children: React.ReactNode 31 | placement?: Placement 32 | } 33 | 34 | export default function Popover({ content, show, children, placement = 'auto' }: PopoverProps) { 35 | const [referenceElement, setReferenceElement] = useState(null) 36 | const [popperElement, setPopperElement] = useState(null) 37 | const [arrowElement] = useState(null) 38 | const { styles, update, attributes } = usePopper(referenceElement, popperElement, { 39 | placement, 40 | strategy: 'fixed', 41 | modifiers: [ 42 | { name: 'offset', options: { offset: [8, 8] } }, 43 | { name: 'arrow', options: { element: arrowElement } }, 44 | ], 45 | }) 46 | 47 | useInterval(update, show ? 100 : null) 48 | 49 | return ( 50 | <> 51 | {children} 52 | 53 | 54 | {content} 55 | {/* */} 61 | 62 | 63 | 64 | ) 65 | } 66 | -------------------------------------------------------------------------------- /src/components/QuestionHelper/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useState } from 'react' 2 | import { HelpCircle as Question } from 'react-feather' 3 | import styled from 'styled-components' 4 | import Popover, { PopoverProps } from '../Popover' 5 | 6 | const QuestionWrapper = styled.div` 7 | display: flex; 8 | align-items: center; 9 | justify-content: center; 10 | padding: 0.2rem; 11 | border: none; 12 | background: none; 13 | outline: none; 14 | cursor: default; 15 | border-radius: 36px; 16 | background-color: ${({ theme }) => theme.bg2}; 17 | color: ${({ theme }) => theme.text2}; 18 | 19 | :hover, 20 | :focus { 21 | opacity: 0.7; 22 | } 23 | ` 24 | 25 | const TooltipContainer = styled.div` 26 | width: 228px; 27 | padding: 0.6rem 1rem; 28 | line-height: 150%; 29 | font-weight: 400; 30 | ` 31 | 32 | interface TooltipProps extends Omit { 33 | text: string 34 | } 35 | 36 | export function Tooltip({ text, ...rest }: TooltipProps) { 37 | return {text}} {...rest} /> 38 | } 39 | 40 | export default function QuestionHelper({ text, disabled }: { text: string; disabled?: boolean }) { 41 | const [show, setShow] = useState(false) 42 | 43 | const open = useCallback(() => setShow(true), [setShow]) 44 | const close = useCallback(() => setShow(false), [setShow]) 45 | 46 | return ( 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /src/components/Row/index.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { Box } from 'rebass/styled-components' 3 | 4 | const Row = styled(Box)` 5 | width: 100%; 6 | display: flex; 7 | padding: 0; 8 | align-items: center; 9 | align-items: ${({ align }) => align && align}; 10 | padding: ${({ padding }) => padding}; 11 | border: ${({ border }) => border}; 12 | border-radius: ${({ borderRadius }) => borderRadius}; 13 | justify-content: ${({ justify }) => justify}; 14 | ` 15 | 16 | export const RowBetween = styled(Row)` 17 | justify-content: space-between; 18 | ` 19 | 20 | export const RowFlat = styled.div` 21 | display: flex; 22 | align-items: flex-end; 23 | ` 24 | 25 | export const AutoRow = styled(Row)` 26 | flex-wrap: ${({ wrap }) => wrap ?? 'nowrap'}; 27 | margin: -${({ gap }) => gap}; 28 | & > * { 29 | margin: ${({ gap }) => gap} !important; 30 | } 31 | ` 32 | 33 | export const RowFixed = styled(Row)` 34 | width: fit-content; 35 | ` 36 | 37 | export default Row 38 | -------------------------------------------------------------------------------- /src/components/Select/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import PropTypes from 'prop-types' 4 | import { default as ReactSelect } from 'react-select' 5 | import { isMobile } from 'react-device-detect' 6 | 7 | import Popout from './popout' 8 | 9 | import { customStyles, customStylesMobile, customStylesTime } from './styles' 10 | 11 | const MenuLabel = styled.div` 12 | display: flex; 13 | align-items: center; 14 | width: 100%; 15 | justify-content: flex-start; 16 | flex-direction: row; 17 | ` 18 | 19 | const LabelBox = styled.div`` 20 | 21 | const LogoBox = styled.div` 22 | width: 30px; 23 | display: flex; 24 | justify-content: center; 25 | align-items: flex-start; 26 | margin-right: 8px; 27 | ` 28 | 29 | const CustomMenu = styled.div` 30 | background-color: white; 31 | position: absolute; 32 | border-radius: 16px; 33 | box-shadow: 0 4px 8px 0 rgba(47, 128, 237, 0.1), 0 0 0 0.5px var(--c-zircon); 34 | overflow: hidden; 35 | padding: 0; 36 | width: 180px; 37 | z-index: 5; 38 | margin-top: 10px; 39 | padding-top: 36px; 40 | ` 41 | 42 | const FixedToggle = styled.div` 43 | position: absolute; 44 | height: 24px; 45 | z-index: 10; 46 | background-color: white; 47 | width: 100%; 48 | top: 8px; 49 | display: flex; 50 | align-items: center; 51 | padding-left: 12px; 52 | & > input { 53 | margin-right: 8px; 54 | } 55 | ` 56 | 57 | let addressStart = new RegExp('^0x') 58 | function customFilter(option, searchText) { 59 | const isAddress = addressStart.test(searchText) 60 | if (isAddress) { 61 | return option.data.tokenAddress.toString().toLowerCase().includes(searchText.toString().toLowerCase()) 62 | } 63 | return option.data.label.toString().toLowerCase().includes(searchText.toString().toLowerCase()) 64 | } 65 | 66 | const Select = ({ options, onChange, setCapEth, capEth, tokenSelect = false, placeholder, ...rest }) => { 67 | return tokenSelect ? ( 68 | ( 76 | 77 | {option.logo} 78 | {option.label} 79 | 80 | )} 81 | styles={isMobile ? customStylesMobile : customStyles} 82 | {...rest} 83 | components={{ 84 | DropdownIndicator: () => ( 85 | 86 | 🔎 87 | 88 | ), 89 | Menu: ({ children, innerRef, innerProps }) => { 90 | return ( 91 | 92 | 93 | { 98 | setCapEth(!capEth) 99 | }} 100 | /> 101 | Hide Low Liquidity 102 | 103 | {children} 104 | 105 | ) 106 | }, 107 | }} 108 | /> 109 | ) : ( 110 | 118 | ) 119 | } 120 | 121 | Select.propTypes = { 122 | options: PropTypes.array.isRequired, 123 | onChange: PropTypes.func, 124 | } 125 | 126 | export default Select 127 | 128 | export { Popout } 129 | -------------------------------------------------------------------------------- /src/components/Select/popout.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Button } from 'rebass' 3 | import styled from 'styled-components' 4 | 5 | import Select from 'react-select' 6 | 7 | const selectStyles = { 8 | control: (styles) => ({ 9 | ...styles, 10 | padding: '1rem', 11 | border: 'none', 12 | backgroundColor: 'transparent', 13 | borderBottom: '1px solid #e1e1e1', 14 | boxShadow: 'none', 15 | borderRadius: 0, 16 | ':hover': { 17 | borderColor: '#e1e1e1', 18 | }, 19 | }), 20 | valueContainer: (styles) => ({ 21 | ...styles, 22 | padding: 0, 23 | }), 24 | menu: () => null, 25 | } 26 | 27 | export default class Popout extends Component { 28 | state = { isOpen: false, value: undefined } 29 | toggleOpen = () => { 30 | this.setState((state) => ({ isOpen: !state.isOpen })) 31 | } 32 | 33 | onSelectChange = (value) => { 34 | this.toggleOpen() 35 | this.setState({ value }) 36 | } 37 | 38 | render() { 39 | const { isOpen, value } = this.state 40 | return ( 41 | 56 | {value ? value.label : 'Select...'} 57 | 58 | } 59 | > 60 |