aboutsummaryrefslogtreecommitdiffstats
path: root/packages/website
diff options
context:
space:
mode:
Diffstat (limited to 'packages/website')
-rw-r--r--packages/website/package.json5
-rw-r--r--packages/website/public/images/lock_icon.svg3
-rw-r--r--packages/website/public/images/setup_account_icon.svg3
-rw-r--r--packages/website/public/images/team/alexbrowne.pngbin0 -> 146699 bytes
-rw-r--r--packages/website/public/images/zrx_ecosystem.svg339
-rw-r--r--packages/website/public/index.html13
-rw-r--r--packages/website/ts/blockchain.ts31
-rw-r--r--packages/website/ts/blockchain_watcher.ts7
-rw-r--r--packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx2
-rw-r--r--packages/website/ts/components/dialogs/send_dialog.tsx1
-rw-r--r--packages/website/ts/components/inputs/allowance_toggle.tsx4
-rw-r--r--packages/website/ts/components/inputs/balance_bounded_input.tsx35
-rw-r--r--packages/website/ts/components/inputs/eth_amount_input.tsx4
-rw-r--r--packages/website/ts/components/inputs/token_amount_input.tsx2
-rw-r--r--packages/website/ts/components/onboarding/add_eth_onboarding_step.tsx50
-rw-r--r--packages/website/ts/components/onboarding/congrats_onboarding_step.tsx2
-rw-r--r--packages/website/ts/components/onboarding/install_wallet_onboarding_step.tsx7
-rw-r--r--packages/website/ts/components/onboarding/intro_onboarding_step.tsx11
-rw-r--r--packages/website/ts/components/onboarding/onboarding_card.tsx33
-rw-r--r--packages/website/ts/components/onboarding/onboarding_flow.tsx74
-rw-r--r--packages/website/ts/components/onboarding/portal_onboarding_flow.tsx115
-rw-r--r--packages/website/ts/components/onboarding/set_allowances_onboarding_step.tsx3
-rw-r--r--packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx2
-rw-r--r--packages/website/ts/components/onboarding/wrap_eth_onboarding_step.tsx132
-rw-r--r--packages/website/ts/components/portal/drawer_menu.tsx3
-rw-r--r--packages/website/ts/components/portal/portal.tsx66
-rw-r--r--packages/website/ts/components/portal/section.tsx4
-rw-r--r--packages/website/ts/components/relayer_index/relayer_grid_tile.tsx6
-rw-r--r--packages/website/ts/components/relayer_index/relayer_top_tokens.tsx63
-rw-r--r--packages/website/ts/components/top_bar/provider_display.tsx212
-rw-r--r--packages/website/ts/components/top_bar/provider_picker.tsx79
-rw-r--r--packages/website/ts/components/top_bar/top_bar.tsx4
-rw-r--r--packages/website/ts/components/ui/account_connection.tsx40
-rw-r--r--packages/website/ts/components/ui/animation.tsx8
-rw-r--r--packages/website/ts/components/ui/button.tsx2
-rw-r--r--packages/website/ts/components/ui/circle.tsx16
-rw-r--r--packages/website/ts/components/ui/container.tsx3
-rw-r--r--packages/website/ts/components/ui/drop_down.tsx52
-rw-r--r--packages/website/ts/components/ui/identicon.tsx6
-rw-r--r--packages/website/ts/components/ui/overlay.tsx3
-rw-r--r--packages/website/ts/components/ui/simple_menu.tsx88
-rw-r--r--packages/website/ts/components/ui/text.tsx10
-rw-r--r--packages/website/ts/components/wallet/body_overlay.tsx146
-rw-r--r--packages/website/ts/components/wallet/null_token_row.tsx41
-rw-r--r--packages/website/ts/components/wallet/placeholder.tsx25
-rw-r--r--packages/website/ts/components/wallet/standard_icon_row.tsx44
-rw-r--r--packages/website/ts/components/wallet/wallet.tsx433
-rw-r--r--packages/website/ts/components/wallet/wallet_disconnected_item.tsx100
-rw-r--r--packages/website/ts/components/wallet/wrap_ether_item.tsx7
-rw-r--r--packages/website/ts/containers/portal_onboarding_flow.ts4
-rw-r--r--packages/website/ts/containers/subproviders_documentation.ts3
-rw-r--r--packages/website/ts/index.tsx2
-rw-r--r--packages/website/ts/pages/about/about.tsx29
-rw-r--r--packages/website/ts/pages/jobs/list/list_item.tsx6
-rw-r--r--packages/website/ts/redux/reducer.ts8
-rw-r--r--packages/website/ts/redux/store.ts4
-rw-r--r--packages/website/ts/style/colors.ts4
-rw-r--r--packages/website/ts/style/media.ts14
-rw-r--r--packages/website/ts/types.ts29
-rw-r--r--packages/website/ts/utils/analytics.ts11
-rw-r--r--packages/website/ts/utils/configs.ts2
-rw-r--r--packages/website/ts/utils/constants.ts7
-rw-r--r--packages/website/ts/utils/utils.ts55
-rw-r--r--packages/website/ts/utils/wallet_item_styles.ts9
64 files changed, 1386 insertions, 1140 deletions
diff --git a/packages/website/package.json b/packages/website/package.json
index c7b689356..a5768a60b 100644
--- a/packages/website/package.json
+++ b/packages/website/package.json
@@ -38,6 +38,7 @@
"lodash": "^4.17.4",
"material-ui": "^0.17.1",
"moment": "2.21.0",
+ "numeral": "^2.0.6",
"polished": "^1.9.2",
"query-string": "^6.0.0",
"react": "15.6.1",
@@ -57,8 +58,7 @@
"styled-components": "^3.3.0",
"thenby": "^1.2.3",
"truffle-contract": "2.0.1",
- "web3": "^0.20.0",
- "web3-provider-engine": "^14.0.4",
+ "web3-provider-engine": "14.0.6",
"whatwg-fetch": "^2.0.3",
"xml-js": "^1.6.4"
},
@@ -71,6 +71,7 @@
"@types/lodash": "4.14.104",
"@types/material-ui": "0.18.0",
"@types/node": "^8.0.53",
+ "@types/numeral": "^0.0.22",
"@types/query-string": "^5.1.0",
"@types/react": "16.3.13",
"@types/react-copy-to-clipboard": "^4.2.0",
diff --git a/packages/website/public/images/lock_icon.svg b/packages/website/public/images/lock_icon.svg
new file mode 100644
index 000000000..83e8191a1
--- /dev/null
+++ b/packages/website/public/images/lock_icon.svg
@@ -0,0 +1,3 @@
+<svg width="26" height="32" viewBox="0 0 26 32" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M6.47619 0C3.79509 0 1.60489 2.21216 1.60489 4.92014V6.33135C0.717479 6.33135 -3.60127e-08 7.05602 -3.60127e-08 7.95232V14.379C-3.60127e-08 15.2753 0.717479 16 1.60489 16H11.3475C12.2349 16 12.9524 15.2753 12.9524 14.379V7.95232C12.9524 7.05602 12.2349 6.33135 11.3475 6.33135V4.92014C11.3475 2.21216 9.1573 0 6.47619 0ZM9.6482 6.33135H3.30418V4.92014C3.30418 3.16567 4.72026 1.71633 6.47619 1.71633C8.23213 1.71633 9.6482 3.16567 9.6482 4.92014V6.33135Z" transform="scale(2)" fill="black"/>
+</svg>
diff --git a/packages/website/public/images/setup_account_icon.svg b/packages/website/public/images/setup_account_icon.svg
new file mode 100644
index 000000000..eaa5b2fd6
--- /dev/null
+++ b/packages/website/public/images/setup_account_icon.svg
@@ -0,0 +1,3 @@
+<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M16.5 9C16.5 13.1421 13.1421 16.5 9 16.5C4.85791 16.5 1.5 13.1421 1.5 9C1.5 4.85791 4.85791 1.5 9 1.5C13.1421 1.5 16.5 4.85791 16.5 9ZM18 9C18 13.9706 13.9707 18 9 18C4.0293 18 0 13.9706 0 9C0 4.02942 4.0293 0 9 0C13.9707 0 18 4.02942 18 9ZM9.21973 5.7196C9.5127 5.42664 9.9873 5.42664 10.2803 5.7196L13.0806 8.51953C13.373 8.8125 13.373 9.28735 13.0806 9.5802L10.2803 12.3802C9.9873 12.6731 9.5127 12.6731 9.21973 12.3802C8.92676 12.0873 8.92676 11.6124 9.21973 11.3196L10.7393 9.7998H4.75C4.33594 9.7998 4 9.46399 4 9.0498C4 8.63562 4.33594 8.2998 4.75 8.2998H10.7393L9.21973 6.78015C8.92676 6.4873 8.92676 6.01245 9.21973 5.7196Z" transform="scale(2)" fill="#3289F1"/>
+</svg>
diff --git a/packages/website/public/images/team/alexbrowne.png b/packages/website/public/images/team/alexbrowne.png
new file mode 100644
index 000000000..76a61913e
--- /dev/null
+++ b/packages/website/public/images/team/alexbrowne.png
Binary files differ
diff --git a/packages/website/public/images/zrx_ecosystem.svg b/packages/website/public/images/zrx_ecosystem.svg
index f8aed4637..3b4fa0a6e 100644
--- a/packages/website/public/images/zrx_ecosystem.svg
+++ b/packages/website/public/images/zrx_ecosystem.svg
@@ -1,253 +1,158 @@
-<svg width="352" height="162" viewBox="0 0 352 162" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<svg width="267" height="171" viewBox="0 0 267 171" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect x="1" y="1" width="153" height="169" rx="9" transform="translate(112)" stroke="#3289F1" stroke-width="2"/>
<g filter="url(#filter0_d)">
-<rect width="96.165" height="52.7562" rx="4" transform="translate(4 101)" fill="#545454"/>
-<rect width="29.5892" height="52.7562" rx="4" transform="translate(4 101)" fill="#808080"/>
-<g opacity="0.4">
-<path d="M0 3.28518V14.7525C0 14.8229 0.0570457 14.88 0.127415 14.88H12.5223C12.6631 14.88 12.7772 14.7659 12.7772 14.6251V14.532C12.7772 14.4427 12.7048 14.3703 12.6154 14.3703H12.5811C12.5107 14.3703 12.4537 14.3132 12.4537 14.2429V14.1963C12.4537 14.1517 12.4175 14.1155 12.3728 14.1155C12.3282 14.1155 12.292 14.0793 12.292 14.0346V13.6401C12.292 13.5508 12.2196 13.4784 12.1302 13.4784C12.0409 13.4784 11.9685 13.406 11.9685 13.3166V13.177C11.9685 13.1323 11.9323 13.0961 11.8876 13.0961C11.843 13.0961 11.8068 13.0599 11.8068 13.0153V12.8756C11.8068 12.7863 11.7343 12.7139 11.645 12.7139C11.5557 12.7139 11.4833 12.6415 11.4833 12.5522V11.9837C11.4833 11.8944 11.4109 11.822 11.3215 11.822C11.2322 11.822 11.1598 11.7496 11.1598 11.6603V11.2658C11.1598 11.2211 11.1236 11.1849 11.0789 11.1849C11.0343 11.1849 10.9981 11.1487 10.9981 11.104V10.837C10.9981 10.7477 10.9257 10.6753 10.8363 10.6753C10.747 10.6753 10.6746 10.6028 10.6746 10.5135V10.0137C10.6746 9.74576 10.4574 9.52852 10.1894 9.52852H10.1501C9.90384 9.52852 9.70418 9.32886 9.70418 9.08257V8.96009C9.70418 8.78144 9.55936 8.63661 9.38071 8.63661C9.20206 8.63661 9.05724 8.49179 9.05724 8.31314V8.25437C9.05724 8.04326 8.8861 7.87213 8.67499 7.87213H8.65289C8.42958 7.87213 8.24855 7.6911 8.24855 7.46778V7.17134C8.24855 6.78431 7.9348 6.47056 7.54777 6.47056H7.43987C6.99325 6.47056 6.63119 6.1085 6.63119 5.66188V4.1252C6.63119 3.66127 6.2551 3.28518 5.79117 3.28518H5.62752C5.25397 3.28518 4.95115 2.98237 4.95115 2.60882C4.95115 2.23528 4.64834 1.93246 4.27479 1.93246H3.99287C3.46362 1.93246 3.03458 1.49947 3.03458 0.970218C3.03458 0.436584 2.60198 0 2.06835 0H1.59715C0.715067 0 0 0.715067 0 1.59715V3.28518Z" transform="translate(60.4883 136.168)" fill="#7EBD91" stroke="#CCFFBF" stroke-width="0.381182"/>
-<path d="M0 1.72904V7.76449C0 7.80153 0.030024 7.83155 0.0670605 7.83155H12.6431C12.7171 7.83155 12.7772 7.77151 12.7772 7.69743C12.7772 7.62336 12.7171 7.56331 12.6431 7.56331H12.5208C12.4837 7.56331 12.4537 7.53329 12.4537 7.49625C12.4537 7.45921 12.4237 7.42919 12.3866 7.42919H12.3728C12.3282 7.42919 12.292 7.39298 12.292 7.34832V7.25562C12.292 7.1663 12.2196 7.09389 12.1302 7.09389H12.0691C12.0135 7.09389 11.9685 7.04885 11.9685 6.9933V6.97357C11.9685 6.92891 11.9323 6.89271 11.8876 6.89271C11.843 6.89271 11.8068 6.8565 11.8068 6.81184V6.79212C11.8068 6.73656 11.7617 6.69152 11.7062 6.69152H11.645C11.5557 6.69152 11.4833 6.61911 11.4833 6.52979V6.38384C11.4833 6.29451 11.4109 6.2221 11.3215 6.2221C11.2322 6.2221 11.1598 6.14969 11.1598 6.06036V5.96767C11.1598 5.923 11.1236 5.8868 11.0789 5.8868C11.0343 5.8868 10.9981 5.85059 10.9981 5.80593V5.75268C10.9981 5.6786 10.938 5.61856 10.8639 5.61856H10.8363C10.747 5.61856 10.6746 5.54614 10.6746 5.45682V5.31678C10.6746 5.15012 10.5395 5.01501 10.3728 5.01501H9.93889C9.80927 5.01501 9.70418 4.90993 9.70418 4.7803C9.70418 4.65067 9.5991 4.54559 9.46947 4.54559H9.25842C9.14731 4.54559 9.05724 4.45551 9.05724 4.34441C9.05724 4.2333 8.96716 4.14322 8.85605 4.14322H8.61739C8.41369 4.14322 8.24855 3.97809 8.24855 3.77439C8.24855 3.57069 8.08342 3.40556 7.87972 3.40556H7.43987C6.99325 3.40556 6.63119 3.0435 6.63119 2.59688V2.5673C6.63119 2.10434 6.25589 1.72904 5.79293 1.72904H5.30713C5.11053 1.72904 4.95115 1.56967 4.95115 1.37306C4.95115 1.17646 4.79178 1.01708 4.59517 1.01708H3.54312C3.26226 1.01708 3.03458 0.789403 3.03458 0.508542C3.03458 0.227682 2.8069 0 2.52604 0H1.59715C0.715067 0 0 0.715067 0 1.59715V1.72904Z" transform="translate(86.043 143.219) scale(-1 1)" fill="#C99957" stroke="#F0D165" stroke-width="0.381182"/>
+<rect width="38" height="44" rx="5" transform="translate(125 12)" fill="#F2F2F2"/>
+<path d="M0 5C0 2.23858 2.23858 0 5 0H33C35.7614 0 38 2.23858 38 5V22H0V5Z" transform="translate(125 12)" fill="#5C5E9A"/>
+<rect width="16" height="3" rx="1" transform="translate(130 38)" fill="#C4C4C4"/>
+<rect width="13.8182" height="2.55072" rx="1" transform="translate(129.605 44)" fill="#89BFFF"/>
</g>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(35.6055 107.762)" fill="#636363"/>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(35.6055 111.148)" fill="#636363"/>
-<rect width="9.41476" height="2.02908" rx="1.01454" transform="translate(35.6055 114.527)" fill="#636363"/>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(35.6055 117.91)" fill="#636363"/>
-<rect width="8.06979" height="2.02908" rx="1.01454" transform="translate(35.6055 121.289)" fill="#636363"/>
-<rect width="9.41476" height="2.02908" rx="1.01454" transform="translate(35.6055 124.672)" fill="#636363"/>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(35.6055 128.055)" fill="#636363"/>
-<rect width="6.72483" height="2.02908" rx="1.01454" transform="translate(35.6055 131.438)" fill="#636363"/>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(35.6055 134.816)" fill="#636363"/>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(35.6055 138.199)" fill="#636363"/>
-<rect width="8.06979" height="2.02908" rx="1.01454" transform="translate(35.6055 141.582)" fill="#636363"/>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(35.6055 144.965)" fill="#636363"/>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(35.6055 148.348)" fill="#636363"/>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(35.6055 151.727)" fill="#636363"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(46.3672 107.762)" fill="#636363"/>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(35.6055 104.383)" fill="#636363"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(46.3672 104.383)" fill="#636363"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(46.3672 111.148)" fill="#636363"/>
-<rect width="12.1047" height="2.02908" rx="1.01454" transform="translate(46.3672 114.527)" fill="#636363"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(46.3672 117.91)" fill="#636363"/>
-<rect width="10.3754" height="2.02908" rx="1.01454" transform="translate(46.3672 121.289)" fill="#636363"/>
-<rect width="12.1047" height="2.02908" rx="1.01454" transform="translate(46.3672 124.672)" fill="#636363"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(46.3672 128.055)" fill="#636363"/>
-<rect width="8.64621" height="2.02908" rx="1.01454" transform="translate(46.3672 131.438)" fill="#636363"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(46.3672 134.816)" fill="#636363"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(46.3672 138.199)" fill="#636363"/>
-<rect width="10.3754" height="2.02908" rx="1.01454" transform="translate(46.3672 141.582)" fill="#636363"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(46.3672 144.965)" fill="#636363"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(46.3672 148.348)" fill="#636363"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(46.3672 151.727)" fill="#636363"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(89.4062 107.762)" fill="#636363"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(89.4062 104.383)" fill="#636363"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(89.4062 111.148)" fill="#636363"/>
-<rect width="9.41476" height="2.02908" rx="1.01454" transform="translate(89.4062 114.527)" fill="#636363"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(89.4062 117.91)" fill="#636363"/>
-<rect width="8.06979" height="2.02908" rx="1.01454" transform="translate(89.4062 121.289)" fill="#636363"/>
-<rect width="9.41476" height="2.02908" rx="1.01454" transform="translate(89.4062 124.672)" fill="#636363"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(89.4062 128.055)" fill="#636363"/>
-<rect width="6.72483" height="2.02908" rx="1.01454" transform="translate(89.4062 131.438)" fill="#636363"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(89.4062 134.816)" fill="#636363"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(89.4062 138.199)" fill="#636363"/>
-<rect width="8.06979" height="2.02908" rx="1.01454" transform="translate(89.4062 141.582)" fill="#636363"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(89.4062 144.965)" fill="#636363"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(89.4062 148.348)" fill="#636363"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(89.4062 151.727)" fill="#636363"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(63.8516 114.527)" fill="#5F7866"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(64.5234 113.176)" fill="#5F7866"/>
-<rect width="0.672483" height="1.35272" rx="0.336241" transform="translate(65.8672 113.176)" fill="#5F7866"/>
-<rect width="0.672483" height="1.35272" rx="0.336241" transform="translate(67.2148 112.496)" fill="#5F7866"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(68.5586 113.176)" fill="#5F7866"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(69.9023 114.527)" fill="#7D6A4F"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(71.25 114.527)" fill="#7D6A4F"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(72.5938 113.848)" fill="#5F7866"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(73.9414 113.848)" fill="#5F7866"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(75.2812 113.176)" fill="#5F7866"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(76.6289 112.496)" fill="#5F7866"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(77.9727 113.176)" fill="#7D6A4F"/>
-<rect width="0.672483" height="2.70545" rx="0.336241" transform="translate(79.3203 113.848)" fill="#7D6A4F"/>
+<g filter="url(#filter1_d)">
+<rect width="38" height="44" rx="5" transform="translate(171 12)" fill="#F2F2F2"/>
+<path d="M0 5C0 2.23858 2.23858 0 5 0H33C35.7614 0 38 2.23858 38 5V22H0V5Z" transform="translate(171 12)" fill="#737592"/>
+<rect width="16" height="3" rx="1" transform="translate(176 38)" fill="#C4C4C4"/>
+<rect width="13.8182" height="2.55072" rx="1" transform="translate(175.605 44)" fill="#89BFFF"/>
</g>
-<g style="mix-blend-mode:luminosity" filter="url(#filter1_d)">
-<g style="mix-blend-mode:difference" filter="url(#filter2_dd)">
-<rect width="96.165" height="52.7562" rx="4" transform="translate(347.164 101) scale(-1 1)" fill="#545454"/>
-<path d="M0 4C0 1.79086 1.79086 0 4 0H29.5892V52.7562H4C1.79086 52.7562 0 50.9653 0 48.7562V4Z" transform="translate(347.164 101) scale(-1 1)" fill="#808080"/>
-<g opacity="0.4">
-<path d="M0 3.28518V14.7525C0 14.8229 0.0570457 14.88 0.127415 14.88H12.5223C12.6631 14.88 12.7772 14.7659 12.7772 14.6251V14.532C12.7772 14.4427 12.7048 14.3703 12.6154 14.3703H12.5811C12.5107 14.3703 12.4537 14.3132 12.4537 14.2429V14.1963C12.4537 14.1517 12.4175 14.1155 12.3728 14.1155C12.3282 14.1155 12.292 14.0793 12.292 14.0346V13.6401C12.292 13.5508 12.2196 13.4784 12.1302 13.4784C12.0409 13.4784 11.9685 13.406 11.9685 13.3166V13.177C11.9685 13.1323 11.9323 13.0961 11.8876 13.0961C11.843 13.0961 11.8068 13.0599 11.8068 13.0153V12.8756C11.8068 12.7863 11.7343 12.7139 11.645 12.7139C11.5557 12.7139 11.4833 12.6415 11.4833 12.5522V11.9837C11.4833 11.8944 11.4109 11.822 11.3215 11.822C11.2322 11.822 11.1598 11.7496 11.1598 11.6603V11.2658C11.1598 11.2211 11.1236 11.1849 11.0789 11.1849C11.0343 11.1849 10.9981 11.1487 10.9981 11.104V10.837C10.9981 10.7477 10.9257 10.6753 10.8363 10.6753C10.747 10.6753 10.6746 10.6028 10.6746 10.5135V10.0137C10.6746 9.74576 10.4574 9.52852 10.1894 9.52852H10.1501C9.90384 9.52852 9.70418 9.32886 9.70418 9.08257V8.96009C9.70418 8.78144 9.55936 8.63661 9.38071 8.63661C9.20206 8.63661 9.05724 8.49179 9.05724 8.31314V8.25437C9.05724 8.04326 8.8861 7.87213 8.67499 7.87213H8.65289C8.42958 7.87213 8.24855 7.6911 8.24855 7.46778V7.17134C8.24855 6.78431 7.9348 6.47056 7.54777 6.47056H7.43987C6.99325 6.47056 6.63119 6.1085 6.63119 5.66188V4.1252C6.63119 3.66127 6.2551 3.28518 5.79117 3.28518H5.62752C5.25397 3.28518 4.95115 2.98237 4.95115 2.60882C4.95115 2.23528 4.64834 1.93246 4.27479 1.93246H3.99287C3.46362 1.93246 3.03458 1.49947 3.03458 0.970218C3.03458 0.436584 2.60198 0 2.06835 0H1.59715C0.715067 0 0 0.715067 0 1.59715V3.28518Z" transform="translate(290.676 136.168) scale(-1 1)" fill="#7EBD91" stroke="#CCFFBF" stroke-width="0.381182"/>
-<path d="M0 1.72904V7.76449C0 7.80153 0.030024 7.83155 0.0670605 7.83155H12.6431C12.7171 7.83155 12.7772 7.77151 12.7772 7.69743C12.7772 7.62336 12.7171 7.56331 12.6431 7.56331H12.5208C12.4837 7.56331 12.4537 7.53329 12.4537 7.49625C12.4537 7.45921 12.4237 7.42919 12.3866 7.42919H12.3728C12.3282 7.42919 12.292 7.39298 12.292 7.34832V7.25562C12.292 7.1663 12.2196 7.09389 12.1302 7.09389H12.0691C12.0135 7.09389 11.9685 7.04885 11.9685 6.9933V6.97357C11.9685 6.92891 11.9323 6.89271 11.8876 6.89271C11.843 6.89271 11.8068 6.8565 11.8068 6.81184V6.79212C11.8068 6.73656 11.7617 6.69152 11.7062 6.69152H11.645C11.5557 6.69152 11.4833 6.61911 11.4833 6.52979V6.38384C11.4833 6.29451 11.4109 6.2221 11.3215 6.2221C11.2322 6.2221 11.1598 6.14969 11.1598 6.06036V5.96767C11.1598 5.923 11.1236 5.8868 11.0789 5.8868C11.0343 5.8868 10.9981 5.85059 10.9981 5.80593V5.75268C10.9981 5.6786 10.938 5.61856 10.8639 5.61856H10.8363C10.747 5.61856 10.6746 5.54614 10.6746 5.45682V5.31678C10.6746 5.15012 10.5395 5.01501 10.3728 5.01501H9.93889C9.80927 5.01501 9.70418 4.90993 9.70418 4.7803C9.70418 4.65067 9.5991 4.54559 9.46947 4.54559H9.25842C9.14731 4.54559 9.05724 4.45551 9.05724 4.34441C9.05724 4.2333 8.96716 4.14322 8.85605 4.14322H8.61739C8.41369 4.14322 8.24855 3.97809 8.24855 3.77439C8.24855 3.57069 8.08342 3.40556 7.87972 3.40556H7.43987C6.99325 3.40556 6.63119 3.0435 6.63119 2.59688V2.5673C6.63119 2.10434 6.25589 1.72904 5.79293 1.72904H5.30713C5.11053 1.72904 4.95115 1.56967 4.95115 1.37306C4.95115 1.17646 4.79178 1.01708 4.59517 1.01708H3.54312C3.26226 1.01708 3.03458 0.789403 3.03458 0.508542C3.03458 0.227682 2.8069 0 2.52604 0H1.59715C0.715067 0 0 0.715067 0 1.59715V1.72904Z" transform="translate(265.121 143.219)" fill="#C99957" stroke="#F0D165" stroke-width="0.381182"/>
+<g filter="url(#filter2_d)">
+<rect width="38" height="44" rx="5" transform="translate(217 12)" fill="#F2F2F2"/>
+<path d="M0 5C0 2.23858 2.23858 0 5 0H33C35.7614 0 38 2.23858 38 5V22H0V5Z" transform="translate(217 12)" fill="#434456"/>
+<rect width="16" height="3" rx="1" transform="translate(222 38)" fill="#C4C4C4"/>
+<rect width="13.8182" height="2.55072" rx="1" transform="translate(221.605 44)" fill="#89BFFF"/>
</g>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(315.559 107.762) scale(-1 1)" fill="#636363"/>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(315.559 111.148) scale(-1 1)" fill="#636363"/>
-<rect width="9.41476" height="2.02908" rx="1.01454" transform="translate(315.559 114.527) scale(-1 1)" fill="#636363"/>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(315.559 117.91) scale(-1 1)" fill="#636363"/>
-<rect width="8.06979" height="2.02908" rx="1.01454" transform="translate(315.559 121.289) scale(-1 1)" fill="#636363"/>
-<rect width="9.41476" height="2.02908" rx="1.01454" transform="translate(315.559 124.672) scale(-1 1)" fill="#636363"/>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(315.559 128.055) scale(-1 1)" fill="#636363"/>
-<rect width="6.72483" height="2.02908" rx="1.01454" transform="translate(315.559 131.438) scale(-1 1)" fill="#636363"/>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(315.559 134.816) scale(-1 1)" fill="#636363"/>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(315.559 138.199) scale(-1 1)" fill="#636363"/>
-<rect width="8.06979" height="2.02908" rx="1.01454" transform="translate(315.559 141.582) scale(-1 1)" fill="#636363"/>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(315.559 144.965) scale(-1 1)" fill="#636363"/>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(315.559 148.348) scale(-1 1)" fill="#636363"/>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(315.559 151.727) scale(-1 1)" fill="#636363"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(304.797 107.762) scale(-1 1)" fill="#636363"/>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(315.559 104.383) scale(-1 1)" fill="#636363"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(304.797 104.383) scale(-1 1)" fill="#636363"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(304.797 111.148) scale(-1 1)" fill="#636363"/>
-<rect width="12.1047" height="2.02908" rx="1.01454" transform="translate(304.797 114.527) scale(-1 1)" fill="#636363"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(304.797 117.91) scale(-1 1)" fill="#636363"/>
-<rect width="10.3754" height="2.02908" rx="1.01454" transform="translate(304.797 121.289) scale(-1 1)" fill="#636363"/>
-<rect width="12.1047" height="2.02908" rx="1.01454" transform="translate(304.797 124.672) scale(-1 1)" fill="#636363"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(304.797 128.055) scale(-1 1)" fill="#636363"/>
-<rect width="8.64621" height="2.02908" rx="1.01454" transform="translate(304.797 131.438) scale(-1 1)" fill="#636363"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(304.797 134.816) scale(-1 1)" fill="#636363"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(304.797 138.199) scale(-1 1)" fill="#636363"/>
-<rect width="10.3754" height="2.02908" rx="1.01454" transform="translate(304.797 141.582) scale(-1 1)" fill="#636363"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(304.797 144.965) scale(-1 1)" fill="#636363"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(304.797 148.348) scale(-1 1)" fill="#636363"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(304.797 151.727) scale(-1 1)" fill="#636363"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(261.758 107.762) scale(-1 1)" fill="#636363"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(261.758 104.383) scale(-1 1)" fill="#636363"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(261.758 111.148) scale(-1 1)" fill="#636363"/>
-<rect width="9.41476" height="2.02908" rx="1.01454" transform="translate(261.758 114.527) scale(-1 1)" fill="#636363"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(261.758 117.91) scale(-1 1)" fill="#636363"/>
-<rect width="8.06979" height="2.02908" rx="1.01454" transform="translate(261.758 121.289) scale(-1 1)" fill="#636363"/>
-<rect width="9.41476" height="2.02908" rx="1.01454" transform="translate(261.758 124.672) scale(-1 1)" fill="#636363"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(261.758 128.055) scale(-1 1)" fill="#636363"/>
-<rect width="6.72483" height="2.02908" rx="1.01454" transform="translate(261.758 131.438) scale(-1 1)" fill="#636363"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(261.758 134.816) scale(-1 1)" fill="#636363"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(261.758 138.199) scale(-1 1)" fill="#636363"/>
-<rect width="8.06979" height="2.02908" rx="1.01454" transform="translate(261.758 141.582) scale(-1 1)" fill="#636363"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(261.758 144.965) scale(-1 1)" fill="#636363"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(261.758 148.348) scale(-1 1)" fill="#636363"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(261.758 151.727) scale(-1 1)" fill="#636363"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(287.312 114.527) scale(-1 1)" fill="#5F7866"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(286.641 113.176) scale(-1 1)" fill="#5F7866"/>
-<rect width="0.672483" height="1.35272" rx="0.336241" transform="translate(285.297 113.176) scale(-1 1)" fill="#5F7866"/>
-<rect width="0.672483" height="1.35272" rx="0.336241" transform="translate(283.949 112.496) scale(-1 1)" fill="#5F7866"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(282.605 113.176) scale(-1 1)" fill="#5F7866"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(281.262 114.527) scale(-1 1)" fill="#7D6A4F"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(279.914 114.527) scale(-1 1)" fill="#7D6A4F"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(278.57 113.848) scale(-1 1)" fill="#5F7866"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(277.223 113.848) scale(-1 1)" fill="#5F7866"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(275.883 113.176) scale(-1 1)" fill="#5F7866"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(274.535 112.496) scale(-1 1)" fill="#5F7866"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(273.191 113.176) scale(-1 1)" fill="#7D6A4F"/>
-<rect width="0.672483" height="2.70545" rx="0.336241" transform="translate(271.844 113.848) scale(-1 1)" fill="#7D6A4F"/>
+<g filter="url(#filter3_d)">
+<rect width="38" height="44" rx="5" transform="translate(125 63)" fill="#F2F2F2"/>
+<path d="M0 5C0 2.23858 2.23858 0 5 0H33C35.7614 0 38 2.23858 38 5V22H0V5Z" transform="translate(125 63)" fill="#434456"/>
+<rect width="16" height="3" rx="1" transform="translate(130 89)" fill="#C4C4C4"/>
+<rect width="13.8182" height="2.55072" rx="1" transform="translate(129.605 95)" fill="#89BFFF"/>
</g>
+<g filter="url(#filter4_d)">
+<rect width="38" height="44" rx="5" transform="translate(171 63)" fill="#F2F2F2"/>
+<path d="M0 5C0 2.23858 2.23858 0 5 0H33C35.7614 0 38 2.23858 38 5V22H0V5Z" transform="translate(171 63)" fill="#5C5E9A"/>
+<rect width="16" height="3" rx="1" transform="translate(176 89)" fill="#C4C4C4"/>
+<rect width="13.8182" height="2.55072" rx="1" transform="translate(175.605 95)" fill="#89BFFF"/>
</g>
-<g style="mix-blend-mode:hard-light" filter="url(#filter3_d)">
-<rect width="96.165" height="52.7562" rx="4" transform="translate(129 100.246)" fill="#15095E"/>
-<rect width="29.5892" height="52.7562" rx="4" transform="translate(129 100.246)" fill="#010036"/>
-<g opacity="0.4">
-<path d="M0 3.28518V14.7525C0 14.8229 0.0570457 14.88 0.127415 14.88H12.5223C12.6631 14.88 12.7772 14.7659 12.7772 14.6251V14.532C12.7772 14.4427 12.7048 14.3703 12.6154 14.3703H12.5811C12.5107 14.3703 12.4537 14.3132 12.4537 14.2429V14.1963C12.4537 14.1517 12.4175 14.1155 12.3728 14.1155C12.3282 14.1155 12.292 14.0793 12.292 14.0346V13.6401C12.292 13.5508 12.2196 13.4784 12.1302 13.4784C12.0409 13.4784 11.9685 13.406 11.9685 13.3166V13.177C11.9685 13.1323 11.9323 13.0961 11.8876 13.0961C11.843 13.0961 11.8068 13.0599 11.8068 13.0153V12.8756C11.8068 12.7863 11.7343 12.7139 11.645 12.7139C11.5557 12.7139 11.4833 12.6415 11.4833 12.5522V11.9837C11.4833 11.8944 11.4109 11.822 11.3215 11.822C11.2322 11.822 11.1598 11.7496 11.1598 11.6603V11.2658C11.1598 11.2211 11.1236 11.1849 11.0789 11.1849C11.0343 11.1849 10.9981 11.1487 10.9981 11.104V10.837C10.9981 10.7477 10.9257 10.6753 10.8363 10.6753C10.747 10.6753 10.6746 10.6028 10.6746 10.5135V10.0137C10.6746 9.74576 10.4574 9.52852 10.1894 9.52852H10.1501C9.90384 9.52852 9.70418 9.32886 9.70418 9.08257V8.96009C9.70418 8.78144 9.55936 8.63661 9.38071 8.63661C9.20206 8.63661 9.05724 8.49179 9.05724 8.31314V8.25437C9.05724 8.04326 8.8861 7.87213 8.67499 7.87213H8.65289C8.42958 7.87213 8.24855 7.6911 8.24855 7.46778V7.17134C8.24855 6.78431 7.9348 6.47056 7.54777 6.47056H7.43987C6.99325 6.47056 6.63119 6.1085 6.63119 5.66188V4.1252C6.63119 3.66127 6.2551 3.28518 5.79117 3.28518H5.62752C5.25397 3.28518 4.95115 2.98237 4.95115 2.60882C4.95115 2.23528 4.64834 1.93246 4.27479 1.93246H3.99287C3.46362 1.93246 3.03458 1.49947 3.03458 0.970218C3.03458 0.436584 2.60198 0 2.06835 0H1.59715C0.715067 0 0 0.715067 0 1.59715V3.28518Z" transform="translate(185.488 135.418)" fill="#7EBD91" stroke="#CCFFBF" stroke-width="0.381182"/>
-<path d="M0 1.72904V7.76449C0 7.80153 0.030024 7.83155 0.0670605 7.83155H12.6431C12.7171 7.83155 12.7772 7.77151 12.7772 7.69743C12.7772 7.62336 12.7171 7.56331 12.6431 7.56331H12.5208C12.4837 7.56331 12.4537 7.53329 12.4537 7.49625C12.4537 7.45921 12.4237 7.42919 12.3866 7.42919H12.3728C12.3282 7.42919 12.292 7.39298 12.292 7.34832V7.25562C12.292 7.1663 12.2196 7.09389 12.1302 7.09389H12.0691C12.0135 7.09389 11.9685 7.04885 11.9685 6.9933V6.97357C11.9685 6.92891 11.9323 6.89271 11.8876 6.89271C11.843 6.89271 11.8068 6.8565 11.8068 6.81184V6.79212C11.8068 6.73656 11.7617 6.69152 11.7062 6.69152H11.645C11.5557 6.69152 11.4833 6.61911 11.4833 6.52979V6.38384C11.4833 6.29451 11.4109 6.2221 11.3215 6.2221C11.2322 6.2221 11.1598 6.14969 11.1598 6.06036V5.96767C11.1598 5.923 11.1236 5.8868 11.0789 5.8868C11.0343 5.8868 10.9981 5.85059 10.9981 5.80593V5.75268C10.9981 5.6786 10.938 5.61856 10.8639 5.61856H10.8363C10.747 5.61856 10.6746 5.54614 10.6746 5.45682V5.31678C10.6746 5.15012 10.5395 5.01501 10.3728 5.01501H9.93889C9.80927 5.01501 9.70418 4.90993 9.70418 4.7803C9.70418 4.65067 9.5991 4.54559 9.46947 4.54559H9.25842C9.14731 4.54559 9.05724 4.45551 9.05724 4.34441C9.05724 4.2333 8.96716 4.14322 8.85605 4.14322H8.61739C8.41369 4.14322 8.24855 3.97809 8.24855 3.77439C8.24855 3.57069 8.08342 3.40556 7.87972 3.40556H7.43987C6.99325 3.40556 6.63119 3.0435 6.63119 2.59688V2.5673C6.63119 2.10434 6.25589 1.72904 5.79293 1.72904H5.30713C5.11053 1.72904 4.95115 1.56967 4.95115 1.37306C4.95115 1.17646 4.79178 1.01708 4.59517 1.01708H3.54312C3.26226 1.01708 3.03458 0.789403 3.03458 0.508542C3.03458 0.227682 2.8069 0 2.52604 0H1.59715C0.715067 0 0 0.715067 0 1.59715V1.72904Z" transform="translate(211.043 142.469) scale(-1 1)" fill="#C95E57" stroke="#942C39" stroke-width="0.381182"/>
+<g filter="url(#filter5_d)">
+<rect width="38" height="44" rx="5" transform="translate(217 63)" fill="#F2F2F2"/>
+<path d="M0 5C0 2.23858 2.23858 0 5 0H33C35.7614 0 38 2.23858 38 5V22H0V5Z" transform="translate(217 63)" fill="#4E6B77"/>
+<rect width="16" height="3" rx="1" transform="translate(222 89)" fill="#C4C4C4"/>
+<rect width="13.8182" height="2.55072" rx="1" transform="translate(221.605 95)" fill="#89BFFF"/>
</g>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(160.605 107.008)" fill="#010036"/>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(160.605 110.395)" fill="#010036"/>
-<rect width="9.41476" height="2.02908" rx="1.01454" transform="translate(160.605 113.773)" fill="#010036"/>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(160.605 117.156)" fill="#010036"/>
-<rect width="8.06979" height="2.02908" rx="1.01454" transform="translate(160.605 120.535)" fill="#010036"/>
-<rect width="9.41476" height="2.02908" rx="1.01454" transform="translate(160.605 123.918)" fill="#010036"/>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(160.605 127.301)" fill="#010036"/>
-<rect width="6.72483" height="2.02908" rx="1.01454" transform="translate(160.605 130.684)" fill="#010036"/>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(160.605 134.062)" fill="#010036"/>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(160.605 137.445)" fill="#010036"/>
-<rect width="8.06979" height="2.02908" rx="1.01454" transform="translate(160.605 140.828)" fill="#010036"/>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(160.605 144.211)" fill="#010036"/>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(160.605 147.594)" fill="#010036"/>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(160.605 150.973)" fill="#010036"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(171.367 107.008)" fill="#010036"/>
-<rect width="8.74228" height="2.02908" rx="1.01454" transform="translate(160.605 103.629)" fill="#010036"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(171.367 103.629)" fill="#010036"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(171.367 110.395)" fill="#010036"/>
-<rect width="12.1047" height="2.02908" rx="1.01454" transform="translate(171.367 113.773)" fill="#010036"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(171.367 117.156)" fill="#010036"/>
-<rect width="10.3754" height="2.02908" rx="1.01454" transform="translate(171.367 120.535)" fill="#010036"/>
-<rect width="12.1047" height="2.02908" rx="1.01454" transform="translate(171.367 123.918)" fill="#010036"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(171.367 127.301)" fill="#010036"/>
-<rect width="8.64621" height="2.02908" rx="1.01454" transform="translate(171.367 130.684)" fill="#010036"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(171.367 134.062)" fill="#010036"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(171.367 137.445)" fill="#010036"/>
-<rect width="10.3754" height="2.02908" rx="1.01454" transform="translate(171.367 140.828)" fill="#010036"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(171.367 144.211)" fill="#010036"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(171.367 147.594)" fill="#010036"/>
-<rect width="11.2401" height="2.02908" rx="1.01454" transform="translate(171.367 150.973)" fill="#010036"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(214.406 107.008)" fill="#010036"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(214.406 103.629)" fill="#010036"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(214.406 110.395)" fill="#010036"/>
-<rect width="9.41476" height="2.02908" rx="1.01454" transform="translate(214.406 113.773)" fill="#010036"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(214.406 117.156)" fill="#010036"/>
-<rect width="8.06979" height="2.02908" rx="1.01454" transform="translate(214.406 120.535)" fill="#010036"/>
-<rect width="9.41476" height="2.02908" rx="1.01454" transform="translate(214.406 123.918)" fill="#010036"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(214.406 127.301)" fill="#010036"/>
-<rect width="6.72483" height="2.02908" rx="1.01454" transform="translate(214.406 130.684)" fill="#010036"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(214.406 134.062)" fill="#010036"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(214.406 137.445)" fill="#010036"/>
-<rect width="8.06979" height="2.02908" rx="1.01454" transform="translate(214.406 140.828)" fill="#010036"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(214.406 144.211)" fill="#010036"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(214.406 147.594)" fill="#010036"/>
-<rect width="8.74227" height="2.02908" rx="1.01454" transform="translate(214.406 150.973)" fill="#010036"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(188.852 115.773)" fill="#5F7866"/>
-<path d="M0 0.336241C0 0.15054 0.15054 0 0.336241 0C0.521942 0 0.672483 0.15054 0.672483 0.336241V1.69284C0.672483 1.87854 0.521942 2.02908 0.336241 2.02908C0.15054 2.02908 0 1.87854 0 1.69284V0.336241Z" transform="translate(189.523 114.422)" fill="#5F7866"/>
-<rect width="0.672483" height="1.35272" rx="0.336241" transform="translate(190.867 113.422)" fill="#5F7866"/>
-<rect width="0.672483" height="1.35272" rx="0.336241" transform="translate(192.215 113.742)" fill="#5F7866"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(193.559 112.422)" fill="#5F7866"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(194.902 113.773)" fill="#7D6A4F"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(196.25 113.773)" fill="#7D6A4F"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(197.594 113.094)" fill="#5F7866"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(198.938 111.094)" fill="#5F7866"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(200.281 109.422)" fill="#5F7866"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(201.629 107.742)" fill="#5F7866"/>
-<rect width="0.672483" height="2.02908" rx="0.336241" transform="translate(202.973 108.422)" fill="#7D6A4F"/>
-<rect width="0.672483" height="2.70545" rx="0.336241" transform="translate(204.32 109.094)" fill="#7D6A4F"/>
+<g filter="url(#filter6_d)">
+<rect width="38" height="44" rx="5" transform="translate(125 114)" fill="#F2F2F2"/>
+<path d="M0 5C0 2.23858 2.23858 0 5 0H33C35.7614 0 38 2.23858 38 5V22H0V5Z" transform="translate(125 114)" fill="#4E6B77"/>
+<rect width="16" height="3" rx="1" transform="translate(130 140)" fill="#C4C4C4"/>
+<rect width="13.8182" height="2.55072" rx="1" transform="translate(129.605 146)" fill="#89BFFF"/>
</g>
-<rect width="80" height="48" transform="translate(137)" fill="url(#pattern0)"/>
-<line x1="1" y1="-1" x2="32" y2="-1" transform="translate(176 58) rotate(90)" stroke="#3289F1" stroke-width="2" stroke-linecap="round"/>
-<path d="M30 0H6V118.5H0" transform="translate(302.5 58.5) rotate(90)" stroke="#3289F1" stroke-width="2" stroke-linecap="round"/>
-<path d="M30 117.5H6V0H0" transform="translate(170.5 58.5) rotate(90)" stroke="#3289F1" stroke-width="2" stroke-linecap="round"/>
+<g filter="url(#filter7_d)">
+<rect width="38" height="44" rx="5" transform="translate(171 114)" fill="#F2F2F2"/>
+<path d="M0 5C0 2.23858 2.23858 0 5 0H33C35.7614 0 38 2.23858 38 5V22H0V5Z" transform="translate(171 114)" fill="#434456"/>
+<rect width="16" height="3" rx="1" transform="translate(176 140)" fill="#C4C4C4"/>
+<rect width="13.8182" height="2.55072" rx="1" transform="translate(175.605 146)" fill="#89BFFF"/>
+</g>
+<g filter="url(#filter8_d)">
+<rect width="38" height="44" rx="5" transform="translate(217 114)" fill="#F2F2F2"/>
+<path d="M0 5C0 2.23858 2.23858 0 5 0H33C35.7614 0 38 2.23858 38 5V22H0V5Z" transform="translate(217 114)" fill="#737492"/>
+<rect width="16" height="3" rx="1" transform="translate(222 140)" fill="#C4C4C4"/>
+<rect width="13.8182" height="2.55072" rx="1" transform="translate(221.605 146)" fill="#89BFFF"/>
+</g>
+<rect x="-1" y="-1" width="68" height="76" rx="6" transform="translate(2 50)" fill="#F2F2F2" stroke="#3289F1" stroke-width="2"/>
+<path d="M0 5C0 2.23858 2.23858 0 5 0H59C61.7614 0 64 2.23858 64 5V15H0V5Z" transform="translate(3 51)" fill="#F6F6F6"/>
+<rect width="6" height="4" rx="2" transform="translate(57 70)" fill="#89BFFF"/>
+<rect width="6" height="4" rx="2" transform="translate(57 83)" fill="#89BFFF"/>
+<rect width="6" height="4" rx="2" transform="translate(57 95)" fill="#89BFFF"/>
+<rect width="6" height="4" rx="2" transform="translate(57 107)" fill="#89BFFF"/>
+<rect width="18" height="4" rx="1" transform="translate(18 70)" fill="#E0E0E0"/>
+<rect width="18" height="4" rx="1" transform="translate(18 56)" fill="#E0E0E0"/>
+<rect width="18" height="4" transform="translate(18 83)" fill="#E0E0E0"/>
+<rect width="18" height="4" transform="translate(18 95)" fill="#E0E0E0"/>
+<rect width="18" height="4" transform="translate(18 107)" fill="#E0E0E0"/>
+<circle cx="3" cy="3" r="3" transform="translate(7 70)" fill="#E0E0E0"/>
+<circle cx="3" cy="3" r="3" transform="translate(7 55)" fill="#3289F1"/>
+<circle cx="3" cy="3" r="3" transform="translate(7 82)" fill="#E0E0E0"/>
+<circle cx="3" cy="3" r="3" transform="translate(7 94)" fill="#E0E0E0"/>
+<circle cx="3" cy="3" r="3" transform="translate(7 106)" fill="#E0E0E0"/>
+<path d="M22.7368 20V21.3333C22.7368 22.8 21.6 24 20.2105 24H2.52632C1.12421 24 0 22.8 0 21.3333V2.66667C0 1.2 1.12421 0 2.52632 0H20.2105C21.6 0 22.7368 1.2 22.7368 2.66667V4H11.3684C9.96632 4 8.8421 5.2 8.8421 6.66667V17.3333C8.8421 18.8 9.96632 20 11.3684 20H22.7368ZM11.3684 17.3333H24V6.66667H11.3684V17.3333ZM16.4211 14C15.3726 14 14.5263 13.1067 14.5263 12C14.5263 10.8933 15.3726 10 16.4211 10C17.4695 10 18.3158 10.8933 18.3158 12C18.3158 13.1067 17.4695 14 16.4211 14Z" transform="translate(24 16)" fill="#3289F1"/>
+<line y1="-1" x2="42" y2="-1" transform="translate(70 86)" stroke="#3289F1" stroke-width="2"/>
<defs>
-<filter id="filter0_d" x="0" y="101" width="104.165" height="60.7562" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<filter id="filter0_d" x="123" y="11" width="42" height="48" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 255 0"/>
+<feOffset dy="1"/>
+<feGaussianBlur stdDeviation="1"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
+</filter>
+<filter id="filter1_d" x="169" y="11" width="42" height="48" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 255 0"/>
-<feOffset dy="4"/>
-<feGaussianBlur stdDeviation="2"/>
+<feOffset dy="1"/>
+<feGaussianBlur stdDeviation="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
-<filter id="filter1_d" x="247" y="101" width="104.165" height="60.7562" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<filter id="filter2_d" x="215" y="11" width="42" height="48" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 255 0"/>
-<feOffset dy="4"/>
-<feGaussianBlur stdDeviation="2"/>
+<feOffset dy="1"/>
+<feGaussianBlur stdDeviation="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
-<filter id="filter2_dd" x="247" y="101" width="104.165" height="60.7562" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<filter id="filter3_d" x="123" y="62" width="42" height="48" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 255 0"/>
-<feOffset dy="4"/>
-<feGaussianBlur stdDeviation="2"/>
+<feOffset dy="1"/>
+<feGaussianBlur stdDeviation="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
+</filter>
+<filter id="filter4_d" x="169" y="62" width="42" height="48" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 255 0"/>
-<feOffset dy="4"/>
-<feGaussianBlur stdDeviation="2"/>
+<feOffset dy="1"/>
+<feGaussianBlur stdDeviation="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
-<feBlend mode="normal" in2="effect1_dropShadow" result="effect2_dropShadow"/>
-<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow" result="shape"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
+</filter>
+<filter id="filter5_d" x="215" y="62" width="42" height="48" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 255 0"/>
+<feOffset dy="1"/>
+<feGaussianBlur stdDeviation="1"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
+</filter>
+<filter id="filter6_d" x="123" y="113" width="42" height="48" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 255 0"/>
+<feOffset dy="1"/>
+<feGaussianBlur stdDeviation="1"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
+</filter>
+<filter id="filter7_d" x="169" y="113" width="42" height="48" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 255 0"/>
+<feOffset dy="1"/>
+<feGaussianBlur stdDeviation="1"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
-<filter id="filter3_d" x="125" y="100.246" width="104.165" height="60.7562" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<filter id="filter8_d" x="215" y="113" width="42" height="48" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 255 0"/>
-<feOffset dy="4"/>
-<feGaussianBlur stdDeviation="2"/>
+<feOffset dy="1"/>
+<feGaussianBlur stdDeviation="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
-<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
-<use xlink:href="#image0" transform="scale(0.00142857 0.00235294)"/>
-</pattern>
-<image id="image0" width="700" height="425" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAArwAAAGpCAYAAABvSeX2AAAAAXNSR0IArs4c6QAAQABJREFUeAHsvQeUZVd5pv2deEPFjurcCigBikhCASRA2IyNsQk2wTbBGLANxmHs4f8nrX/NWvYaMx7HZQ94AJtshBEWFtEWmIyFhEAGCYSy1Gp1tzpUd4UbTvrf99y63aVSVXVV1831bun2PfekvfezT937nu98+/vMVERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABPqIgNNHbVVTRWBgCfzXK7f2VN+yLDNvZ2AbpgKbxrdExTwrpZkdyio2FqGpQ445fsWKqWv1bIP5TmzTcWZuLbVCmFjmB5amkQ3VapaE45ZkOMSt25FKZknBNX80tfVHYtsf4mRh0ab3+haV6nbmusCOzThWcBKbqdat5rk2UiqZoe5jaEchcMyroo60fs5U6n6gHHp/GJVnPu1vji2LuvN1NlFP7bJNBXvV00fNsNzzBWNhgVkEXgce8AG1bqEbWD1ObajoWR3s42MVc5wxCwrg6qcW41WqRnZgyLehKLPiMbOZsdCco1XzRzA+lcTSiczi8cBG4ymrhqNW9SrmzuC62eFYdCSy5FjJSmN1y7zQ4nrNbDIzf9izxK+b7xXQjAmbrhctKZVxvdSslBQsK8yYW0pw/Ki5ad3qUcHCIZw3COwwrg3vQNXGikWbHKvZ+rpnbuzZJK47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Y9ybBOi/5C3/jd+bFDTqLSTAyWv07/I/2A8edLzgCd/xznizF38orVa7kdudmpyYmH/onkfmD99z38Lx21859dj5x+cbswsNr1JqhiZoRN1W0wSmw3lwcZclhBmFzb7EvIdUxQfM7oAXyLBAwwOjw7b1bBB0+7Kt7Qu2i+lOBERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABLaMwP8PiHLGLtI5pqYAAAAASUVORK5CYII="/>
</defs>
</svg>
diff --git a/packages/website/public/index.html b/packages/website/public/index.html
index 4c0985c71..060f2c3c2 100644
--- a/packages/website/public/index.html
+++ b/packages/website/public/index.html
@@ -70,7 +70,18 @@
})(document, 'script', 'twitter-wjs');
</script>
<!-- End Twitter SDK -->
-
+ <!-- Hotjar Tracking Code for https://0xproject.com/ -->
+ <script>
+ (function (h, o, t, j, a, r) {
+ h.hj = h.hj || function () { (h.hj.q = h.hj.q || []).push(arguments) };
+ h._hjSettings = { hjid: 935597, hjsv: 6 };
+ a = o.getElementsByTagName('head')[0];
+ r = o.createElement('script'); r.async = 1;
+ r.src = t + h._hjSettings.hjid + j + h._hjSettings.hjsv;
+ a.appendChild(r);
+ })(window, document, 'https://static.hotjar.com/c/hotjar-', '.js?sv=');
+ </script>
+ <!-- End Hotjar Tracking Code -->
<!-- Main -->
<script type="text/javascript" crossorigin="anonymous" src="/bundle.js" charset="utf-8"></script>
</body>
diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts
index d18c34c32..cc2afa28a 100644
--- a/packages/website/ts/blockchain.ts
+++ b/packages/website/ts/blockchain.ts
@@ -12,10 +12,10 @@ import {
import { isValidOrderHash, signOrderHashAsync } from '@0xproject/order-utils';
import { EtherscanLinkSuffixes, utils as sharedUtils } from '@0xproject/react-shared';
import {
- InjectedWeb3Subprovider,
ledgerEthereumBrowserClientFactoryAsync,
LedgerSubprovider,
RedundantSubprovider,
+ SignerSubprovider,
Subprovider,
} from '@0xproject/subproviders';
import {
@@ -46,6 +46,7 @@ import {
Fill,
InjectedProviderObservable,
InjectedProviderUpdate,
+ InjectedWeb3,
Order as PortalOrder,
Providers,
ProviderType,
@@ -59,7 +60,6 @@ import { configs } from 'ts/utils/configs';
import { constants } from 'ts/utils/constants';
import { errorReporter } from 'ts/utils/error_reporter';
import { utils } from 'ts/utils/utils';
-import Web3 = require('web3');
import ProviderEngine = require('web3-provider-engine');
import FilterSubprovider = require('web3-provider-engine/subproviders/filters');
import RpcSubprovider = require('web3-provider-engine/subproviders/rpc');
@@ -73,6 +73,8 @@ const providerToName: { [provider: string]: string } = {
[Providers.Metamask]: constants.PROVIDER_NAME_METAMASK,
[Providers.Parity]: constants.PROVIDER_NAME_PARITY_SIGNER,
[Providers.Mist]: constants.PROVIDER_NAME_MIST,
+ [Providers.Toshi]: constants.PROVIDER_NAME_TOSHI,
+ [Providers.Cipher]: constants.PROVIDER_NAME_CIPHER,
};
export class Blockchain {
@@ -95,8 +97,19 @@ export class Blockchain {
}
return providerNameIfExists;
}
- private static _getInjectedWeb3(): any {
- return (window as any).web3;
+ private static _getInjectedWeb3(): InjectedWeb3 {
+ const injectedWeb3IfExists = (window as any).web3;
+ // Our core assumptions about the injected web3 object is that it has the following
+ // properties and methods.
+ if (
+ _.isUndefined(injectedWeb3IfExists) ||
+ _.isUndefined(injectedWeb3IfExists.version) ||
+ _.isUndefined(injectedWeb3IfExists.version.getNetwork) ||
+ _.isUndefined(injectedWeb3IfExists.currentProvider)
+ ) {
+ return undefined;
+ }
+ return injectedWeb3IfExists;
}
private static async _getInjectedWeb3ProviderNetworkIdIfExistsAsync(): Promise<number | undefined> {
// Hack: We need to know the networkId the injectedWeb3 is connected to (if it is defined) in
@@ -117,7 +130,7 @@ export class Blockchain {
return networkIdIfExists;
}
private static async _getProviderAsync(
- injectedWeb3: Web3,
+ injectedWeb3: InjectedWeb3,
networkIdIfExists: number,
shouldUserLedgerProvider: boolean = false,
): Promise<[Provider, LedgerSubprovider | undefined]> {
@@ -151,7 +164,7 @@ export class Blockchain {
// We catch all requests involving a users account and send it to the injectedWeb3
// instance. All other requests go to the public hosted node.
const provider = new ProviderEngine();
- provider.addProvider(new InjectedWeb3Subprovider(injectedWeb3.currentProvider));
+ provider.addProvider(new SignerSubprovider(injectedWeb3.currentProvider));
provider.addProvider(new FilterSubprovider());
const rpcSubproviders = _.map(publicNodeUrlsIfExistsForNetworkId, publicNodeUrl => {
return new RpcSubprovider({
@@ -832,10 +845,10 @@ export class Blockchain {
this._dispatcher.updateNetworkId(networkId);
await this._rehydrateStoreWithContractEventsAsync();
}
- private _updateProviderName(injectedWeb3: Web3): void {
- const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3);
+ private _updateProviderName(injectedWeb3IfExists: InjectedWeb3): void {
+ const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3IfExists);
const providerName = doesInjectedWeb3Exist
- ? Blockchain._getNameGivenProvider(injectedWeb3.currentProvider)
+ ? Blockchain._getNameGivenProvider(injectedWeb3IfExists.currentProvider)
: constants.PROVIDER_NAME_PUBLIC;
this._dispatcher.updateInjectedProviderName(providerName);
}
diff --git a/packages/website/ts/blockchain_watcher.ts b/packages/website/ts/blockchain_watcher.ts
index df5f73fd1..4b23aa98a 100644
--- a/packages/website/ts/blockchain_watcher.ts
+++ b/packages/website/ts/blockchain_watcher.ts
@@ -10,6 +10,7 @@ export class BlockchainWatcher {
private _watchBalanceIntervalId: NodeJS.Timer;
private _prevUserEtherBalanceInWei?: BigNumber;
private _prevUserAddressIfExists: string;
+ private _prevNodeVersionIfExists: string;
constructor(dispatcher: Dispatcher, web3Wrapper: Web3Wrapper, shouldPollUserAddress: boolean) {
this._dispatcher = dispatcher;
this._shouldPollUserAddress = shouldPollUserAddress;
@@ -43,11 +44,9 @@ export class BlockchainWatcher {
);
}
private async _updateBalanceAsync(): Promise<void> {
- let prevNodeVersion: string;
- // Check for node version changes
const currentNodeVersion = await this._web3Wrapper.getNodeVersionAsync();
- if (currentNodeVersion !== prevNodeVersion) {
- prevNodeVersion = currentNodeVersion;
+ if (this._prevNodeVersionIfExists !== currentNodeVersion) {
+ this._prevNodeVersionIfExists = currentNodeVersion;
this._dispatcher.updateNodeVersion(currentNodeVersion);
}
diff --git a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx
index 9ac78e80e..7b09cc92c 100644
--- a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx
+++ b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx
@@ -103,7 +103,6 @@ export class EthWethConversionDialog extends React.Component<
shouldCheckAllowance={false}
onChange={this._onValueChange.bind(this)}
amount={this.state.value}
- onVisitBalancesPageClick={this.props.onCancelled}
/>
) : (
<EthAmountInput
@@ -112,7 +111,6 @@ export class EthWethConversionDialog extends React.Component<
onChange={this._onValueChange.bind(this)}
shouldCheckBalance={true}
shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs}
- onVisitBalancesPageClick={this.props.onCancelled}
/>
)}
<div className="pt1" style={{ fontSize: 12 }}>
diff --git a/packages/website/ts/components/dialogs/send_dialog.tsx b/packages/website/ts/components/dialogs/send_dialog.tsx
index 421f18b4f..8a98fdf69 100644
--- a/packages/website/ts/components/dialogs/send_dialog.tsx
+++ b/packages/website/ts/components/dialogs/send_dialog.tsx
@@ -80,7 +80,6 @@ export class SendDialog extends React.Component<SendDialogProps, SendDialogState
shouldCheckAllowance={false}
onChange={this._onValueChange.bind(this)}
amount={this.state.value}
- onVisitBalancesPageClick={this.props.onCancelled}
lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
/>
</div>
diff --git a/packages/website/ts/components/inputs/allowance_toggle.tsx b/packages/website/ts/components/inputs/allowance_toggle.tsx
index 0dd2a5aa5..0d5995696 100644
--- a/packages/website/ts/components/inputs/allowance_toggle.tsx
+++ b/packages/website/ts/components/inputs/allowance_toggle.tsx
@@ -48,10 +48,10 @@ const styles: Styles = {
width: 25,
},
offTrackStyle: {
- backgroundColor: colors.allowanceToggleOffTrack,
+ backgroundColor: colors.grey300,
},
onTrackStyle: {
- backgroundColor: colors.allowanceToggleOnTrack,
+ backgroundColor: colors.mediumBlue,
},
};
diff --git a/packages/website/ts/components/inputs/balance_bounded_input.tsx b/packages/website/ts/components/inputs/balance_bounded_input.tsx
index 968609030..f23beb436 100644
--- a/packages/website/ts/components/inputs/balance_bounded_input.tsx
+++ b/packages/website/ts/components/inputs/balance_bounded_input.tsx
@@ -3,9 +3,8 @@ import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
import TextField from 'material-ui/TextField';
import * as React from 'react';
-import { Link } from 'react-router-dom';
import { RequiredLabel } from 'ts/components/ui/required_label';
-import { ValidatedBigNumberCallback, WebsitePaths } from 'ts/types';
+import { ValidatedBigNumberCallback } from 'ts/types';
import { utils } from 'ts/utils/utils';
interface BalanceBoundedInputProps {
@@ -18,8 +17,6 @@ interface BalanceBoundedInputProps {
shouldShowIncompleteErrs?: boolean;
shouldCheckBalance: boolean;
validate?: (amount: BigNumber) => React.ReactNode;
- onVisitBalancesPageClick?: () => void;
- shouldHideVisitBalancesLink?: boolean;
isDisabled?: boolean;
shouldShowErrs?: boolean;
shouldShowUnderline?: boolean;
@@ -35,7 +32,6 @@ interface BalanceBoundedInputState {
export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProps, BalanceBoundedInputState> {
public static defaultProps: Partial<BalanceBoundedInputProps> = {
shouldShowIncompleteErrs: false,
- shouldHideVisitBalancesLink: false,
isDisabled: false,
shouldShowErrs: true,
hintText: 'amount',
@@ -124,38 +120,11 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp
return 'Cannot be zero';
}
if (this.props.shouldCheckBalance && amount.gt(balance)) {
- return <span>Insufficient balance. {this._renderIncreaseBalanceLink()}</span>;
+ return <span>Insufficient balance.</span>;
}
const errMsg = _.isUndefined(this.props.validate) ? undefined : this.props.validate(amount);
return errMsg;
}
- private _renderIncreaseBalanceLink(): React.ReactNode {
- if (this.props.shouldHideVisitBalancesLink) {
- return null;
- }
-
- const increaseBalanceText = 'Increase balance';
- const linkStyle = {
- cursor: 'pointer',
- color: colors.darkestGrey,
- textDecoration: 'underline',
- display: 'inline',
- };
- if (_.isUndefined(this.props.onVisitBalancesPageClick)) {
- return (
- <Link to={`${WebsitePaths.Portal}/balances`} style={linkStyle}>
- {increaseBalanceText}
- </Link>
- );
- } else {
- return (
- <div onClick={this.props.onVisitBalancesPageClick} style={linkStyle}>
- {increaseBalanceText}
- </div>
- );
- }
- }
-
private _setAmountState(amount: string, balance: BigNumber, callback: () => void = _.noop): void {
const errorMsg = this._validate(amount, balance);
this.props.onErrorMsgChange(errorMsg);
diff --git a/packages/website/ts/components/inputs/eth_amount_input.tsx b/packages/website/ts/components/inputs/eth_amount_input.tsx
index 1f0f27410..552d4277a 100644
--- a/packages/website/ts/components/inputs/eth_amount_input.tsx
+++ b/packages/website/ts/components/inputs/eth_amount_input.tsx
@@ -14,9 +14,7 @@ interface EthAmountInputProps {
onChange: ValidatedBigNumberCallback;
onErrorMsgChange?: (errorMsg: React.ReactNode) => void;
shouldShowIncompleteErrs: boolean;
- onVisitBalancesPageClick?: () => void;
shouldCheckBalance: boolean;
- shouldHideVisitBalancesLink?: boolean;
shouldShowErrs?: boolean;
shouldShowUnderline?: boolean;
style?: React.CSSProperties;
@@ -46,8 +44,6 @@ export class EthAmountInput extends React.Component<EthAmountInputProps, EthAmou
onErrorMsgChange={this.props.onErrorMsgChange}
shouldCheckBalance={this.props.shouldCheckBalance}
shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs}
- onVisitBalancesPageClick={this.props.onVisitBalancesPageClick}
- shouldHideVisitBalancesLink={this.props.shouldHideVisitBalancesLink}
hintText={this.props.hintText}
shouldShowErrs={this.props.shouldShowErrs}
shouldShowUnderline={this.props.shouldShowUnderline}
diff --git a/packages/website/ts/components/inputs/token_amount_input.tsx b/packages/website/ts/components/inputs/token_amount_input.tsx
index a67120320..93ef516cf 100644
--- a/packages/website/ts/components/inputs/token_amount_input.tsx
+++ b/packages/website/ts/components/inputs/token_amount_input.tsx
@@ -21,7 +21,6 @@ interface TokenAmountInputProps {
shouldCheckAllowance: boolean;
onChange: ValidatedBigNumberCallback;
onErrorMsgChange?: (errorMsg: React.ReactNode) => void;
- onVisitBalancesPageClick?: () => void;
lastForceTokenStateRefetch: number;
shouldShowErrs?: boolean;
shouldShowUnderline?: boolean;
@@ -88,7 +87,6 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok
validate={this._validate.bind(this)}
shouldCheckBalance={this.props.shouldCheckBalance}
shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs}
- onVisitBalancesPageClick={this.props.onVisitBalancesPageClick}
isDisabled={!this.state.isBalanceAndAllowanceLoaded}
hintText={this.props.hintText}
shouldShowErrs={this.props.shouldShowErrs}
diff --git a/packages/website/ts/components/onboarding/add_eth_onboarding_step.tsx b/packages/website/ts/components/onboarding/add_eth_onboarding_step.tsx
index 31ce99d31..bccdc0c18 100644
--- a/packages/website/ts/components/onboarding/add_eth_onboarding_step.tsx
+++ b/packages/website/ts/components/onboarding/add_eth_onboarding_step.tsx
@@ -1,18 +1,42 @@
+import { BigNumber } from '@0xproject/utils';
import * as React from 'react';
import { Container } from 'ts/components/ui/container';
+import { Image } from 'ts/components/ui/image';
import { Text } from 'ts/components/ui/text';
+import { constants } from 'ts/utils/constants';
+import { utils } from 'ts/utils/utils';
-export interface AddEthOnboardingStepProps {}
+export interface AddEthOnboardingStepProps {
+ userEthBalanceInWei: BigNumber;
+}
-export const AddEthOnboardingStep: React.StatelessComponent<AddEthOnboardingStepProps> = () => (
- <div className="flex items-center flex-column">
- <Text> Before you begin you will need to send some ETH to your metamask wallet.</Text>
- <Container marginTop="15px" marginBottom="15px">
- <img src="/images/ether_alt.svg" height="50px" width="50px" />
- </Container>
- <Text>
- Click on the <img src="/images/metamask_icon.png" height="20px" width="20px" /> metamask extension in your
- browser and click either <b>BUY</b> or <b>DEPOSIT</b>.
- </Text>
- </div>
-);
+export const AddEthOnboardingStep: React.StatelessComponent<AddEthOnboardingStepProps> = props =>
+ props.userEthBalanceInWei.gt(0) ? (
+ <div className="flex items-center flex-column">
+ <Text>
+ Great! Looks like you already have{' '}
+ <b>
+ {utils.getFormattedAmount(
+ props.userEthBalanceInWei,
+ constants.DECIMAL_PLACES_ETH,
+ constants.ETHER_SYMBOL,
+ )}{' '}
+ </b>
+ in your wallet.
+ </Text>
+ <Container marginTop="15px" marginBottom="15px">
+ <Image src="/images/ether_alt.svg" height="50px" width="50px" />
+ </Container>
+ </div>
+ ) : (
+ <div className="flex items-center flex-column">
+ <Text> Before you begin you will need to send some ETH to your wallet.</Text>
+ <Container marginTop="15px" marginBottom="15px">
+ <Image src="/images/ether_alt.svg" height="50px" width="50px" />
+ </Container>
+ <Text className="xs-hide">
+ Click on the <Image src="/images/metamask_icon.png" height="20px" width="20px" /> MetaMask extension in
+ your browser and click either <b>BUY</b> or <b>DEPOSIT</b>.
+ </Text>
+ </div>
+ );
diff --git a/packages/website/ts/components/onboarding/congrats_onboarding_step.tsx b/packages/website/ts/components/onboarding/congrats_onboarding_step.tsx
index 3a8db8c36..8100fd2c0 100644
--- a/packages/website/ts/components/onboarding/congrats_onboarding_step.tsx
+++ b/packages/website/ts/components/onboarding/congrats_onboarding_step.tsx
@@ -10,6 +10,6 @@ export const CongratsOnboardingStep: React.StatelessComponent<CongratsOnboarding
<Container marginTop="25px" marginBottom="15px" className="flex justify-center">
<img src="/images/zrx_ecosystem.svg" height="150px" />
</Container>
- <Text>No need to log in. Each relayer automatically detects and connects to your metamask wallet.</Text>
+ <Text>No need to log in. Each relayer automatically detects and connects to your wallet.</Text>
</div>
);
diff --git a/packages/website/ts/components/onboarding/install_wallet_onboarding_step.tsx b/packages/website/ts/components/onboarding/install_wallet_onboarding_step.tsx
index a54496186..a95c464af 100644
--- a/packages/website/ts/components/onboarding/install_wallet_onboarding_step.tsx
+++ b/packages/website/ts/components/onboarding/install_wallet_onboarding_step.tsx
@@ -8,11 +8,12 @@ export interface InstallWalletOnboardingStepProps {}
export const InstallWalletOnboardingStep: React.StatelessComponent<InstallWalletOnboardingStepProps> = () => (
<div className="flex items-center flex-column">
- <Container marginTop="15px" marginBottom="15px">
- <ActionAccountBalanceWallet style={{ width: '30px', height: '30px' }} color={colors.orange} />
- </Container>
<Text>
Before you begin, you need to connect to a wallet. This will be used across all 0x relayers and dApps.
</Text>
+ <Container marginTop="15px" marginBottom="15px">
+ <ActionAccountBalanceWallet style={{ width: '50px', height: '50px' }} color={colors.orange} />
+ </Container>
+ <Text>Please refresh the page once you've done this to continue!</Text>
</div>
);
diff --git a/packages/website/ts/components/onboarding/intro_onboarding_step.tsx b/packages/website/ts/components/onboarding/intro_onboarding_step.tsx
index 548839218..3a27b6854 100644
--- a/packages/website/ts/components/onboarding/intro_onboarding_step.tsx
+++ b/packages/website/ts/components/onboarding/intro_onboarding_step.tsx
@@ -1,5 +1,6 @@
import * as React from 'react';
import { Container } from 'ts/components/ui/container';
+import { Image } from 'ts/components/ui/image';
import { Text } from 'ts/components/ui/text';
export interface IntroOnboardingStepProps {}
@@ -7,15 +8,19 @@ export interface IntroOnboardingStepProps {}
export const IntroOnboardingStep: React.StatelessComponent<IntroOnboardingStepProps> = () => (
<div className="flex items-center flex-column">
<Text>
- In order to start trading on any 0x relayer in the 0x ecosystem, you need to complete two simple steps.
+ In order to start trading on any 0x relayer in the 0x ecosystem, you need to complete three simple steps.
</Text>
<Container width="100%" marginTop="25px" marginBottom="15px" className="flex justify-around">
<div className="flex flex-column items-center">
- <img src="/images/eth_token.svg" height="50px" width="50x" />
+ <Image src="/images/ether.png" height="50px" width="50px" />
+ <Text> Add ETH </Text>
+ </div>
+ <div className="flex flex-column items-center">
+ <Image src="/images/eth_token.svg" height="50px" width="50x" />
<Text> Wrap ETH </Text>
</div>
<div className="flex flex-column items-center">
- <img src="/images/fake_toggle.svg" height="50px" width="50px" />
+ <Image src="/images/fake_toggle.svg" height="50px" width="50px" />
<Text> Unlock tokens </Text>
</div>
</Container>
diff --git a/packages/website/ts/components/onboarding/onboarding_card.tsx b/packages/website/ts/components/onboarding/onboarding_card.tsx
index bc83b8034..ba5b3d6ea 100644
--- a/packages/website/ts/components/onboarding/onboarding_card.tsx
+++ b/packages/website/ts/components/onboarding/onboarding_card.tsx
@@ -1,6 +1,7 @@
import { colors } from '@0xproject/react-shared';
import * as React from 'react';
+import * as _ from 'lodash';
import { Button } from 'ts/components/ui/button';
import { Container } from 'ts/components/ui/container';
import { IconButton } from 'ts/components/ui/icon_button';
@@ -16,6 +17,7 @@ export interface OnboardingCardProps {
onClose: () => void;
onClickNext: () => void;
onClickBack: () => void;
+ onContinueButtonClick?: () => void;
continueButtonDisplay?: ContinueButtonDisplay;
shouldHideBackButton?: boolean;
shouldHideNextButton?: boolean;
@@ -28,6 +30,7 @@ export const OnboardingCard: React.StatelessComponent<OnboardingCardProps> = ({
content,
continueButtonDisplay,
continueButtonText,
+ onContinueButtonClick,
onClickNext,
onClickBack,
onClose,
@@ -36,7 +39,7 @@ export const OnboardingCard: React.StatelessComponent<OnboardingCardProps> = ({
borderRadius,
}) => (
<Island borderRadius={borderRadius}>
- <Container paddingRight="30px" paddingLeft="30px" maxWidth={350} paddingTop="15px" paddingBottom="15px">
+ <Container paddingRight="30px" paddingLeft="30px" paddingTop="15px" paddingBottom="15px">
<div className="flex flex-column">
<div className="flex justify-between">
<Title>{title}</Title>
@@ -52,7 +55,7 @@ export const OnboardingCard: React.StatelessComponent<OnboardingCardProps> = ({
{continueButtonDisplay && (
<Button
isDisabled={continueButtonDisplay === 'disabled'}
- onClick={onClickNext}
+ onClick={!_.isUndefined(onContinueButtonClick) ? onContinueButtonClick : onClickNext}
fontColor={colors.white}
fontSize="15px"
backgroundColor={colors.mediumBlue}
@@ -60,17 +63,21 @@ export const OnboardingCard: React.StatelessComponent<OnboardingCardProps> = ({
{continueButtonText}
</Button>
)}
- <Container className="flex justify-between" marginTop="15px">
- {!shouldHideBackButton && (
- <Text fontColor={colors.grey} onClick={onClickBack}>
- Back
- </Text>
- )}
- {!shouldHideNextButton && (
- <Text fontColor={colors.grey} onClick={onClickNext}>
- Skip
- </Text>
- )}
+ <Container className="clearfix" marginTop="15px">
+ <div className="left">
+ {!shouldHideBackButton && (
+ <Text fontColor={colors.grey} onClick={onClickBack}>
+ Back
+ </Text>
+ )}
+ </div>
+ <div className="right">
+ {!shouldHideNextButton && (
+ <Text fontColor={colors.grey} onClick={onClickNext}>
+ Skip
+ </Text>
+ )}
+ </div>
</Container>
</div>
</Container>
diff --git a/packages/website/ts/components/onboarding/onboarding_flow.tsx b/packages/website/ts/components/onboarding/onboarding_flow.tsx
index ec8d96191..c2b4a4ca7 100644
--- a/packages/website/ts/components/onboarding/onboarding_flow.tsx
+++ b/packages/website/ts/components/onboarding/onboarding_flow.tsx
@@ -6,16 +6,34 @@ import { ContinueButtonDisplay, OnboardingTooltip } from 'ts/components/onboardi
import { Animation } from 'ts/components/ui/animation';
import { Container } from 'ts/components/ui/container';
import { Overlay } from 'ts/components/ui/overlay';
+import { PointerDirection } from 'ts/components/ui/pointer';
+import { zIndex } from 'ts/style/z_index';
-export interface Step {
+export interface FixedPositionSettings {
+ type: 'fixed';
+ top?: string;
+ bottom?: string;
+ left?: string;
+ right?: string;
+ pointerDirection?: PointerDirection;
+}
+
+export interface TargetPositionSettings {
+ type: 'target';
target: string;
+ placement: Placement;
+}
+
+export interface Step {
+ // Provide either a CSS selector, or fixed position settings. Only applies to desktop.
+ position: TargetPositionSettings | FixedPositionSettings;
title?: string;
content: React.ReactNode;
- placement?: Placement;
shouldHideBackButton?: boolean;
shouldHideNextButton?: boolean;
continueButtonDisplay?: ContinueButtonDisplay;
continueButtonText?: string;
+ onContinueButtonClick?: () => void;
}
export interface OnboardingFlowProps {
@@ -38,40 +56,57 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> {
return null;
}
let onboardingElement = null;
+ const currentStep = this._getCurrentStep();
if (this.props.isMobile) {
- onboardingElement = <Animation type="easeUpFromBottom">{this._renderOnboardignCard()}</Animation>;
- } else {
+ onboardingElement = <Animation type="easeUpFromBottom">{this._renderOnboardingCard()}</Animation>;
+ } else if (currentStep.position.type === 'target') {
+ const { placement, target } = currentStep.position;
onboardingElement = (
- <Popper
- referenceElement={this._getElementForStep()}
- placement={this._getCurrentStep().placement}
- positionFixed={true}
- >
+ <Popper referenceElement={document.querySelector(target)} placement={placement} positionFixed={true}>
{this._renderPopperChildren.bind(this)}
</Popper>
);
+ } else if (currentStep.position.type === 'fixed') {
+ const { top, right, bottom, left, pointerDirection } = currentStep.position;
+ onboardingElement = (
+ <Container
+ position="fixed"
+ zIndex={zIndex.aboveOverlay}
+ top={top}
+ right={right}
+ bottom={bottom}
+ left={left}
+ >
+ {this._renderToolTip(pointerDirection)}
+ </Container>
+ );
}
if (this.props.disableOverlay) {
return onboardingElement;
}
- return <Overlay>{onboardingElement}</Overlay>;
- }
- private _getElementForStep(): Element {
- return document.querySelector(this._getCurrentStep().target);
+ return (
+ <div>
+ <Overlay onClick={this.props.onClose} />
+ {onboardingElement}
+ </div>
+ );
}
private _renderPopperChildren(props: PopperChildrenProps): React.ReactNode {
+ const customStyles = { zIndex: zIndex.aboveOverlay };
+ // On re-render, we want to re-center the popper.
+ props.scheduleUpdate();
return (
- <div ref={props.ref} style={props.style} data-placement={props.placement}>
+ <div ref={props.ref} style={{ ...props.style, ...customStyles }} data-placement={props.placement}>
{this._renderToolTip()}
</div>
);
}
- private _renderToolTip(): React.ReactNode {
+ private _renderToolTip(pointerDirection?: PointerDirection): React.ReactNode {
const { steps, stepIndex } = this.props;
const step = steps[stepIndex];
const isLastStep = steps.length - 1 === stepIndex;
return (
- <Container marginLeft="30px" maxWidth={350}>
+ <Container marginLeft="30px" width="400px">
<OnboardingTooltip
title={step.title}
content={step.content}
@@ -83,17 +118,19 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> {
onClickBack={this._goToPrevStep.bind(this)}
continueButtonDisplay={step.continueButtonDisplay}
continueButtonText={step.continueButtonText}
+ onContinueButtonClick={step.onContinueButtonClick}
+ pointerDirection={pointerDirection}
/>
</Container>
);
}
- private _renderOnboardignCard(): React.ReactNode {
+ private _renderOnboardingCard(): React.ReactNode {
const { steps, stepIndex } = this.props;
const step = steps[stepIndex];
const isLastStep = steps.length - 1 === stepIndex;
return (
- <Container position="relative" zIndex={1} maxWidth="100vw">
+ <Container position="relative" zIndex={1}>
<OnboardingCard
title={step.title}
content={step.content}
@@ -105,6 +142,7 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> {
onClickBack={this._goToPrevStep.bind(this)}
continueButtonDisplay={step.continueButtonDisplay}
continueButtonText={step.continueButtonText}
+ onContinueButtonClick={step.onContinueButtonClick}
borderRadius="10px 10px 0px 0px"
/>
</Container>
diff --git a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx
index 296b410fe..b7c5a9f64 100644
--- a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx
+++ b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx
@@ -9,10 +9,19 @@ import { AddEthOnboardingStep } from 'ts/components/onboarding/add_eth_onboardin
import { CongratsOnboardingStep } from 'ts/components/onboarding/congrats_onboarding_step';
import { InstallWalletOnboardingStep } from 'ts/components/onboarding/install_wallet_onboarding_step';
import { IntroOnboardingStep } from 'ts/components/onboarding/intro_onboarding_step';
-import { OnboardingFlow, Step } from 'ts/components/onboarding/onboarding_flow';
+import {
+ FixedPositionSettings,
+ OnboardingFlow,
+ Step,
+ TargetPositionSettings,
+} from 'ts/components/onboarding/onboarding_flow';
import { SetAllowancesOnboardingStep } from 'ts/components/onboarding/set_allowances_onboarding_step';
import { UnlockWalletOnboardingStep } from 'ts/components/onboarding/unlock_wallet_onboarding_step';
-import { WrapEthOnboardingStep } from 'ts/components/onboarding/wrap_eth_onboarding_step';
+import {
+ WrapEthOnboardingStep1,
+ WrapEthOnboardingStep2,
+ WrapEthOnboardingStep3,
+} from 'ts/components/onboarding/wrap_eth_onboarding_step';
import { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle';
import { ProviderType, ScreenWidths, Token, TokenByAddress, TokenStateByAddress } from 'ts/types';
import { analytics } from 'ts/utils/analytics';
@@ -24,7 +33,7 @@ export interface PortalOnboardingFlowProps extends RouteComponentProps<any> {
stepIndex: number;
isRunning: boolean;
userAddress: string;
- hasBeenSeen: boolean;
+ hasBeenClosed: boolean;
providerType: ProviderType;
injectedProviderName: string;
blockchainIsLoaded: boolean;
@@ -40,15 +49,24 @@ export interface PortalOnboardingFlowProps extends RouteComponentProps<any> {
class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProps> {
private _unlisten: () => void;
public componentDidMount(): void {
- this._overrideOnboardingStateIfShould();
+ this._adjustStepIfShould();
// If there is a route change, just close onboarding.
this._unlisten = this.props.history.listen(() => this.props.updateIsRunning(false));
}
public componentWillUnmount(): void {
this._unlisten();
}
- public componentDidUpdate(): void {
- this._overrideOnboardingStateIfShould();
+ public componentDidUpdate(prevProps: PortalOnboardingFlowProps): void {
+ this._adjustStepIfShould();
+ if (!prevProps.isRunning && this.props.isRunning) {
+ // On mobile, make sure the wallet is completely visible.
+ if (this.props.screenWidth === ScreenWidths.Sm) {
+ document.querySelector('.wallet').scrollIntoView();
+ }
+ }
+ if (!prevProps.blockchainIsLoaded && this.props.blockchainIsLoaded) {
+ this._autoStartOnboardingIfShould();
+ }
}
public render(): React.ReactNode {
return (
@@ -64,71 +82,91 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
);
}
private _getSteps(): Step[] {
+ const nextToWalletPosition: TargetPositionSettings = {
+ type: 'target',
+ target: '.wallet',
+ placement: 'right',
+ };
+ const underMetamaskExtension: FixedPositionSettings = {
+ type: 'fixed',
+ top: '30px',
+ right: '10px',
+ pointerDirection: 'top',
+ };
const steps: Step[] = [
{
- target: '.wallet',
+ position: nextToWalletPosition,
title: '0x Ecosystem Setup',
content: <InstallWalletOnboardingStep />,
- placement: 'right',
shouldHideBackButton: true,
shouldHideNextButton: true,
},
{
- target: '.wallet',
+ position: underMetamaskExtension,
title: '0x Ecosystem Setup',
content: <UnlockWalletOnboardingStep />,
- placement: 'right',
shouldHideBackButton: true,
shouldHideNextButton: true,
},
{
- target: '.wallet',
+ position: nextToWalletPosition,
title: '0x Ecosystem Account Setup',
content: <IntroOnboardingStep />,
- placement: 'right',
shouldHideBackButton: true,
continueButtonDisplay: 'enabled',
},
{
- target: '.eth-row',
- title: 'Add ETH',
- content: <AddEthOnboardingStep />,
- placement: 'right',
+ position: nextToWalletPosition,
+ title: 'Step 1: Add ETH',
+ content: (
+ <AddEthOnboardingStep userEthBalanceInWei={this.props.userEtherBalanceInWei || new BigNumber(0)} />
+ ),
continueButtonDisplay: this._userHasVisibleEth() ? 'enabled' : 'disabled',
},
{
- target: '.weth-row',
- title: 'Step 1/2',
+ position: nextToWalletPosition,
+ title: 'Step 2: Wrap ETH',
+ content: <WrapEthOnboardingStep1 />,
+ continueButtonDisplay: 'enabled',
+ },
+ {
+ position: nextToWalletPosition,
+ title: 'Step 2: Wrap ETH',
+ content: <WrapEthOnboardingStep2 />,
+ continueButtonDisplay: this._userHasVisibleWeth() ? 'enabled' : 'disabled',
+ },
+ {
+ position: nextToWalletPosition,
+ title: 'Step 2: Wrap ETH',
content: (
- <WrapEthOnboardingStep
- formattedEthBalanceIfExists={
+ <WrapEthOnboardingStep3
+ formattedWethBalanceIfExists={
this._userHasVisibleWeth() ? this._getFormattedWethBalance() : undefined
}
/>
),
- placement: 'right',
- continueButtonDisplay: this._userHasVisibleWeth() ? 'enabled' : undefined,
+ continueButtonDisplay: this._userHasVisibleWeth() ? 'enabled' : 'disabled',
},
{
- target: '.weth-row',
- title: 'Step 2/2',
+ position: nextToWalletPosition,
+ title: 'Step 3: Unlock Tokens',
content: (
<SetAllowancesOnboardingStep
zrxAllowanceToggle={this._renderZrxAllowanceToggle()}
ethAllowanceToggle={this._renderEthAllowanceToggle()}
+ doesUserHaveAllowancesForWethAndZrx={this._doesUserHaveAllowancesForWethAndZrx()}
/>
),
- placement: 'right',
- continueButtonDisplay: this._userHasAllowancesForWethAndZrx() ? 'enabled' : 'disabled',
+ continueButtonDisplay: this._doesUserHaveAllowancesForWethAndZrx() ? 'enabled' : 'disabled',
},
{
- target: '.wallet',
- title: '🎉 Congrats! The ecosystem awaits.',
+ position: nextToWalletPosition,
+ title: '🎉 The Ecosystem Awaits',
content: <CongratsOnboardingStep />,
- placement: 'right',
continueButtonDisplay: 'enabled',
shouldHideNextButton: true,
continueButtonText: 'Enter the 0x Ecosystem',
+ onContinueButtonClick: this._handleFinalStepContinueClick.bind(this),
},
];
return steps;
@@ -155,7 +193,7 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
private _userHasVisibleWeth(): boolean {
return this._getWethBalance() > new BigNumber(0);
}
- private _userHasAllowancesForWethAndZrx(): boolean {
+ private _doesUserHaveAllowancesForWethAndZrx(): boolean {
const ethToken = utils.getEthToken(this.props.tokenByAddress);
const zrxToken = utils.getZrxToken(this.props.tokenByAddress);
if (ethToken && zrxToken) {
@@ -167,11 +205,6 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
}
return false;
}
- private _overrideOnboardingStateIfShould(): void {
- this._autoStartOnboardingIfShould();
- this._adjustStepIfShould();
- }
-
private _adjustStepIfShould(): void {
const stepIndex = this.props.stepIndex;
if (this._isAddressAvailable()) {
@@ -195,7 +228,10 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
}
}
private _autoStartOnboardingIfShould(): void {
- if (!this.props.isRunning && !this.props.hasBeenSeen && this.props.blockchainIsLoaded) {
+ if (
+ (this.props.stepIndex === 0 && !this.props.isRunning && this.props.blockchainIsLoaded) ||
+ (!this.props.isRunning && !this.props.hasBeenClosed && this.props.blockchainIsLoaded)
+ ) {
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
analytics.logEvent('Portal', 'Onboarding Started - Automatic', networkName, this.props.stepIndex);
this.props.updateIsRunning(true);
@@ -238,6 +274,13 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
/>
);
}
+ private _handleFinalStepContinueClick(): void {
+ if (utils.isMobile(this.props.screenWidth)) {
+ window.scrollTo(0, 0);
+ this.props.history.push('/portal');
+ }
+ this._closeOnboarding();
+ }
}
export const PortalOnboardingFlow = withRouter(PlainPortalOnboardingFlow);
diff --git a/packages/website/ts/components/onboarding/set_allowances_onboarding_step.tsx b/packages/website/ts/components/onboarding/set_allowances_onboarding_step.tsx
index 1ff248c40..5ddfe38d7 100644
--- a/packages/website/ts/components/onboarding/set_allowances_onboarding_step.tsx
+++ b/packages/website/ts/components/onboarding/set_allowances_onboarding_step.tsx
@@ -5,11 +5,13 @@ import { Text } from 'ts/components/ui/text';
export interface SetAllowancesOnboardingStepProps {
zrxAllowanceToggle: React.ReactNode;
ethAllowanceToggle: React.ReactNode;
+ doesUserHaveAllowancesForWethAndZrx: boolean;
}
export const SetAllowancesOnboardingStep: React.StatelessComponent<SetAllowancesOnboardingStepProps> = ({
ethAllowanceToggle,
zrxAllowanceToggle,
+ doesUserHaveAllowancesForWethAndZrx,
}) => (
<div className="flex items-center flex-column">
<Text>Unlock your tokens for trading. You only need to do this once for each token.</Text>
@@ -23,5 +25,6 @@ export const SetAllowancesOnboardingStep: React.StatelessComponent<SetAllowances
<Container marginTop="10px">{zrxAllowanceToggle}</Container>
</div>
</Container>
+ {doesUserHaveAllowancesForWethAndZrx && <Text>Perfect! Both your ZRX and WETH tokens are unlocked.</Text>}
</div>
);
diff --git a/packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx b/packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx
index 0039aa545..4ed7137d4 100644
--- a/packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx
+++ b/packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx
@@ -10,7 +10,7 @@ export const UnlockWalletOnboardingStep: React.StatelessComponent<UnlockWalletOn
<Container marginTop="15px" marginBottom="15px">
<img src="/images/metamask_icon.png" height="50px" width="50px" />
</Container>
- <Text center={true}>Unlock your metamask extension to get started.</Text>
+ <Text center={true}>Unlock your MetaMask extension to get started.</Text>
</div>
</div>
);
diff --git a/packages/website/ts/components/onboarding/wrap_eth_onboarding_step.tsx b/packages/website/ts/components/onboarding/wrap_eth_onboarding_step.tsx
index b21b39341..4d336c80f 100644
--- a/packages/website/ts/components/onboarding/wrap_eth_onboarding_step.tsx
+++ b/packages/website/ts/components/onboarding/wrap_eth_onboarding_step.tsx
@@ -4,70 +4,78 @@ import { Container } from 'ts/components/ui/container';
import { IconButton } from 'ts/components/ui/icon_button';
import { Text } from 'ts/components/ui/text';
-export interface WrapEthOnboardingStepProps {
- formattedEthBalanceIfExists?: string;
+export interface WrapEthOnboardingStep1Props {}
+
+export const WrapEthOnboardingStep1: React.StatelessComponent<WrapEthOnboardingStep1Props> = () => (
+ <div className="flex items-center flex-column">
+ <Text>
+ You need to convert some of your ETH into tradeable <b>Wrapped ETH (WETH)</b>.
+ </Text>
+ <Container width="100%" marginTop="25px" marginBottom="15px" className="flex justify-center">
+ <div className="flex flex-column items-center">
+ <Text fontWeight={700}> 1 ETH </Text>
+ <img src="/images/eth_dollar.svg" height="75px" width="75x" />
+ </div>
+ <Container marginRight="25px" marginLeft="25px" position="relative" top="20px">
+ <Text fontSize="36px">=</Text>
+ </Container>
+ <div className="flex flex-column items-center">
+ <Text fontWeight={700}> 1 WETH </Text>
+ <img src="/images/eth_token_erc20.svg" height="75px" width="75px" />
+ </div>
+ </Container>
+ <Text>
+ Think of it like the coin version of a paper note. It has the same value, but some machines only take coins.
+ </Text>
+ </div>
+);
+
+export interface WrapEthOnboardingStep2Props {}
+
+export const WrapEthOnboardingStep2: React.StatelessComponent<WrapEthOnboardingStep2Props> = () => (
+ <div className="flex items-center flex-column">
+ <Text>Wrapping your ETH is a reversable transaction, so don't worry about losing your ETH.</Text>
+ <Text>
+ Click
+ <Container display="inline-block" marginLeft="10px" marginRight="10px">
+ <IconButton
+ iconName="zmdi-long-arrow-down"
+ color={colors.mediumBlue}
+ labelText="wrap"
+ display="inline-flex"
+ />
+ </Container>
+ to wrap your ETH.
+ </Text>
+ </div>
+);
+
+export interface WrapEthOnboardingStep3Props {
+ formattedWethBalanceIfExists?: string;
}
-export const WrapEthOnboardingStep: React.StatelessComponent<WrapEthOnboardingStepProps> = ({
- formattedEthBalanceIfExists,
-}) => {
- if (formattedEthBalanceIfExists) {
- return (
- <div className="flex items-center flex-column">
- <Text>Congrats you now have {formattedEthBalanceIfExists} in your wallet.</Text>
- <Container width="100%" marginTop="25px" marginBottom="15px" className="flex justify-center">
- <div className="flex flex-column items-center">
- <Text fontWeight={700}> 1 ETH </Text>
- <img src="/images/eth_dollar.svg" height="75px" width="75x" />
- </div>
- <Container marginRight="25px" marginLeft="25px" position="relative" top="20px">
- <Text fontSize="25px">
- <i className="zmdi zmdi-long-arrow-right" />
- </Text>
- </Container>
- <div className="flex flex-column items-center">
- <Text fontWeight={700}> 1 WETH </Text>
- <img src="/images/eth_token_erc20.svg" height="75px" width="75px" />
- </div>
- </Container>
+export const WrapEthOnboardingStep3: React.StatelessComponent<WrapEthOnboardingStep3Props> = ({
+ formattedWethBalanceIfExists,
+}) => (
+ <div className="flex items-center flex-column">
+ <Text>
+ You have <b>{formattedWethBalanceIfExists || '0 WETH'}</b> in your wallet.
+ {formattedWethBalanceIfExists && ' Great!'}
+ </Text>
+ <Container width="100%" marginTop="25px" marginBottom="15px" className="flex justify-center">
+ <div className="flex flex-column items-center">
+ <Text fontWeight={700}> 1 ETH </Text>
+ <img src="/images/eth_dollar.svg" height="75px" width="75x" />
</div>
- );
- } else {
- return (
- <div className="flex items-center flex-column">
- <Text>
- You need to convert some of your ETH into tradeable <b>Wrapped ETH (WETH)</b>.
- </Text>
- <Container width="100%" marginTop="25px" marginBottom="15px" className="flex justify-center">
- <div className="flex flex-column items-center">
- <Text fontWeight={700}> 1 ETH </Text>
- <img src="/images/eth_dollar.svg" height="75px" width="75x" />
- </div>
- <Container marginRight="25px" marginLeft="25px" position="relative" top="20px">
- <Text fontSize="36px">=</Text>
- </Container>
- <div className="flex flex-column items-center">
- <Text fontWeight={700}> 1 WETH </Text>
- <img src="/images/eth_token_erc20.svg" height="75px" width="75px" />
- </div>
- </Container>
- <Text>
- Think of it like the coin version of a paper note. It has the same value, but some machines only
- take coins.
- </Text>
- <Text>
- Click
- <Container display="inline-block" marginLeft="10px" marginRight="10px">
- <IconButton
- iconName="zmdi-long-arrow-down"
- color={colors.mediumBlue}
- labelText="wrap"
- display="inline-flex"
- />
- </Container>
- to wrap your ETH.
+ <Container marginRight="25px" marginLeft="25px" position="relative" top="20px">
+ <Text fontSize="25px">
+ <i className="zmdi zmdi-long-arrow-right" />
</Text>
+ </Container>
+ <div className="flex flex-column items-center">
+ <Text fontWeight={700}> 1 WETH </Text>
+ <img src="/images/eth_token_erc20.svg" height="75px" width="75px" />
</div>
- );
- }
-};
+ </Container>
+ </div>
+);
diff --git a/packages/website/ts/components/portal/drawer_menu.tsx b/packages/website/ts/components/portal/drawer_menu.tsx
index 205a60afc..a6707e86c 100644
--- a/packages/website/ts/components/portal/drawer_menu.tsx
+++ b/packages/website/ts/components/portal/drawer_menu.tsx
@@ -44,12 +44,13 @@ export const DrawerMenu = (props: DrawerMenuProps) => {
iconName: 'zmdi-portable-wifi',
};
const menuItemEntries = _.concat(relayerItemEntry, defaultMenuItemEntries);
- const displayMessage = utils.getReadableAccountState(
+ const accountState = utils.getAccountState(
props.blockchainIsLoaded && !_.isUndefined(props.blockchain),
props.providerType,
props.injectedProviderName,
props.userAddress,
);
+ const displayMessage = utils.getReadableAccountState(accountState, props.userAddress);
return (
<div style={styles.root}>
<Header userAddress={props.userAddress} displayMessage={displayMessage} />
diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx
index 438c7b52f..8c3b5cfd7 100644
--- a/packages/website/ts/components/portal/portal.tsx
+++ b/packages/website/ts/components/portal/portal.tsx
@@ -1,10 +1,9 @@
import { colors, constants as sharedConstants } from '@0xproject/react-shared';
import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
-import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet';
import * as React from 'react';
import * as DocumentTitle from 'react-document-title';
-import { Route, RouteComponentProps, Switch } from 'react-router-dom';
+import { Link, Route, RouteComponentProps, Switch } from 'react-router-dom';
import { Blockchain } from 'ts/blockchain';
import { BlockchainErrDialog } from 'ts/components/dialogs/blockchain_err_dialog';
@@ -24,7 +23,7 @@ import { TopBar, TopBarDisplayType } from 'ts/components/top_bar/top_bar';
import { TradeHistory } from 'ts/components/trade_history/trade_history';
import { Container } from 'ts/components/ui/container';
import { FlashMessage } from 'ts/components/ui/flash_message';
-import { Island } from 'ts/components/ui/island';
+import { Image } from 'ts/components/ui/image';
import { Text } from 'ts/components/ui/text';
import { Wallet } from 'ts/components/wallet/wallet';
import { GenerateOrderForm } from 'ts/containers/generate_order_form';
@@ -319,15 +318,14 @@ export class Portal extends React.Component<PortalProps, PortalState> {
);
}
private _renderWallet(): React.ReactNode {
- const startOnboarding = this._renderStartOnboarding();
const isMobile = utils.isMobile(this.props.screenWidth);
// We need room to scroll down for mobile onboarding
const marginBottom = isMobile ? '200px' : '15px';
return (
<div>
- <Container>
- {isMobile && <Container marginBottom="15px">{startOnboarding}</Container>}
- <Container marginBottom={marginBottom}>
+ <Container className="flex flex-column items-center">
+ {isMobile && <Container marginBottom="20px">{this._renderStartOnboarding()}</Container>}
+ <Container marginBottom={marginBottom} width="100%">
<Wallet
style={
!isMobile && this.props.isPortalOnboardingShowing
@@ -355,7 +353,7 @@ export class Portal extends React.Component<PortalProps, PortalState> {
refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this)}
/>
</Container>
- {!isMobile && <Container marginTop="15px">{startOnboarding}</Container>}
+ {!isMobile && <Container marginTop="8px">{this._renderStartOnboarding()}</Container>}
</Container>
<PortalOnboardingFlow
blockchain={this._blockchain}
@@ -366,26 +364,24 @@ export class Portal extends React.Component<PortalProps, PortalState> {
);
}
private _renderStartOnboarding(): React.ReactNode {
- return (
- <Island>
- <Container
- marginTop="30px"
- marginBottom="30px"
- marginLeft="30px"
- marginRight="30px"
- className="flex justify-around items-center"
- >
- <ActionAccountBalanceWallet style={{ width: '30px', height: '30px' }} color={colors.orange} />
- <Text
- fontColor={colors.grey}
- fontSize="16px"
- center={true}
- onClick={this._startOnboarding.bind(this)}
- >
- Learn how to set up your account
- </Text>
+ const isMobile = utils.isMobile(this.props.screenWidth);
+ const shouldStartOnboarding = !isMobile || this.props.location.pathname === `${WebsitePaths.Portal}/account`;
+ const startOnboarding = (
+ <Container className="flex items-center center">
+ <Text fontColor={colors.mediumBlue} fontSize="16px" onClick={this._startOnboarding.bind(this)}>
+ Set up your account to start trading
+ </Text>
+ <Container marginLeft="8px" paddingTop="3px">
+ <Image src="/images/setup_account_icon.svg" height="20px" width="20x" />
</Container>
- </Island>
+ </Container>
+ );
+ return !shouldStartOnboarding ? (
+ <Link to={{ pathname: `${WebsitePaths.Portal}/account` }} style={{ textDecoration: 'none' }}>
+ {startOnboarding}
+ </Link>
+ ) : (
+ startOnboarding
);
}
@@ -393,10 +389,6 @@ export class Portal extends React.Component<PortalProps, PortalState> {
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
analytics.logEvent('Portal', 'Onboarding Started - Manual', networkName, this.props.portalOnboardingStep);
this.props.dispatcher.updatePortalOnboardingShowing(true);
- // On mobile, make sure the wallet is completely visible.
- if (this.props.screenWidth === ScreenWidths.Sm) {
- document.querySelector('.wallet').scrollIntoView();
- }
}
private _renderWalletSection(): React.ReactNode {
return <Section header={<TextHeader labelText="Your Account" />} body={this._renderWallet()} />;
@@ -535,11 +527,15 @@ export class Portal extends React.Component<PortalProps, PortalState> {
);
}
private _renderRelayerIndexSection(): React.ReactNode {
+ return <Section header={<TextHeader labelText="0x Relayers" />} body={this._renderRelayerIndex()} />;
+ }
+ private _renderRelayerIndex(): React.ReactNode {
+ const isMobile = utils.isMobile(this.props.screenWidth);
return (
- <Section
- header={<TextHeader labelText="0x Relayers" />}
- body={<RelayerIndex networkId={this.props.networkId} screenWidth={this.props.screenWidth} />}
- />
+ <Container className="flex flex-column items-center">
+ {isMobile && <Container marginBottom="20px">{this._renderStartOnboarding()}</Container>}
+ <RelayerIndex networkId={this.props.networkId} screenWidth={this.props.screenWidth} />
+ </Container>
);
}
private _renderNotFoundMessage(): React.ReactNode {
diff --git a/packages/website/ts/components/portal/section.tsx b/packages/website/ts/components/portal/section.tsx
index 455ed07c9..b6c9fd098 100644
--- a/packages/website/ts/components/portal/section.tsx
+++ b/packages/website/ts/components/portal/section.tsx
@@ -6,9 +6,9 @@ export interface SectionProps {
}
export const Section = (props: SectionProps) => {
return (
- <div className="flex flex-column" style={{ height: '100%' }}>
+ <div className="flex flex-column">
{props.header}
- <div className="flex-auto">{props.body}</div>
+ {props.body}
</div>
);
};
diff --git a/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx b/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx
index b26bf512b..02bc1b014 100644
--- a/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx
+++ b/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx
@@ -9,6 +9,7 @@ import { Container } from 'ts/components/ui/container';
import { Image } from 'ts/components/ui/image';
import { Island } from 'ts/components/ui/island';
import { colors } from 'ts/style/colors';
+import { media } from 'ts/style/media';
import { styled } from 'ts/style/theme';
import { WebsiteBackendRelayerInfo } from 'ts/types';
import { utils } from 'ts/utils/utils';
@@ -55,7 +56,7 @@ const styles: Styles = {
};
const FALLBACK_IMG_SRC = '/images/relayer_fallback.png';
-const FALLBACK_PRIMARY_COLOR = colors.grey200;
+const FALLBACK_PRIMARY_COLOR = colors.grey300;
const NO_CONTENT_MESSAGE = '--';
const RELAYER_ICON_HEIGHT = '110px';
@@ -111,6 +112,9 @@ const GridTile = styled(PlainGridTile)`
&:hover {
transform: translate(0px, -3px);
}
+ ${media.small`
+ transform: none;
+ `};
`;
interface SectionProps {
diff --git a/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx b/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx
index f544fc924..c48b672e9 100644
--- a/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx
+++ b/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx
@@ -2,44 +2,30 @@ import {
colors,
constants as sharedConstants,
EtherscanLinkSuffixes,
- Styles,
utils as sharedUtils,
} from '@0xproject/react-shared';
import * as _ from 'lodash';
import * as React from 'react';
-import { analytics } from 'ts/utils/analytics';
+import { Container } from 'ts/components/ui/container';
+import { Text } from 'ts/components/ui/text';
import { WebsiteBackendTokenInfo } from 'ts/types';
+import { analytics } from 'ts/utils/analytics';
+import { utils } from 'ts/utils/utils';
export interface TopTokensProps {
tokens: WebsiteBackendTokenInfo[];
networkId: number;
}
-const styles: Styles = {
- tokenLabel: {
- textDecoration: 'none',
- color: colors.mediumBlue,
- fontSize: 14,
- },
- followingTokenLabel: {
- paddingLeft: 16,
- },
-};
-
export const TopTokens: React.StatelessComponent<TopTokensProps> = (props: TopTokensProps) => {
return (
<div className="flex">
- {_.map(props.tokens, (tokenInfo: WebsiteBackendTokenInfo, index: number) => {
- const firstItemStyle = { ...styles.tokenLabel, ...styles.followingTokenLabel };
- const style = index !== 0 ? firstItemStyle : styles.tokenLabel;
+ {_.map(props.tokens, (tokenInfo: WebsiteBackendTokenInfo) => {
return (
- <TokenLink
- key={tokenInfo.address}
- tokenInfo={tokenInfo}
- style={style}
- networkId={props.networkId}
- />
+ <Container key={tokenInfo.address} marginRight="16px">
+ <TokenLink tokenInfo={tokenInfo} networkId={props.networkId} />
+ </Container>
);
})}
</div>
@@ -48,12 +34,9 @@ export const TopTokens: React.StatelessComponent<TopTokensProps> = (props: TopTo
interface TokenLinkProps {
tokenInfo: WebsiteBackendTokenInfo;
- style: React.CSSProperties;
networkId: number;
}
-interface TokenLinkState {
- isHovering: boolean;
-}
+interface TokenLinkState {}
class TokenLink extends React.Component<TokenLinkProps, TokenLinkState> {
constructor(props: TokenLinkProps) {
@@ -63,37 +46,21 @@ class TokenLink extends React.Component<TokenLinkProps, TokenLinkState> {
};
}
public render(): React.ReactNode {
- const style = {
- ...this.props.style,
- cursor: 'pointer',
- opacity: this.state.isHovering ? 0.5 : 1,
- };
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
const eventLabel = `${this.props.tokenInfo.symbol}-${networkName}`;
const onClick = (event: React.MouseEvent<HTMLElement>) => {
event.stopPropagation();
analytics.logEvent('Portal', 'Token Click', eventLabel);
+ const tokenLink = this._tokenLinkFromToken(this.props.tokenInfo, this.props.networkId);
+ utils.openUrl(tokenLink);
};
return (
- <a
- href={tokenLinkFromToken(this.props.tokenInfo, this.props.networkId)}
- target="_blank"
- style={style}
- onMouseEnter={this._onToggleHover.bind(this, true)}
- onMouseLeave={this._onToggleHover.bind(this, false)}
- onClick={onClick}
- >
+ <Text fontSize="14px" fontColor={colors.mediumBlue} onClick={onClick}>
{this.props.tokenInfo.symbol}
- </a>
+ </Text>
);
}
- private _onToggleHover(isHovering: boolean): void {
- this.setState({
- isHovering,
- });
+ private _tokenLinkFromToken(tokenInfo: WebsiteBackendTokenInfo, networkId: number): string {
+ return sharedUtils.getEtherScanLinkIfExists(tokenInfo.address, networkId, EtherscanLinkSuffixes.Address);
}
}
-
-function tokenLinkFromToken(tokenInfo: WebsiteBackendTokenInfo, networkId: number): string {
- return sharedUtils.getEtherScanLinkIfExists(tokenInfo.address, networkId, EtherscanLinkSuffixes.Address);
-}
diff --git a/packages/website/ts/components/top_bar/provider_display.tsx b/packages/website/ts/components/top_bar/provider_display.tsx
index 496e5cae0..806eaeea5 100644
--- a/packages/website/ts/components/top_bar/provider_display.tsx
+++ b/packages/website/ts/components/top_bar/provider_display.tsx
@@ -1,21 +1,26 @@
import { Styles } from '@0xproject/react-shared';
import * as _ from 'lodash';
import CircularProgress from 'material-ui/CircularProgress';
-import RaisedButton from 'material-ui/RaisedButton';
+import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet';
import * as React from 'react';
import { Blockchain } from 'ts/blockchain';
-import { ProviderPicker } from 'ts/components/top_bar/provider_picker';
+import { AccountConnection } from 'ts/components/ui/account_connection';
import { Container } from 'ts/components/ui/container';
import { DropDown } from 'ts/components/ui/drop_down';
import { Identicon } from 'ts/components/ui/identicon';
import { Image } from 'ts/components/ui/image';
import { Island } from 'ts/components/ui/island';
+import {
+ CopyAddressSimpleMenuItem,
+ DifferentWalletSimpleMenuItem,
+ GoToAccountManagementSimpleMenuItem,
+ SimpleMenu,
+} from 'ts/components/ui/simple_menu';
import { Text } from 'ts/components/ui/text';
import { Dispatcher } from 'ts/redux/dispatcher';
import { colors } from 'ts/style/colors';
-import { ProviderType } from 'ts/types';
-import { constants } from 'ts/utils/constants';
+import { AccountState, ProviderType } from 'ts/types';
import { utils } from 'ts/utils/utils';
const ROOT_HEIGHT = 24;
@@ -42,133 +47,108 @@ const styles: Styles = {
export class ProviderDisplay extends React.Component<ProviderDisplayProps, ProviderDisplayState> {
public render(): React.ReactNode {
- const isExternallyInjectedProvider = utils.isExternallyInjected(
- this.props.providerType,
- this.props.injectedProviderName,
- );
- const displayMessage = utils.getReadableAccountState(
- this._isBlockchainReady(),
- this.props.providerType,
- this.props.injectedProviderName,
- this.props.userAddress,
- );
- // If the "injected" provider is our fallback public node, then we want to
- // show the "connect a wallet" message instead of the providerName
- const injectedProviderName = isExternallyInjectedProvider
- ? this.props.injectedProviderName
- : 'Connect a wallet';
- const providerTitle =
- this.props.providerType === ProviderType.Injected ? injectedProviderName : 'Ledger Nano S';
- const isProviderMetamask = providerTitle === constants.PROVIDER_NAME_METAMASK;
- const hoverActiveNode = (
- <Island className="flex items-center p1" style={styles.root}>
- <div>
- {this._isBlockchainReady() ? (
- <Identicon address={this.props.userAddress} diameter={ROOT_HEIGHT} />
- ) : (
- <CircularProgress size={ROOT_HEIGHT} thickness={2} />
- )}
- </div>
+ const activeNode = (
+ <Island className="flex items-center py1 px2" style={styles.root}>
+ {this._renderIcon()}
<Container marginLeft="12px" marginRight="12px">
- <Text fontSize="14px" fontColor={colors.darkGrey}>
- {displayMessage}
- </Text>
+ {this._renderDisplayMessage()}
</Container>
- {isProviderMetamask && (
- <Image src="/images/metamask_icon.png" height={ROOT_HEIGHT} width={ROOT_HEIGHT} />
- )}
+ {this._renderInjectedProvider()}
</Island>
);
- const hasLedgerProvider = this.props.providerType === ProviderType.Ledger;
- const horizontalPosition = isExternallyInjectedProvider || hasLedgerProvider ? 'left' : 'middle';
return (
<div style={{ width: 'fit-content', height: 48, float: 'right' }}>
<DropDown
- hoverActiveNode={hoverActiveNode}
- popoverContent={this.renderPopoverContent(isExternallyInjectedProvider, hasLedgerProvider)}
- anchorOrigin={{ horizontal: horizontalPosition, vertical: 'bottom' }}
- targetOrigin={{ horizontal: horizontalPosition, vertical: 'top' }}
+ activeNode={activeNode}
+ popoverContent={this._renderPopoverContent()}
+ anchorOrigin={{ horizontal: 'middle', vertical: 'bottom' }}
+ targetOrigin={{ horizontal: 'middle', vertical: 'top' }}
zDepth={1}
/>
</div>
);
}
- public renderPopoverContent(hasInjectedProvider: boolean, hasLedgerProvider: boolean): React.ReactNode {
- if (!this._isBlockchainReady()) {
- return null;
- } else if (hasInjectedProvider || hasLedgerProvider) {
- return (
- <ProviderPicker
- dispatcher={this.props.dispatcher}
- networkId={this.props.networkId}
- injectedProviderName={this.props.injectedProviderName}
- providerType={this.props.providerType}
- onToggleLedgerDialog={this.props.onToggleLedgerDialog}
- blockchain={this.props.blockchain}
- />
- );
- } else {
- // Nothing to connect to, show install/info popover
- return (
- <div className="px2" style={{ maxWidth: 420 }}>
- <div className="center h4 py2" style={{ color: colors.grey700 }}>
- Choose a wallet:
- </div>
- <div className="flex pb3">
- <div className="center px2">
- <div style={{ color: colors.darkGrey }}>Install a browser wallet</div>
- <div className="py2">
- <img src="/images/metamask_or_parity.png" width="135" />
- </div>
- <div>
- Use{' '}
- <a
- href={constants.URL_METAMASK_CHROME_STORE}
- target="_blank"
- style={{ color: colors.lightBlueA700 }}
- >
- Metamask
- </a>{' '}
- or{' '}
- <a
- href={constants.URL_PARITY_CHROME_STORE}
- target="_blank"
- style={{ color: colors.lightBlueA700 }}
- >
- Parity Signer
- </a>
- </div>
- </div>
- <div>
- <div
- className="pl1 ml1"
- style={{ borderLeft: `1px solid ${colors.grey300}`, height: 65 }}
- />
- <div className="py1">or</div>
- <div
- className="pl1 ml1"
- style={{ borderLeft: `1px solid ${colors.grey300}`, height: 68 }}
- />
- </div>
- <div className="px2 center">
- <div style={{ color: colors.darkGrey }}>Connect to a ledger hardware wallet</div>
- <div style={{ paddingTop: 21, paddingBottom: 29 }}>
- <img src="/images/ledger_icon.png" style={{ width: 80 }} />
- </div>
- <div>
- <RaisedButton
- style={{ width: '100%' }}
- label="Use Ledger"
- onClick={this.props.onToggleLedgerDialog}
- />
- </div>
- </div>
- </div>
- </div>
- );
+ private _renderPopoverContent(): React.ReactNode {
+ const accountState = this._getAccountState();
+ switch (accountState) {
+ case AccountState.Ready:
+ return (
+ <SimpleMenu>
+ <CopyAddressSimpleMenuItem userAddress={this.props.userAddress} />
+ <DifferentWalletSimpleMenuItem onClick={this.props.onToggleLedgerDialog} />
+ <GoToAccountManagementSimpleMenuItem />
+ </SimpleMenu>
+ );
+ case AccountState.Disconnected:
+ case AccountState.Locked:
+ case AccountState.Loading:
+ default:
+ return null;
+ }
+ }
+ private _renderIcon(): React.ReactNode {
+ const accountState = this._getAccountState();
+ switch (accountState) {
+ case AccountState.Ready:
+ return <Identicon address={this.props.userAddress} diameter={ROOT_HEIGHT} />;
+ case AccountState.Loading:
+ return <CircularProgress size={ROOT_HEIGHT} thickness={2} />;
+ case AccountState.Locked:
+ return <Image src="/images/lock_icon.svg" height="20px" width="20px" />;
+ case AccountState.Disconnected:
+ return <ActionAccountBalanceWallet color={colors.mediumBlue} />;
+ default:
+ return null;
+ }
+ }
+ private _renderDisplayMessage(): React.ReactNode {
+ const accountState = this._getAccountState();
+ const displayMessage = utils.getReadableAccountState(accountState, this.props.userAddress);
+ const fontColor = this._getDisplayMessageFontColor();
+ return (
+ <Text fontSize="16px" fontColor={fontColor} fontWeight={500}>
+ {displayMessage}
+ </Text>
+ );
+ }
+ private _getDisplayMessageFontColor(): string {
+ const accountState = this._getAccountState();
+ switch (accountState) {
+ case AccountState.Loading:
+ return colors.darkGrey;
+ case AccountState.Ready:
+ case AccountState.Locked:
+ case AccountState.Disconnected:
+ default:
+ return colors.black;
+ }
+ }
+ private _renderInjectedProvider(): React.ReactNode {
+ const accountState = this._getAccountState();
+ switch (accountState) {
+ case AccountState.Ready:
+ case AccountState.Locked:
+ return (
+ <AccountConnection
+ accountState={accountState}
+ injectedProviderName={this.props.injectedProviderName}
+ />
+ );
+ case AccountState.Disconnected:
+ case AccountState.Loading:
+ default:
+ return null;
}
}
private _isBlockchainReady(): boolean {
return this.props.blockchainIsLoaded && !_.isUndefined(this.props.blockchain);
}
+ private _getAccountState(): AccountState {
+ return utils.getAccountState(
+ this._isBlockchainReady(),
+ this.props.providerType,
+ this.props.injectedProviderName,
+ this.props.userAddress,
+ );
+ }
}
diff --git a/packages/website/ts/components/top_bar/provider_picker.tsx b/packages/website/ts/components/top_bar/provider_picker.tsx
deleted file mode 100644
index 7937f2e9d..000000000
--- a/packages/website/ts/components/top_bar/provider_picker.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-import { colors, constants as sharedConstants } from '@0xproject/react-shared';
-import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton';
-import * as React from 'react';
-import { Blockchain } from 'ts/blockchain';
-import { Dispatcher } from 'ts/redux/dispatcher';
-import { ProviderType } from 'ts/types';
-
-interface ProviderPickerProps {
- networkId: number;
- injectedProviderName: string;
- providerType: ProviderType;
- onToggleLedgerDialog: () => void;
- dispatcher: Dispatcher;
- blockchain: Blockchain;
-}
-
-interface ProviderPickerState {}
-
-export class ProviderPicker extends React.Component<ProviderPickerProps, ProviderPickerState> {
- public render(): React.ReactNode {
- const isLedgerSelected = this.props.providerType === ProviderType.Ledger;
- const menuStyle = {
- padding: 10,
- paddingTop: 15,
- paddingBottom: 15,
- };
- // Show dropdown with two options
- return (
- <div style={{ width: 225, overflow: 'hidden' }}>
- <RadioButtonGroup name="provider" defaultSelected={this.props.providerType}>
- <RadioButton
- onClick={this._onProviderRadioChanged.bind(this, ProviderType.Injected)}
- style={{ ...menuStyle, backgroundColor: !isLedgerSelected && colors.grey50 }}
- value={ProviderType.Injected}
- label={this._renderLabel(this.props.injectedProviderName, !isLedgerSelected)}
- />
- <RadioButton
- onClick={this._onProviderRadioChanged.bind(this, ProviderType.Ledger)}
- style={{ ...menuStyle, backgroundColor: isLedgerSelected && colors.grey50 }}
- value={ProviderType.Ledger}
- label={this._renderLabel('Ledger Nano S', isLedgerSelected)}
- />
- </RadioButtonGroup>
- </div>
- );
- }
- private _renderLabel(title: string, shouldShowNetwork: boolean): React.ReactNode {
- const label = (
- <div className="flex">
- <div style={{ fontSize: 14 }}>{title}</div>
- {shouldShowNetwork && this._renderNetwork()}
- </div>
- );
- return label;
- }
- private _renderNetwork(): React.ReactNode {
- const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
- return (
- <div className="flex" style={{ marginTop: 1 }}>
- <div className="relative" style={{ width: 14, paddingLeft: 14 }}>
- <img
- src={`/images/network_icons/${networkName.toLowerCase()}.png`}
- className="absolute"
- style={{ top: 6, width: 10 }}
- />
- </div>
- <div style={{ color: colors.lightGrey, fontSize: 11 }}>{networkName}</div>
- </div>
- );
- }
- private _onProviderRadioChanged(value: string): void {
- if (value === ProviderType.Ledger) {
- this.props.onToggleLedgerDialog();
- } else {
- // tslint:disable-next-line:no-floating-promises
- this.props.blockchain.updateProviderToInjectedAsync();
- }
- }
-}
diff --git a/packages/website/ts/components/top_bar/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx
index fac6c131f..778536663 100644
--- a/packages/website/ts/components/top_bar/top_bar.tsx
+++ b/packages/website/ts/components/top_bar/top_bar.tsx
@@ -199,7 +199,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
cursor: 'pointer',
paddingTop: 16,
};
- const hoverActiveNode = (
+ const activeNode = (
<div className="flex relative" style={{ color: menuIconStyle.color }}>
<div style={{ paddingRight: 10 }}>{this.props.translate.get(Key.Developers, Deco.Cap)}</div>
<div className="absolute" style={{ paddingLeft: 3, right: 3, top: -2 }}>
@@ -224,7 +224,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
<div className={menuClasses}>
<div className="flex justify-between">
<DropDown
- hoverActiveNode={hoverActiveNode}
+ activeNode={activeNode}
popoverContent={popoverContent}
anchorOrigin={{ horizontal: 'middle', vertical: 'bottom' }}
targetOrigin={{ horizontal: 'middle', vertical: 'top' }}
diff --git a/packages/website/ts/components/ui/account_connection.tsx b/packages/website/ts/components/ui/account_connection.tsx
new file mode 100644
index 000000000..6d0b90922
--- /dev/null
+++ b/packages/website/ts/components/ui/account_connection.tsx
@@ -0,0 +1,40 @@
+import * as React from 'react';
+
+import { Circle } from 'ts/components/ui/circle';
+import { Container } from 'ts/components/ui/container';
+import { Text } from 'ts/components/ui/text';
+import { colors } from 'ts/style/colors';
+import { AccountState } from 'ts/types';
+
+export interface AccountConnectionProps {
+ accountState: AccountState;
+ injectedProviderName: string;
+}
+
+export const AccountConnection: React.StatelessComponent<AccountConnectionProps> = ({
+ accountState,
+ injectedProviderName,
+}) => {
+ return (
+ <Container className="flex items-center">
+ <Circle diameter={6} fillColor={getInjectedProviderColor(accountState)} />
+ <Container marginLeft="6px">
+ <Text fontSize="12px" lineHeight="14px" fontColor={colors.darkGrey}>
+ {injectedProviderName}
+ </Text>
+ </Container>
+ </Container>
+ );
+};
+
+const getInjectedProviderColor = (accountState: AccountState) => {
+ switch (accountState) {
+ case AccountState.Ready:
+ return colors.limeGreen;
+ case AccountState.Locked:
+ case AccountState.Loading:
+ case AccountState.Disconnected:
+ default:
+ return colors.red;
+ }
+};
diff --git a/packages/website/ts/components/ui/animation.tsx b/packages/website/ts/components/ui/animation.tsx
index 136f3d005..943e3bf28 100644
--- a/packages/website/ts/components/ui/animation.tsx
+++ b/packages/website/ts/components/ui/animation.tsx
@@ -14,21 +14,29 @@ const appearFromBottomFrames = keyframes`
position: fixed;
bottom: -500px;
left: 0px;
+ right: 0px;
}
to {
position: fixed;
bottom: 0px;
left: 0px;
+ right: 0px;
}
`;
+const stylesForAnimation: { [K in AnimationType]: string } = {
+ // Needed for safari
+ easeUpFromBottom: `position: fixed`,
+};
+
const animations: { [K in AnimationType]: string } = {
easeUpFromBottom: `${appearFromBottomFrames} 1s ease 0s 1 forwards`,
};
export const Animation = styled(PlainAnimation)`
animation: ${props => animations[props.type]};
+ ${props => stylesForAnimation[props.type]};
`;
Animation.displayName = 'Animation';
diff --git a/packages/website/ts/components/ui/button.tsx b/packages/website/ts/components/ui/button.tsx
index 02fa47480..1489a74a6 100644
--- a/packages/website/ts/components/ui/button.tsx
+++ b/packages/website/ts/components/ui/button.tsx
@@ -37,7 +37,7 @@ export const Button = styled(PlainButton)`
background-color: ${props => props.backgroundColor};
border: ${props => (props.borderColor ? `1px solid ${props.borderColor}` : 'none')};
&:hover {
- background-color: ${props => (!props.isDisabled ? darken(0.1, props.backgroundColor) : '')};
+ background-color: ${props => (!props.isDisabled ? darken(0.1, props.backgroundColor) : '')} !important;
}
&:active {
background-color: ${props => (!props.isDisabled ? darken(0.2, props.backgroundColor) : '')};
diff --git a/packages/website/ts/components/ui/circle.tsx b/packages/website/ts/components/ui/circle.tsx
new file mode 100644
index 000000000..75103d066
--- /dev/null
+++ b/packages/website/ts/components/ui/circle.tsx
@@ -0,0 +1,16 @@
+import * as React from 'react';
+
+export interface CircleProps {
+ className?: string;
+ diameter: number;
+ fillColor: string;
+}
+
+export const Circle: React.StatelessComponent<CircleProps> = ({ className, diameter, fillColor }) => {
+ const radius = diameter / 2;
+ return (
+ <svg className={className} height={diameter} width={diameter}>
+ <circle cx={radius} cy={radius} r={radius} fill={fillColor} />
+ </svg>
+ );
+};
diff --git a/packages/website/ts/components/ui/container.tsx b/packages/website/ts/components/ui/container.tsx
index a747ef01f..edbf8814b 100644
--- a/packages/website/ts/components/ui/container.tsx
+++ b/packages/website/ts/components/ui/container.tsx
@@ -14,7 +14,10 @@ export interface ContainerProps {
backgroundColor?: string;
borderRadius?: StringOrNum;
maxWidth?: StringOrNum;
+ maxHeight?: StringOrNum;
width?: StringOrNum;
+ height?: StringOrNum;
+ minWidth?: StringOrNum;
minHeight?: StringOrNum;
isHidden?: boolean;
className?: string;
diff --git a/packages/website/ts/components/ui/drop_down.tsx b/packages/website/ts/components/ui/drop_down.tsx
index 22cb942f8..4d5caef08 100644
--- a/packages/website/ts/components/ui/drop_down.tsx
+++ b/packages/website/ts/components/ui/drop_down.tsx
@@ -1,4 +1,4 @@
-import Popover, { PopoverAnimationVertical } from 'material-ui/Popover';
+import Popover from 'material-ui/Popover';
import * as React from 'react';
import { MaterialUIPosition } from 'ts/types';
@@ -7,13 +7,20 @@ const DEFAULT_STYLE = {
fontSize: 14,
};
-interface DropDownProps {
- hoverActiveNode: React.ReactNode;
+export enum DropdownMouseEvent {
+ Hover = 'hover',
+ Click = 'click',
+}
+
+export interface DropDownProps {
+ activeNode: React.ReactNode;
popoverContent: React.ReactNode;
anchorOrigin: MaterialUIPosition;
targetOrigin: MaterialUIPosition;
style?: React.CSSProperties;
zDepth?: number;
+ activateEvent?: DropdownMouseEvent;
+ closeEvent?: DropdownMouseEvent;
}
interface DropDownState {
@@ -25,6 +32,8 @@ export class DropDown extends React.Component<DropDownProps, DropDownState> {
public static defaultProps: Partial<DropDownProps> = {
style: DEFAULT_STYLE,
zDepth: 1,
+ activateEvent: DropdownMouseEvent.Hover,
+ closeEvent: DropdownMouseEvent.Hover,
};
private _isHovering: boolean;
private _popoverCloseCheckIntervalId: number;
@@ -58,46 +67,61 @@ export class DropDown extends React.Component<DropDownProps, DropDownState> {
onMouseEnter={this._onHover.bind(this)}
onMouseLeave={this._onHoverOff.bind(this)}
>
- {this.props.hoverActiveNode}
+ <div onClick={this._onActiveNodeClick.bind(this)}>{this.props.activeNode}</div>
<Popover
open={this.state.isDropDownOpen}
anchorEl={this.state.anchorEl}
anchorOrigin={this.props.anchorOrigin}
targetOrigin={this.props.targetOrigin}
onRequestClose={this._closePopover.bind(this)}
- useLayerForClickAway={false}
- animation={PopoverAnimationVertical}
+ useLayerForClickAway={this.props.closeEvent === DropdownMouseEvent.Click}
+ animated={false}
zDepth={this.props.zDepth}
>
- <div onMouseEnter={this._onHover.bind(this)} onMouseLeave={this._onHoverOff.bind(this)}>
+ <div
+ onMouseEnter={this._onHover.bind(this)}
+ onMouseLeave={this._onHoverOff.bind(this)}
+ onClick={this._closePopover.bind(this)}
+ >
{this.props.popoverContent}
</div>
</Popover>
</div>
);
}
+ private _onActiveNodeClick(event: React.FormEvent<HTMLInputElement>): void {
+ if (this.props.activateEvent === DropdownMouseEvent.Click) {
+ this.setState({
+ isDropDownOpen: true,
+ anchorEl: event.currentTarget,
+ });
+ }
+ }
private _onHover(event: React.FormEvent<HTMLInputElement>): void {
this._isHovering = true;
- this._checkIfShouldOpenPopover(event);
+ if (this.props.activateEvent === DropdownMouseEvent.Hover) {
+ this._checkIfShouldOpenPopover(event);
+ }
+ }
+ private _onHoverOff(): void {
+ this._isHovering = false;
}
private _checkIfShouldOpenPopover(event: React.FormEvent<HTMLInputElement>): void {
if (this.state.isDropDownOpen) {
return; // noop
}
-
this.setState({
isDropDownOpen: true,
anchorEl: event.currentTarget,
});
}
- private _onHoverOff(): void {
- this._isHovering = false;
- }
private _checkIfShouldClosePopover(): void {
- if (!this.state.isDropDownOpen || this._isHovering) {
+ if (!this.state.isDropDownOpen) {
return; // noop
}
- this._closePopover();
+ if (this.props.closeEvent === DropdownMouseEvent.Hover && !this._isHovering) {
+ this._closePopover();
+ }
}
private _closePopover(): void {
this.setState({
diff --git a/packages/website/ts/components/ui/identicon.tsx b/packages/website/ts/components/ui/identicon.tsx
index cc1655962..b5b374973 100644
--- a/packages/website/ts/components/ui/identicon.tsx
+++ b/packages/website/ts/components/ui/identicon.tsx
@@ -2,6 +2,7 @@ import blockies = require('blockies');
import * as _ from 'lodash';
import * as React from 'react';
+import { Circle } from 'ts/components/ui/circle';
import { Image } from 'ts/components/ui/image';
import { colors } from 'ts/style/colors';
@@ -20,7 +21,6 @@ export class Identicon extends React.Component<IdenticonProps, IdenticonState> {
public render(): React.ReactNode {
const address = this.props.address;
const diameter = this.props.diameter;
- const radius = diameter / 2;
return (
<div
className="circle relative transitionFix"
@@ -40,9 +40,7 @@ export class Identicon extends React.Component<IdenticonProps, IdenticonState> {
width={diameter}
/>
) : (
- <svg height={diameter} width={diameter}>
- <circle cx={radius} cy={radius} r={radius} fill={colors.grey200} />
- </svg>
+ <Circle diameter={diameter} fillColor={colors.grey200} />
)}
</div>
);
diff --git a/packages/website/ts/components/ui/overlay.tsx b/packages/website/ts/components/ui/overlay.tsx
index 8b126a6d5..da26317de 100644
--- a/packages/website/ts/components/ui/overlay.tsx
+++ b/packages/website/ts/components/ui/overlay.tsx
@@ -4,7 +4,6 @@ import * as React from 'react';
import { zIndex } from 'ts/style/z_index';
export interface OverlayProps {
- children?: React.ReactNode;
style?: React.CSSProperties;
onClick?: () => void;
}
@@ -19,7 +18,7 @@ const style: React.CSSProperties = {
backgroundColor: 'rgba(0, 0, 0, 0.6)',
};
-export const Overlay: React.StatelessComponent = (props: OverlayProps) => (
+export const Overlay: React.StatelessComponent<OverlayProps> = props => (
<div style={{ ...style, ...props.style }} onClick={props.onClick}>
{props.children}
</div>
diff --git a/packages/website/ts/components/ui/simple_menu.tsx b/packages/website/ts/components/ui/simple_menu.tsx
new file mode 100644
index 000000000..74b8ef6ae
--- /dev/null
+++ b/packages/website/ts/components/ui/simple_menu.tsx
@@ -0,0 +1,88 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import * as CopyToClipboard from 'react-copy-to-clipboard';
+import { Link } from 'react-router-dom';
+
+import { Container } from 'ts/components/ui/container';
+import { Text } from 'ts/components/ui/text';
+import { colors } from 'ts/style/colors';
+import { WebsitePaths } from 'ts/types';
+
+export interface SimpleMenuProps {
+ minWidth?: number | string;
+}
+
+export const SimpleMenu: React.StatelessComponent<SimpleMenuProps> = ({ children, minWidth }) => {
+ return (
+ <Container
+ marginLeft="16px"
+ marginRight="16px"
+ marginBottom="16px"
+ minWidth={minWidth}
+ className="flex flex-column"
+ >
+ {children}
+ </Container>
+ );
+};
+
+SimpleMenu.defaultProps = {
+ minWidth: '220px',
+};
+
+export interface SimpleMenuItemProps {
+ displayText: string;
+ onClick?: () => void;
+}
+export const SimpleMenuItem: React.StatelessComponent<SimpleMenuItemProps> = ({ displayText, onClick }) => {
+ // Falling back to _.noop for onclick retains the hovering effect
+ return (
+ <Container marginTop="16px" className="flex flex-column">
+ <Text
+ fontSize="14px"
+ fontColor={colors.darkGrey}
+ onClick={onClick || _.noop}
+ hoverColor={colors.mediumBlue}
+ >
+ {displayText}
+ </Text>
+ </Container>
+ );
+};
+
+export interface CopyAddressSimpleMenuItemProps {
+ userAddress: string;
+ onClick?: () => void;
+}
+export const CopyAddressSimpleMenuItem: React.StatelessComponent<CopyAddressSimpleMenuItemProps> = ({
+ userAddress,
+ onClick,
+}) => {
+ return (
+ <CopyToClipboard text={userAddress}>
+ <SimpleMenuItem displayText="Copy Address to Clipboard" onClick={onClick} />
+ </CopyToClipboard>
+ );
+};
+
+export interface GoToAccountManagementSimpleMenuItemProps {
+ onClick?: () => void;
+}
+export const GoToAccountManagementSimpleMenuItem: React.StatelessComponent<
+ GoToAccountManagementSimpleMenuItemProps
+> = ({ onClick }) => {
+ return (
+ <Link to={`${WebsitePaths.Portal}/account`} style={{ textDecoration: 'none' }}>
+ <SimpleMenuItem displayText="Manage Account..." onClick={onClick} />
+ </Link>
+ );
+};
+
+export interface DifferentWalletSimpleMenuItemProps {
+ onClick?: () => void;
+}
+export const DifferentWalletSimpleMenuItem: React.StatelessComponent<DifferentWalletSimpleMenuItemProps> = ({
+ onClick,
+}) => {
+ return <SimpleMenuItem displayText="Use a Different Wallet..." onClick={onClick} />;
+};
diff --git a/packages/website/ts/components/ui/text.tsx b/packages/website/ts/components/ui/text.tsx
index 1e2a123b7..315f72854 100644
--- a/packages/website/ts/components/ui/text.tsx
+++ b/packages/website/ts/components/ui/text.tsx
@@ -3,7 +3,7 @@ import { darken } from 'polished';
import * as React from 'react';
import { styled } from 'ts/style/theme';
-export type TextTag = 'p' | 'div' | 'span' | 'label' | 'h1' | 'h2' | 'h3' | 'h4';
+export type TextTag = 'p' | 'div' | 'span' | 'label' | 'h1' | 'h2' | 'h3' | 'h4' | 'i';
export interface TextProps {
className?: string;
@@ -15,7 +15,9 @@ export interface TextProps {
minHeight?: string;
center?: boolean;
fontWeight?: number | string;
- onClick?: () => void;
+ textDecorationLine?: string;
+ onClick?: (event: React.MouseEvent<HTMLElement>) => void;
+ hoverColor?: string;
}
const PlainText: React.StatelessComponent<TextProps> = ({ children, className, onClick, Tag }) => (
@@ -28,6 +30,7 @@ export const Text = styled(PlainText)`
font-family: ${props => props.fontFamily};
font-weight: ${props => props.fontWeight};
font-size: ${props => props.fontSize};
+ text-decoration-line: ${props => props.textDecorationLine};
${props => (props.lineHeight ? `line-height: ${props.lineHeight}` : '')};
${props => (props.center ? 'text-align: center' : '')};
color: ${props => props.fontColor};
@@ -35,7 +38,7 @@ export const Text = styled(PlainText)`
${props => (props.onClick ? 'cursor: pointer' : '')};
transition: color 0.5s ease;
&:hover {
- ${props => (props.onClick ? `color: ${darken(0.1, props.fontColor)}` : '')};
+ ${props => (props.onClick ? `color: ${props.hoverColor || darken(0.3, props.fontColor)}` : '')};
}
`;
@@ -45,6 +48,7 @@ Text.defaultProps = {
fontColor: colors.black,
fontSize: '15px',
lineHeight: '1.5em',
+ textDecorationLine: 'none',
Tag: 'div',
};
diff --git a/packages/website/ts/components/wallet/body_overlay.tsx b/packages/website/ts/components/wallet/body_overlay.tsx
new file mode 100644
index 000000000..5ced704f9
--- /dev/null
+++ b/packages/website/ts/components/wallet/body_overlay.tsx
@@ -0,0 +1,146 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+
+import { Blockchain } from 'ts/blockchain';
+import { Container } from 'ts/components/ui/container';
+import { Image } from 'ts/components/ui/image';
+import { Island } from 'ts/components/ui/island';
+import { Text } from 'ts/components/ui/text';
+import { Dispatcher } from 'ts/redux/dispatcher';
+import { colors } from 'ts/style/colors';
+import { styled } from 'ts/style/theme';
+import { AccountState, BrowserType, ProviderType } from 'ts/types';
+import { constants } from 'ts/utils/constants';
+import { utils } from 'ts/utils/utils';
+
+const METAMASK_IMG_SRC = '/images/metamask_icon.png';
+
+export interface BodyOverlayProps {
+ dispatcher: Dispatcher;
+ userAddress: string;
+ injectedProviderName: string;
+ providerType: ProviderType;
+ onToggleLedgerDialog: () => void;
+ blockchain?: Blockchain;
+ blockchainIsLoaded: boolean;
+}
+
+interface BodyOverlayState {}
+
+export class BodyOverlay extends React.Component<BodyOverlayProps, BodyOverlayState> {
+ public render(): React.ReactNode {
+ const accountState = this._getAccountState();
+ switch (accountState) {
+ case AccountState.Locked:
+ return <LockedOverlay onUseDifferentWalletClicked={this.props.onToggleLedgerDialog} />;
+ case AccountState.Disconnected:
+ return <DisconnectedOverlay onUseDifferentWalletClicked={this.props.onToggleLedgerDialog} />;
+ case AccountState.Ready:
+ case AccountState.Loading:
+ default:
+ return null;
+ }
+ }
+ private _isBlockchainReady(): boolean {
+ return this.props.blockchainIsLoaded && !_.isUndefined(this.props.blockchain);
+ }
+ private _getAccountState(): AccountState {
+ return utils.getAccountState(
+ this._isBlockchainReady(),
+ this.props.providerType,
+ this.props.injectedProviderName,
+ this.props.userAddress,
+ );
+ }
+}
+
+interface LockedOverlayProps {
+ className?: string;
+ onUseDifferentWalletClicked?: () => void;
+}
+const PlainLockedOverlay: React.StatelessComponent<LockedOverlayProps> = ({
+ className,
+ onUseDifferentWalletClicked,
+}) => (
+ <div className={className}>
+ <Container
+ className="flex flex-column items-center"
+ marginBottom="24px"
+ marginTop="24px"
+ marginLeft="48px"
+ marginRight="48px"
+ >
+ <Image src={METAMASK_IMG_SRC} height="70px" />
+ <Container marginTop="12px">
+ <Text fontColor={colors.metaMaskOrange} fontSize="16px" fontWeight="bold">
+ Please Unlock MetaMask
+ </Text>
+ </Container>
+ <UseDifferentWallet fontColor={colors.darkGrey} onClick={onUseDifferentWalletClicked} />
+ </Container>
+ </div>
+);
+const LockedOverlay = styled(PlainLockedOverlay)`
+ background: ${colors.metaMaskTransparentOrange};
+ border: 1px solid ${colors.metaMaskOrange};
+ border-radius: 10px;
+`;
+
+interface DisconnectedOverlayProps {
+ onUseDifferentWalletClicked?: () => void;
+}
+const DisconnectedOverlay = (props: DisconnectedOverlayProps) => {
+ return (
+ <div className="flex flex-column items-center">
+ <GetMetaMask />
+ <UseDifferentWallet fontColor={colors.mediumBlue} onClick={props.onUseDifferentWalletClicked} />
+ </div>
+ );
+};
+
+interface UseDifferentWallet {
+ fontColor: string;
+ onClick?: () => void;
+}
+const UseDifferentWallet = (props: UseDifferentWallet) => {
+ return (
+ <Container marginTop="12px">
+ <Text fontColor={props.fontColor} fontSize="16px" textDecorationLine="underline" onClick={props.onClick}>
+ Use a different wallet
+ </Text>
+ </Container>
+ );
+};
+
+const GetMetaMask = () => {
+ const browserType = utils.getBrowserType();
+ let extensionLink;
+ switch (browserType) {
+ case BrowserType.Chrome:
+ extensionLink = constants.URL_METAMASK_CHROME_STORE;
+ break;
+ case BrowserType.Firefox:
+ extensionLink = constants.URL_METAMASK_FIREFOX_STORE;
+ break;
+ case BrowserType.Opera:
+ extensionLink = constants.URL_METAMASK_OPERA_STORE;
+ break;
+ default:
+ extensionLink = constants.URL_METAMASK_HOMEPAGE;
+ }
+ return (
+ <a href={extensionLink} target="_blank" style={{ textDecoration: 'none' }}>
+ <Island
+ className="flex items-center py1 px2"
+ style={{ height: 28, borderRadius: 28, backgroundColor: colors.mediumBlue }}
+ >
+ <Image src={METAMASK_IMG_SRC} width="28px" />
+ <Container marginLeft="8px" marginRight="12px">
+ <Text fontColor={colors.white} fontSize="16px" fontWeight={500}>
+ Get MetaMask Wallet
+ </Text>
+ </Container>
+ </Island>
+ </a>
+ );
+};
diff --git a/packages/website/ts/components/wallet/null_token_row.tsx b/packages/website/ts/components/wallet/null_token_row.tsx
new file mode 100644
index 000000000..a1ec9871a
--- /dev/null
+++ b/packages/website/ts/components/wallet/null_token_row.tsx
@@ -0,0 +1,41 @@
+import * as React from 'react';
+
+import { Circle } from 'ts/components/ui/circle';
+import { Container } from 'ts/components/ui/container';
+import { Text } from 'ts/components/ui/text';
+import { PlaceHolder } from 'ts/components/wallet/placeholder';
+import { StandardIconRow } from 'ts/components/wallet/standard_icon_row';
+import { colors } from 'ts/style/colors';
+
+export interface NullTokenRowProps {
+ iconDimension: number;
+ fillColor: string;
+}
+
+export const NullTokenRow: React.StatelessComponent<NullTokenRowProps> = ({ iconDimension, fillColor }) => {
+ const icon = <Circle diameter={iconDimension} fillColor={fillColor} />;
+ const main = (
+ <div className="flex flex-column">
+ <PlaceHolder hideChildren={true} fillColor={fillColor}>
+ <Text fontSize="16px" fontWeight="bold" lineHeight="1em">
+ 0.00 XXX
+ </Text>
+ </PlaceHolder>
+ <Container marginTop="3px">
+ <PlaceHolder hideChildren={true} fillColor={fillColor}>
+ <Text fontSize="14px" fontColor={colors.darkGrey} lineHeight="1em">
+ $0.00
+ </Text>
+ </PlaceHolder>
+ </Container>
+ </div>
+ );
+ const accessory = (
+ <Container marginRight="12px">
+ <PlaceHolder hideChildren={true} fillColor={fillColor}>
+ <Container width="20px" height="14px" />
+ </PlaceHolder>
+ </Container>
+ );
+ return <StandardIconRow icon={icon} main={main} accessory={accessory} />;
+};
diff --git a/packages/website/ts/components/wallet/placeholder.tsx b/packages/website/ts/components/wallet/placeholder.tsx
new file mode 100644
index 000000000..bf40d2ea8
--- /dev/null
+++ b/packages/website/ts/components/wallet/placeholder.tsx
@@ -0,0 +1,25 @@
+import * as React from 'react';
+
+import { styled } from 'ts/style/theme';
+
+export interface PlaceHolderProps {
+ className?: string;
+ hideChildren: React.ReactNode;
+ fillColor: string;
+}
+
+const PlainPlaceHolder: React.StatelessComponent<PlaceHolderProps> = ({ className, hideChildren, children }) => {
+ const childrenVisibility = hideChildren ? 'hidden' : 'visible';
+ const childrenStyle: React.CSSProperties = { visibility: childrenVisibility };
+ return (
+ <div className={className}>
+ <div style={childrenStyle}>{children}</div>
+ </div>
+ );
+};
+
+export const PlaceHolder = styled(PlainPlaceHolder)`
+ background-color: ${props => (props.hideChildren ? props.fillColor : 'transparent')};
+ display: inline-block;
+ border-radius: 2px;
+`;
diff --git a/packages/website/ts/components/wallet/standard_icon_row.tsx b/packages/website/ts/components/wallet/standard_icon_row.tsx
new file mode 100644
index 000000000..1a2ec021b
--- /dev/null
+++ b/packages/website/ts/components/wallet/standard_icon_row.tsx
@@ -0,0 +1,44 @@
+import * as React from 'react';
+
+import { colors } from 'ts/style/colors';
+import { styled } from 'ts/style/theme';
+
+export interface StandardIconRowProps {
+ className?: string;
+ icon: React.ReactNode;
+ main: React.ReactNode;
+ accessory?: React.ReactNode;
+ minHeight?: string;
+ borderBottomColor?: string;
+ borderBottomStyle?: string;
+ borderWidth?: string;
+ backgroundColor?: string;
+}
+const PlainStandardIconRow: React.StatelessComponent<StandardIconRowProps> = ({ className, icon, main, accessory }) => {
+ return (
+ <div className={`flex items-center ${className}`}>
+ <div className="flex items-center px2">{icon}</div>
+ <div className="flex-none pr2">{main}</div>
+ <div className="flex-auto" />
+ <div>{accessory}</div>
+ </div>
+ );
+};
+
+export const StandardIconRow = styled(PlainStandardIconRow)`
+ min-height: ${props => props.minHeight};
+ border-bottom-color: ${props => props.borderBottomColor};
+ border-bottom-style: ${props => props.borderBottomStyle};
+ border-width: ${props => props.borderWidth};
+ background-color: ${props => props.backgroundColor};
+`;
+
+StandardIconRow.defaultProps = {
+ minHeight: '85px',
+ borderBottomColor: colors.walletBorder,
+ borderBottomStyle: 'solid',
+ borderWidth: '1px',
+ backgroundColor: colors.walletDefaultItemBackground,
+};
+
+StandardIconRow.displayName = 'StandardIconRow';
diff --git a/packages/website/ts/components/wallet/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx
index 785b2da88..1e0b9ec48 100644
--- a/packages/website/ts/components/wallet/wallet.tsx
+++ b/packages/website/ts/components/wallet/wallet.tsx
@@ -1,36 +1,37 @@
-import {
- constants as sharedConstants,
- EtherscanLinkSuffixes,
- Styles,
- utils as sharedUtils,
-} from '@0xproject/react-shared';
+import { constants as sharedConstants, EtherscanLinkSuffixes, utils as sharedUtils } from '@0xproject/react-shared';
import { BigNumber, errorUtils } from '@0xproject/utils';
-import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as _ from 'lodash';
-import CircularProgress from 'material-ui/CircularProgress';
-import FloatingActionButton from 'material-ui/FloatingActionButton';
-import { ListItem } from 'material-ui/List';
import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet';
-import ContentAdd from 'material-ui/svg-icons/content/add';
-import ContentRemove from 'material-ui/svg-icons/content/remove';
import * as React from 'react';
-import { Link } from 'react-router-dom';
import firstBy = require('thenby');
import { Blockchain } from 'ts/blockchain';
+import { AccountConnection } from 'ts/components/ui/account_connection';
import { Container } from 'ts/components/ui/container';
+import { DropDown, DropdownMouseEvent } from 'ts/components/ui/drop_down';
import { IconButton } from 'ts/components/ui/icon_button';
import { Identicon } from 'ts/components/ui/identicon';
import { Island } from 'ts/components/ui/island';
+import {
+ CopyAddressSimpleMenuItem,
+ DifferentWalletSimpleMenuItem,
+ GoToAccountManagementSimpleMenuItem,
+ SimpleMenu,
+ SimpleMenuItem,
+} from 'ts/components/ui/simple_menu';
+import { Text } from 'ts/components/ui/text';
import { TokenIcon } from 'ts/components/ui/token_icon';
-import { WalletDisconnectedItem } from 'ts/components/wallet/wallet_disconnected_item';
+import { BodyOverlay } from 'ts/components/wallet/body_overlay';
+import { NullTokenRow } from 'ts/components/wallet/null_token_row';
+import { PlaceHolder } from 'ts/components/wallet/placeholder';
+import { StandardIconRow } from 'ts/components/wallet/standard_icon_row';
import { WrapEtherItem } from 'ts/components/wallet/wrap_ether_item';
import { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle';
import { Dispatcher } from 'ts/redux/dispatcher';
import { colors } from 'ts/style/colors';
-import { styled } from 'ts/style/theme';
import {
+ AccountState,
BlockchainErrs,
ProviderType,
ScreenWidths,
@@ -39,12 +40,10 @@ import {
TokenByAddress,
TokenState,
TokenStateByAddress,
- WebsitePaths,
} from 'ts/types';
import { analytics } from 'ts/utils/analytics';
import { constants } from 'ts/utils/constants';
import { utils } from 'ts/utils/utils';
-import { styles as walletItemStyles } from 'ts/utils/wallet_item_styles';
export interface WalletProps {
userAddress: string;
@@ -84,66 +83,14 @@ interface AccessoryItemConfig {
allowanceToggleConfig?: AllowanceToggleConfig;
}
-const styles: Styles = {
- root: {
- width: '100%',
- },
- footerItemInnerDiv: {
- paddingLeft: 24,
- borderTopColor: colors.walletBorder,
- borderTopStyle: 'solid',
- borderWidth: 1,
- },
- borderedItem: {
- borderBottomColor: colors.walletBorder,
- borderBottomStyle: 'solid',
- borderWidth: 1,
- },
- tokenItem: {
- backgroundColor: colors.walletDefaultItemBackground,
- minHeight: 85,
- },
- amountLabel: {
- fontWeight: 'bold',
- color: colors.black,
- },
- valueLabel: {
- color: colors.grey,
- fontSize: 14,
- },
- paddedItem: {
- paddingTop: 8,
- paddingBottom: 8,
- },
- bodyInnerDiv: {
- overflow: 'auto',
- WebkitOverflowScrolling: 'touch',
- },
- manageYourWalletText: {
- color: colors.mediumBlue,
- fontWeight: 'bold',
- },
- loadingBody: {
- height: 381,
- },
-};
-
const ETHER_ICON_PATH = '/images/ether.png';
const ICON_DIMENSION = 28;
const BODY_ITEM_KEY = 'BODY';
const HEADER_ITEM_KEY = 'HEADER';
-const FOOTER_ITEM_KEY = 'FOOTER';
-const DISCONNECTED_ITEM_KEY = 'DISCONNECTED';
const ETHER_ITEM_KEY = 'ETHER';
-const USD_DECIMAL_PLACES = 2;
const NO_ALLOWANCE_TOGGLE_SPACE_WIDTH = 56;
-const ACCOUNT_PATH = `${WebsitePaths.Portal}/account`;
-
-const ActionButton = styled(FloatingActionButton)`
- button {
- position: static !important;
- }
-`;
+const PLACEHOLDER_COLOR = colors.grey300;
+const LOADING_ROWS_COUNT = 6;
export class Wallet extends React.Component<WalletProps, WalletState> {
public static defaultProps = {
@@ -171,72 +118,138 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
}
}
public render(): React.ReactNode {
- const isBlockchainLoaded = this.props.blockchainIsLoaded && this.props.blockchainErr === BlockchainErrs.NoError;
return (
- <Island className="flex flex-column wallet" style={{ ...styles.root, ...this.props.style }}>
- {isBlockchainLoaded ? this._renderLoadedRows() : this._renderLoadingRows()}
+ <Island className="flex flex-column wallet" style={this.props.style}>
+ {this._isBlockchainReady() ? this._renderLoadedRows() : this._renderLoadingRows()}
</Island>
);
}
- private _renderLoadedRows(): React.ReactNode {
- const isAddressAvailable = !_.isEmpty(this.props.userAddress);
- return isAddressAvailable
- ? _.concat(this._renderConnectedHeaderRows(), this._renderBody(), this._renderFooterRows())
- : _.concat(this._renderDisconnectedHeaderRows(), this._renderDisconnectedRows());
- }
private _renderLoadingRows(): React.ReactNode {
- return _.concat(this._renderDisconnectedHeaderRows(), this._renderLoadingBodyRows());
+ return _.concat(this._renderLoadingHeaderRows(), this._renderLoadingBodyRows());
+ }
+ private _renderLoadingHeaderRows(): React.ReactElement<{}> {
+ return this._renderPlainHeaderRow('Loading...');
}
private _renderLoadingBodyRows(): React.ReactElement<{}> {
+ const bodyStyle = this._getBodyStyle();
+ const loadingRowsRange = _.range(LOADING_ROWS_COUNT);
return (
- <div key={BODY_ITEM_KEY} className="flex items-center" style={styles.loadingBody}>
- <div className="mx-auto">
- <CircularProgress size={40} thickness={5} />
- </div>
+ <div key={BODY_ITEM_KEY} className="flex flex-column" style={bodyStyle}>
+ {_.map(loadingRowsRange, index => {
+ return <NullTokenRow key={index} iconDimension={ICON_DIMENSION} fillColor={PLACEHOLDER_COLOR} />;
+ })}
+ <Container
+ className="flex items-center"
+ position="absolute"
+ width="100%"
+ height="100%"
+ maxHeight={bodyStyle.maxHeight}
+ >
+ <div className="mx-auto">
+ <BodyOverlay
+ dispatcher={this.props.dispatcher}
+ userAddress={this.props.userAddress}
+ injectedProviderName={this.props.injectedProviderName}
+ providerType={this.props.providerType}
+ onToggleLedgerDialog={this.props.onToggleLedgerDialog}
+ blockchain={this.props.blockchain}
+ blockchainIsLoaded={this.props.blockchainIsLoaded}
+ />
+ </div>
+ </Container>
</div>
);
}
+ private _renderLoadedRows(): React.ReactNode {
+ const isAddressAvailable = !_.isEmpty(this.props.userAddress);
+ return isAddressAvailable
+ ? _.concat(this._renderConnectedHeaderRows(), this._renderBody())
+ : _.concat(this._renderDisconnectedHeaderRows(), this._renderLoadingBodyRows());
+ }
private _renderDisconnectedHeaderRows(): React.ReactElement<{}> {
- const primaryText = 'wallet';
- return (
- <StandardIconRow
- key={HEADER_ITEM_KEY}
- icon={<ActionAccountBalanceWallet color={colors.mediumBlue} />}
- main={primaryText.toUpperCase()}
- style={styles.borderedItem}
- />
+ const isExternallyInjectedProvider = utils.isExternallyInjected(
+ this.props.providerType,
+ this.props.injectedProviderName,
);
+ const text = isExternallyInjectedProvider ? 'Please unlock MetaMask...' : 'Please connect a wallet...';
+ return this._renderPlainHeaderRow(text);
}
- private _renderDisconnectedRows(): React.ReactElement<{}> {
+ private _renderPlainHeaderRow(text: string): React.ReactElement<{}> {
return (
- <WalletDisconnectedItem
- key={DISCONNECTED_ITEM_KEY}
- providerType={this.props.providerType}
- injectedProviderName={this.props.injectedProviderName}
- onToggleLedgerDialog={this.props.onToggleLedgerDialog}
+ <StandardIconRow
+ key={HEADER_ITEM_KEY}
+ icon={<ActionAccountBalanceWallet color={colors.grey} />}
+ main={
+ <Text fontSize="16px" fontColor={colors.grey}>
+ {text}
+ </Text>
+ // https://github.com/palantir/tslint-react/issues/140
+ // tslint:disable-next-line:jsx-curly-spacing
+ }
+ minHeight="60px"
+ backgroundColor={colors.white}
/>
);
}
private _renderConnectedHeaderRows(): React.ReactElement<{}> {
+ const isMobile = this.props.screenWidth === ScreenWidths.Sm;
const userAddress = this.props.userAddress;
- const primaryText = utils.getAddressBeginAndEnd(userAddress);
+ const accountState = this._getAccountState();
+ const main = (
+ <div className="flex flex-column">
+ <Text fontSize="16px" lineHeight="19px" fontWeight={500}>
+ {utils.getAddressBeginAndEnd(userAddress)}
+ </Text>
+ <AccountConnection accountState={accountState} injectedProviderName={this.props.injectedProviderName} />
+ </div>
+ );
+ const onClick = _.noop;
+ const accessory = (
+ <DropDown
+ activeNode={
+ // this container gives the menu button more of a hover target for the drop down
+ // it prevents accidentally closing the menu by moving off of the button
+ <Container paddingLeft="100px" paddingRight="15px">
+ <Text
+ className="zmdi zmdi-more-horiz"
+ Tag="i"
+ fontSize="32px"
+ fontFamily="Material-Design-Iconic-Font"
+ fontColor={colors.darkGrey}
+ onClick={onClick}
+ hoverColor={colors.mediumBlue}
+ />
+ </Container>
+ }
+ popoverContent={
+ <SimpleMenu minWidth="150px">
+ <CopyAddressSimpleMenuItem userAddress={this.props.userAddress} />
+ <DifferentWalletSimpleMenuItem onClick={this.props.onToggleLedgerDialog} />
+ <SimpleMenuItem displayText="Add Tokens..." onClick={this.props.onAddToken} />
+ <SimpleMenuItem displayText="Remove Tokens..." onClick={this.props.onRemoveToken} />
+ <GoToAccountManagementSimpleMenuItem />
+ </SimpleMenu>
+ }
+ anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
+ targetOrigin={{ horizontal: 'right', vertical: 'top' }}
+ zDepth={1}
+ activateEvent={DropdownMouseEvent.Click}
+ closeEvent={isMobile ? DropdownMouseEvent.Click : DropdownMouseEvent.Hover}
+ />
+ );
return (
- <Link key={HEADER_ITEM_KEY} to={ACCOUNT_PATH} style={{ textDecoration: 'none' }}>
- <StandardIconRow
- icon={<Identicon address={userAddress} diameter={ICON_DIMENSION} />}
- main={primaryText}
- style={styles.borderedItem}
- />
- </Link>
+ <StandardIconRow
+ key={HEADER_ITEM_KEY}
+ icon={<Identicon address={userAddress} diameter={ICON_DIMENSION} />}
+ main={main}
+ accessory={accessory}
+ minHeight="60px"
+ backgroundColor={colors.white}
+ />
);
}
private _renderBody(): React.ReactElement<{}> {
- const bodyStyle: React.CSSProperties = {
- ...styles.bodyInnerDiv,
- overflow: this.state.isHoveringSidebar ? 'auto' : 'hidden',
- // TODO: make this completely responsive
- maxHeight: this.props.screenWidth !== ScreenWidths.Sm ? 475 : undefined,
- };
+ const bodyStyle = this._getBodyStyle();
return (
<div
style={bodyStyle}
@@ -249,6 +262,17 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
</div>
);
}
+ private _getBodyStyle(): React.CSSProperties {
+ return {
+ overflow: 'auto',
+ WebkitOverflowScrolling: 'touch',
+ position: 'relative',
+ overflowY: this.state.isHoveringSidebar ? 'scroll' : 'hidden',
+ marginRight: this.state.isHoveringSidebar ? 0 : 4,
+ // TODO: make this completely responsive
+ maxHeight: this.props.screenWidth !== ScreenWidths.Sm ? 475 : undefined,
+ };
+ }
private _onSidebarHover(_event: React.FormEvent<HTMLInputElement>): void {
this.setState({
isHoveringSidebar: true,
@@ -259,51 +283,6 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
isHoveringSidebar: false,
});
}
- private _renderFooterRows(): React.ReactElement<{}> {
- return (
- <div key={FOOTER_ITEM_KEY}>
- <ListItem
- primaryText={
- <div className="flex">
- <ActionButton mini={true} zDepth={0} onClick={this.props.onAddToken}>
- <ContentAdd />
- </ActionButton>
- <ActionButton mini={true} zDepth={0} className="px1" onClick={this.props.onRemoveToken}>
- <ContentRemove />
- </ActionButton>
- <div
- style={{
- paddingLeft: 10,
- position: 'relative',
- top: '50%',
- transform: 'translateY(33%)',
- }}
- >
- add/remove tokens
- </div>
- </div>
- }
- disabled={true}
- innerDivStyle={styles.footerItemInnerDiv}
- style={styles.borderedItem}
- />
- {this.props.location.pathname !== ACCOUNT_PATH && (
- <Link to={ACCOUNT_PATH} style={{ textDecoration: 'none' }}>
- <ListItem
- primaryText={
- <div className="flex right" style={styles.manageYourWalletText}>
- manage your wallet
- </div>
- // https://github.com/palantir/tslint-react/issues/140
- // tslint:disable-next-line:jsx-curly-spacing
- }
- style={{ ...styles.paddedItem, ...styles.borderedItem }}
- />
- </Link>
- )}
- </div>
- );
- }
private _renderEthRows(): React.ReactNode {
const icon = <img style={{ width: ICON_DIMENSION, height: ICON_DIMENSION }} src={ETHER_ICON_PATH} />;
const primaryText = this._renderAmount(
@@ -325,7 +304,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
wrappedEtherDirection: Side.Deposit,
};
const key = ETHER_ITEM_KEY;
- return this._renderBalanceRow(key, icon, primaryText, secondaryText, accessoryItemConfig, false, 'eth-row');
+ return this._renderBalanceRow(key, icon, primaryText, secondaryText, accessoryItemConfig);
}
private _renderTokenRows(): React.ReactNode {
const trackedTokens = this.props.trackedTokens;
@@ -336,7 +315,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
);
return _.map(trackedTokensStartingWithEtherToken, this._renderTokenRow.bind(this));
}
- private _renderTokenRow(token: Token, index: number): React.ReactNode {
+ private _renderTokenRow(token: Token): React.ReactNode {
const tokenState = this.props.trackedTokenStateByAddress[token.address];
if (_.isUndefined(tokenState)) {
return null;
@@ -364,16 +343,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
},
};
const key = token.address;
- const isLastRow = index === this.props.trackedTokens.length - 1;
- return this._renderBalanceRow(
- key,
- icon,
- primaryText,
- secondaryText,
- accessoryItemConfig,
- isLastRow,
- isWeth ? 'weth-row' : undefined,
- );
+ return this._renderBalanceRow(key, icon, primaryText, secondaryText, accessoryItemConfig);
}
private _renderBalanceRow(
key: string,
@@ -381,23 +351,31 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
primaryText: React.ReactNode,
secondaryText: React.ReactNode,
accessoryItemConfig: AccessoryItemConfig,
- isLastRow: boolean,
className?: string,
): React.ReactNode {
const shouldShowWrapEtherItem =
!_.isUndefined(this.state.wrappedEtherDirection) &&
this.state.wrappedEtherDirection === accessoryItemConfig.wrappedEtherDirection &&
!_.isUndefined(this.props.userEtherBalanceInWei);
- let additionalStyle;
- if (shouldShowWrapEtherItem) {
- additionalStyle = walletItemStyles.focusedItem;
- } else if (!isLastRow) {
- additionalStyle = styles.borderedItem;
- }
- const style = { ...styles.tokenItem, ...additionalStyle };
const etherToken = this._getEthToken();
+ const wrapEtherItem = shouldShowWrapEtherItem ? (
+ <WrapEtherItem
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
+ blockchain={this.props.blockchain}
+ dispatcher={this.props.dispatcher}
+ userEtherBalanceInWei={this.props.userEtherBalanceInWei}
+ direction={accessoryItemConfig.wrappedEtherDirection}
+ etherToken={etherToken}
+ lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
+ onConversionSuccessful={this._closeWrappedEtherActionRow.bind(this)}
+ // tslint:disable:jsx-no-lambda
+ refetchEthTokenStateAsync={async () => this.props.refetchTokenStateAsync(etherToken.address)}
+ />
+ ) : null;
return (
<div id={key} key={key} className={`flex flex-column ${className || ''}`}>
+ {this.state.wrappedEtherDirection === Side.Receive && wrapEtherItem}
<StandardIconRow
icon={icon}
main={
@@ -407,23 +385,8 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
</div>
}
accessory={this._renderAccessoryItems(accessoryItemConfig)}
- style={style}
/>
- {shouldShowWrapEtherItem && (
- <WrapEtherItem
- userAddress={this.props.userAddress}
- networkId={this.props.networkId}
- blockchain={this.props.blockchain}
- dispatcher={this.props.dispatcher}
- userEtherBalanceInWei={this.props.userEtherBalanceInWei}
- direction={accessoryItemConfig.wrappedEtherDirection}
- etherToken={etherToken}
- lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
- onConversionSuccessful={this._closeWrappedEtherActionRow.bind(this)}
- // tslint:disable:jsx-no-lambda
- refetchEthTokenStateAsync={async () => this.props.refetchTokenStateAsync(etherToken.address)}
- />
- )}
+ {this.state.wrappedEtherDirection === Side.Deposit && wrapEtherItem}
</div>
);
}
@@ -466,13 +429,19 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
): React.ReactNode {
if (isLoading) {
return (
- <PlaceHolder hideChildren={isLoading}>
- <div style={styles.amountLabel}>0.00 XXX</div>
+ <PlaceHolder hideChildren={isLoading} fillColor={PLACEHOLDER_COLOR}>
+ <Text fontSize="16px" fontWeight="bold" lineHeight="1em">
+ 0.00 XXX
+ </Text>
</PlaceHolder>
);
} else {
const result = utils.getFormattedAmount(amount, decimals, symbol);
- return <div style={styles.amountLabel}>{result}</div>;
+ return (
+ <Text fontSize="16px" fontWeight="bold" lineHeight="1em">
+ {result}
+ </Text>
+ );
}
}
private _renderValue(
@@ -481,22 +450,16 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
price?: BigNumber,
isLoading: boolean = false,
): React.ReactNode {
- let result;
- if (!isLoading) {
- if (_.isUndefined(price)) {
- result = '--';
- } else {
- const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals);
- const value = unitAmount.mul(price);
- const formattedAmount = value.toFixed(USD_DECIMAL_PLACES);
- result = `$${formattedAmount}`;
- }
- } else {
- result = '$0.00';
- }
+ const result = !isLoading
+ ? _.isUndefined(price)
+ ? '--'
+ : utils.getUsdValueFormattedAmount(amount, decimals, price)
+ : '$0.00';
return (
- <PlaceHolder hideChildren={isLoading}>
- <div style={styles.valueLabel}>{result}</div>
+ <PlaceHolder hideChildren={isLoading} fillColor={PLACEHOLDER_COLOR}>
+ <Text fontSize="14px" fontColor={colors.darkGrey} lineHeight="1em">
+ {result}
+ </Text>
</PlaceHolder>
);
}
@@ -549,41 +512,17 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
private _getEthToken(): Token {
return utils.getEthToken(this.props.tokenByAddress);
}
+ private _isBlockchainReady(): boolean {
+ return this.props.blockchainIsLoaded && !_.isUndefined(this.props.blockchain);
+ }
+ private _getAccountState(): AccountState {
+ return utils.getAccountState(
+ this._isBlockchainReady(),
+ this.props.providerType,
+ this.props.injectedProviderName,
+ this.props.userAddress,
+ );
+ }
}
-interface StandardIconRowProps {
- icon: React.ReactNode;
- main: React.ReactNode;
- accessory?: React.ReactNode;
- style?: React.CSSProperties;
-}
-const StandardIconRow = (props: StandardIconRowProps) => {
- return (
- <div className="flex items-center" style={props.style}>
- <div className="p2">{props.icon}</div>
- <div className="flex-none pr2 pt2 pb2">{props.main}</div>
- <div className="flex-auto" />
- <div>{props.accessory}</div>
- </div>
- );
-};
-interface PlaceHolderProps {
- hideChildren: React.ReactNode;
- children?: React.ReactNode;
-}
-const PlaceHolder = (props: PlaceHolderProps) => {
- const rootBackgroundColor = props.hideChildren ? colors.lightGrey : 'transparent';
- const rootStyle: React.CSSProperties = {
- backgroundColor: rootBackgroundColor,
- display: 'inline-block',
- borderRadius: 2,
- };
- const childrenVisibility = props.hideChildren ? 'hidden' : 'visible';
- const childrenStyle: React.CSSProperties = { visibility: childrenVisibility };
- return (
- <div style={rootStyle}>
- <div style={childrenStyle}>{props.children}</div>
- </div>
- );
-};
// tslint:disable:max-file-line-count
diff --git a/packages/website/ts/components/wallet/wallet_disconnected_item.tsx b/packages/website/ts/components/wallet/wallet_disconnected_item.tsx
deleted file mode 100644
index 024b28544..000000000
--- a/packages/website/ts/components/wallet/wallet_disconnected_item.tsx
+++ /dev/null
@@ -1,100 +0,0 @@
-import { Styles } from '@0xproject/react-shared';
-import FlatButton from 'material-ui/FlatButton';
-import * as React from 'react';
-
-import { colors } from 'ts/style/colors';
-import { BrowserType, ProviderType } from 'ts/types';
-import { constants } from 'ts/utils/constants';
-import { utils } from 'ts/utils/utils';
-
-export interface WalletDisconnectedItemProps {
- providerType: ProviderType;
- injectedProviderName: string;
- onToggleLedgerDialog: () => void;
-}
-
-const styles: Styles = {
- button: {
- border: colors.walletBorder,
- borderStyle: 'solid',
- borderWidth: 1,
- height: 80,
- },
- hrefAdjustment: {
- paddingTop: 20, // HACK: For some reason when we set the href prop of a FlatButton material-ui reduces the top padding
- },
- otherWalletText: {
- fontSize: 14,
- color: colors.grey500,
- textDecoration: 'underline',
- },
-};
-
-const ITEM_HEIGHT = 381;
-const METAMASK_ICON_WIDTH = 35;
-const LEDGER_ICON_WIDTH = 30;
-const BUTTON_BOTTOM_PADDING = 80;
-
-export const WalletDisconnectedItem: React.StatelessComponent<WalletDisconnectedItemProps> = (
- props: WalletDisconnectedItemProps,
-) => {
- const isExternallyInjectedProvider = utils.isExternallyInjected(props.providerType, props.injectedProviderName);
- return (
- <div className="flex flex-center">
- <div className="mx-auto">
- <div className="table" style={{ height: ITEM_HEIGHT }}>
- <div className="table-cell align-middle">
- <ProviderButton isExternallyInjectedProvider={isExternallyInjectedProvider} />
- <div className="flex flex-center py2" style={{ paddingBottom: BUTTON_BOTTOM_PADDING }}>
- <div className="mx-auto">
- <div onClick={props.onToggleLedgerDialog} style={{ cursor: 'pointer' }}>
- <img src="/images/ledger_icon.png" style={{ width: LEDGER_ICON_WIDTH }} />
- <span className="px1" style={styles.otherWalletText}>
- user other wallet
- </span>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- );
-};
-
-interface ProviderButtonProps {
- isExternallyInjectedProvider: boolean;
-}
-
-const ProviderButton: React.StatelessComponent<ProviderButtonProps> = (props: ProviderButtonProps) => {
- const browserType = utils.getBrowserType();
- let extensionLink;
- if (!props.isExternallyInjectedProvider) {
- switch (browserType) {
- case BrowserType.Chrome:
- extensionLink = constants.URL_METAMASK_CHROME_STORE;
- break;
- case BrowserType.Firefox:
- extensionLink = constants.URL_METAMASK_FIREFOX_STORE;
- break;
- case BrowserType.Opera:
- extensionLink = constants.URL_METAMASK_OPERA_STORE;
- break;
- default:
- extensionLink = constants.URL_METAMASK_HOMEPAGE;
- }
- }
- return (
- <FlatButton
- label={props.isExternallyInjectedProvider ? 'Please unlock account' : 'Get Metamask Wallet Extension'}
- labelStyle={{ color: colors.black }}
- labelPosition="after"
- primary={true}
- icon={<img src="/images/metamask_icon.png" width={METAMASK_ICON_WIDTH.toString()} />}
- style={props.isExternallyInjectedProvider ? styles.button : { ...styles.button, ...styles.hrefAdjustment }}
- href={extensionLink}
- target={props.isExternallyInjectedProvider ? undefined : '_blank'}
- disabled={props.isExternallyInjectedProvider}
- />
- );
-};
diff --git a/packages/website/ts/components/wallet/wrap_ether_item.tsx b/packages/website/ts/components/wallet/wrap_ether_item.tsx
index d6135ce4d..2b4cf93fe 100644
--- a/packages/website/ts/components/wallet/wrap_ether_item.tsx
+++ b/packages/website/ts/components/wallet/wrap_ether_item.tsx
@@ -8,6 +8,7 @@ import * as React from 'react';
import { Blockchain } from 'ts/blockchain';
import { EthAmountInput } from 'ts/components/inputs/eth_amount_input';
import { TokenAmountInput } from 'ts/components/inputs/token_amount_input';
+import { Container } from 'ts/components/ui/container';
import { Dispatcher } from 'ts/redux/dispatcher';
import { colors } from 'ts/style/colors';
import { BlockchainCallErrs, Side, Token } from 'ts/types';
@@ -15,7 +16,6 @@ import { analytics } from 'ts/utils/analytics';
import { constants } from 'ts/utils/constants';
import { errorReporter } from 'ts/utils/error_reporter';
import { utils } from 'ts/utils/utils';
-import { styles as walletItemStyles } from 'ts/utils/wallet_item_styles';
export interface WrapEtherItemProps {
userAddress: string;
@@ -95,7 +95,7 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther
const topLabelText = isWrappingEth ? 'Convert ETH into WETH 1:1' : 'Convert WETH into ETH 1:1';
return (
- <div className="flex" style={walletItemStyles.focusedItem}>
+ <Container className="flex" backgroundColor={colors.walletFocusedItemBackground} paddingTop="25px">
<div>{this._renderIsEthConversionHappeningSpinner()} </div>
<div className="flex flex-column">
<div style={styles.topLabel}>{topLabelText}</div>
@@ -143,7 +143,7 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther
{this._renderErrorMsg()}
</div>
- </div>
+ </Container>
);
}
private _onValueChange(_isValid: boolean, amount?: BigNumber): void {
@@ -173,6 +173,7 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther
<FlatButton
backgroundColor={colors.wrapEtherConfirmationButton}
label={labelText}
+ style={{ zIndex: 0 }}
labelStyle={styles.wrapEtherConfirmationButtonLabel}
onClick={this._wrapEtherConfirmationActionAsync.bind(this)}
disabled={this.state.isEthConversionHappening}
diff --git a/packages/website/ts/containers/portal_onboarding_flow.ts b/packages/website/ts/containers/portal_onboarding_flow.ts
index 12daad021..a813205b1 100644
--- a/packages/website/ts/containers/portal_onboarding_flow.ts
+++ b/packages/website/ts/containers/portal_onboarding_flow.ts
@@ -19,7 +19,7 @@ interface ConnectedState {
stepIndex: number;
isRunning: boolean;
userAddress: string;
- hasBeenSeen: boolean;
+ hasBeenClosed: boolean;
providerType: ProviderType;
injectedProviderName: string;
blockchainIsLoaded: boolean;
@@ -43,7 +43,7 @@ const mapStateToProps = (state: State, _ownProps: PortalOnboardingFlowProps): Co
blockchainIsLoaded: state.blockchainIsLoaded,
userEtherBalanceInWei: state.userEtherBalanceInWei,
tokenByAddress: state.tokenByAddress,
- hasBeenSeen: state.hasPortalOnboardingBeenSeen,
+ hasBeenClosed: state.hasPortalOnboardingBeenClosed,
screenWidth: state.screenWidth,
});
diff --git a/packages/website/ts/containers/subproviders_documentation.ts b/packages/website/ts/containers/subproviders_documentation.ts
index 6d4230e53..567f6a37e 100644
--- a/packages/website/ts/containers/subproviders_documentation.ts
+++ b/packages/website/ts/containers/subproviders_documentation.ts
@@ -25,6 +25,7 @@ const docSections = {
emptyWalletSubprovider: 'emptyWalletSubprovider',
fakeGasEstimateSubprovider: 'fakeGasEstimateSubprovider',
injectedWeb3Subprovider: 'injectedWeb3Subprovider',
+ signerSubprovider: 'signerSubprovider',
redundantRPCSubprovider: 'redundantRPCSubprovider',
ganacheSubprovider: 'ganacheSubprovider',
nonceTrackerSubprovider: 'nonceTrackerSubprovider',
@@ -50,6 +51,7 @@ const docsInfoConfig: DocsInfoConfig = {
['emptyWallet-subprovider']: [docSections.emptyWalletSubprovider],
['fakeGasEstimate-subprovider']: [docSections.fakeGasEstimateSubprovider],
['injectedWeb3-subprovider']: [docSections.injectedWeb3Subprovider],
+ ['signer-subprovider']: [docSections.signerSubprovider],
['redundantRPC-subprovider']: [docSections.redundantRPCSubprovider],
['ganache-subprovider']: [docSections.ganacheSubprovider],
['nonceTracker-subprovider']: [docSections.nonceTrackerSubprovider],
@@ -69,6 +71,7 @@ const docsInfoConfig: DocsInfoConfig = {
[docSections.emptyWalletSubprovider]: ['"subproviders/src/subproviders/empty_wallet_subprovider"'],
[docSections.fakeGasEstimateSubprovider]: ['"subproviders/src/subproviders/fake_gas_estimate_subprovider"'],
[docSections.injectedWeb3Subprovider]: ['"subproviders/src/subproviders/injected_web3"'],
+ [docSections.signerSubprovider]: ['"subproviders/src/subproviders/signer"'],
[docSections.redundantRPCSubprovider]: ['"subproviders/src/subproviders/redundant_rpc"'],
[docSections.ganacheSubprovider]: ['"subproviders/src/subproviders/ganache"'],
[docSections.nonceTrackerSubprovider]: ['"subproviders/src/subproviders/nonce_tracker"'],
diff --git a/packages/website/ts/index.tsx b/packages/website/ts/index.tsx
index 23387a95a..c7ccfdf1f 100644
--- a/packages/website/ts/index.tsx
+++ b/packages/website/ts/index.tsx
@@ -97,7 +97,6 @@ render(
<Route path={WebsitePaths.FAQ} component={FAQ as any} />
<Route path={WebsitePaths.About} component={About as any} />
<Route path={WebsitePaths.Wiki} component={Wiki as any} />
- <Route path={`${WebsitePaths.Docs}`} component={LazyZeroExJSDocumentation} />
<Route path={`${WebsitePaths.ZeroExJs}/:version?`} component={LazyZeroExJSDocumentation} />
<Route path={`${WebsitePaths.Connect}/:version?`} component={LazyConnectDocumentation} />
<Route
@@ -144,6 +143,7 @@ render(
component={LazySolCompilerDocumentation}
/>
+ <Route path={`${WebsitePaths.Docs}`} component={LazyZeroExJSDocumentation} />
<Route component={NotFound as any} />
</Switch>
</div>
diff --git a/packages/website/ts/pages/about/about.tsx b/packages/website/ts/pages/about/about.tsx
index 0259af36f..5bb5d06a9 100644
--- a/packages/website/ts/pages/about/about.tsx
+++ b/packages/website/ts/pages/about/about.tsx
@@ -165,16 +165,24 @@ const teamRow5: ProfileInfo[] = [
},
];
-// const teamRow6: ProfileInfo[] = [
-// {
-// name: 'Chris Kalani',
-// title: 'Director of Design',
-// description: `Previously founded Wake (acquired by InVision). Early Facebook product designer.`,
-// image: 'images/team/chris.png',
-// linkedIn: 'https://www.linkedin.com/in/chriskalani/',
-// github: 'https://github.com/chriskalani',
-// },
-// ];
+const teamRow6: ProfileInfo[] = [
+ {
+ name: 'Alex Browne',
+ title: 'Engineer in Residence',
+ description: `Full-stack blockchain engineer. Previously at Plaid. Open source guru and footgun dismantler. Computer Science and Electrical Engineering at Duke.`,
+ image: 'images/team/alexbrowne.png',
+ linkedIn: 'https://www.linkedin.com/in/stephenalexbrowne/',
+ github: 'http://github.com/albrow',
+ },
+ // {
+ // name: 'Chris Kalani',
+ // title: 'Director of Design',
+ // description: `Previously founded Wake (acquired by InVision). Early Facebook product designer.`,
+ // image: 'images/team/chris.png',
+ // linkedIn: 'https://www.linkedin.com/in/chriskalani/',
+ // github: 'https://github.com/chriskalani',
+ // },
+];
const advisors: ProfileInfo[] = [
{
@@ -270,6 +278,7 @@ export class About extends React.Component<AboutProps, AboutState> {
<div className="clearfix">{this._renderProfiles(teamRow3)}</div>
<div className="clearfix">{this._renderProfiles(teamRow4)}</div>
<div className="clearfix">{this._renderProfiles(teamRow5)}</div>
+ <div className="clearfix">{this._renderProfiles(teamRow6)}</div>
</div>
<div className="pt3 pb2">
<div
diff --git a/packages/website/ts/pages/jobs/list/list_item.tsx b/packages/website/ts/pages/jobs/list/list_item.tsx
index d7838bc01..192433d39 100644
--- a/packages/website/ts/pages/jobs/list/list_item.tsx
+++ b/packages/website/ts/pages/jobs/list/list_item.tsx
@@ -1,14 +1,14 @@
import * as React from 'react';
+import { Circle } from 'ts/components/ui/circle';
+
export interface ListItemProps {
bulletColor?: string;
}
export const ListItem: React.StatelessComponent<ListItemProps> = ({ bulletColor, children }) => {
return (
<div className="flex items-center">
- <svg className="flex-none lg-px2 md-px2 sm-pl2" height="26" width="26">
- <circle cx="13" cy="13" r="13" fill={bulletColor || 'transparent'} />
- </svg>
+ <Circle className="flex-none lg-px2 md-px2 sm-pl2" diameter={26} fillColor={bulletColor || 'transparent'} />
<div className="flex-auto px2">{children}</div>
</div>
);
diff --git a/packages/website/ts/redux/reducer.ts b/packages/website/ts/redux/reducer.ts
index ed6a4868e..caddabcf0 100644
--- a/packages/website/ts/redux/reducer.ts
+++ b/packages/website/ts/redux/reducer.ts
@@ -42,7 +42,7 @@ export interface State {
userEtherBalanceInWei?: BigNumber;
portalOnboardingStep: number;
isPortalOnboardingShowing: boolean;
- hasPortalOnboardingBeenSeen: boolean;
+ hasPortalOnboardingBeenClosed: boolean;
// Note: cache of supplied orderJSON in fill order step. Do not use for anything else.
userSuppliedOrderCache: Order;
@@ -85,7 +85,7 @@ export const INITIAL_STATE: State = {
userSuppliedOrderCache: undefined,
portalOnboardingStep: 0,
isPortalOnboardingShowing: false,
- hasPortalOnboardingBeenSeen: false,
+ hasPortalOnboardingBeenClosed: false,
// Docs
docsVersion: DEFAULT_DOCS_VERSION,
availableDocVersions: [DEFAULT_DOCS_VERSION],
@@ -311,7 +311,9 @@ export function reducer(state: State = INITIAL_STATE, action: Action): State {
return {
...state,
isPortalOnboardingShowing,
- hasPortalOnboardingBeenSeen: true,
+ hasPortalOnboardingBeenClosed: !isPortalOnboardingShowing ? true : state.hasPortalOnboardingBeenClosed,
+ // always start onboarding from the beginning
+ portalOnboardingStep: 0,
};
}
diff --git a/packages/website/ts/redux/store.ts b/packages/website/ts/redux/store.ts
index 203f068a1..2672e3f61 100644
--- a/packages/website/ts/redux/store.ts
+++ b/packages/website/ts/redux/store.ts
@@ -13,9 +13,11 @@ export const store: ReduxStore<State> = createStore(
);
store.subscribe(
_.throttle(() => {
+ const state = store.getState();
// Persisted state
stateStorage.saveState({
- hasPortalOnboardingBeenSeen: store.getState().hasPortalOnboardingBeenSeen,
+ hasPortalOnboardingBeenClosed: state.hasPortalOnboardingBeenClosed,
+ isPortalOnboardingShowing: state.isPortalOnboardingShowing,
});
}, ONE_SECOND),
);
diff --git a/packages/website/ts/style/colors.ts b/packages/website/ts/style/colors.ts
index 45be4fe7f..349845a09 100644
--- a/packages/website/ts/style/colors.ts
+++ b/packages/website/ts/style/colors.ts
@@ -6,13 +6,13 @@ const appColors = {
walletDefaultItemBackground: '#fbfbfc',
walletFocusedItemBackground: '#f0f1f4',
allowanceToggleShadow: 'rgba(0, 0, 0, 0)',
- allowanceToggleOffTrack: '#adadad',
- allowanceToggleOnTrack: sharedColors.mediumBlue,
wrapEtherConfirmationButton: sharedColors.mediumBlue,
drawerMenuBackground: '#4a4a4a',
menuItemDefaultSelectedBackground: '#424242',
jobsPageBackground: sharedColors.grey50,
jobsPageOpenPositionRow: sharedColors.grey100,
+ metaMaskOrange: '#f68c24',
+ metaMaskTransparentOrange: 'rgba(255, 248, 242, 0.8)',
};
export const colors = {
diff --git a/packages/website/ts/style/media.ts b/packages/website/ts/style/media.ts
new file mode 100644
index 000000000..3c992eb9f
--- /dev/null
+++ b/packages/website/ts/style/media.ts
@@ -0,0 +1,14 @@
+import { css } from 'ts/style/theme';
+import { ScreenWidths } from 'ts/types';
+
+const generateMediaWrapper = (screenWidth: ScreenWidths) => (...args: any[]) => css`
+ @media (max-width: ${screenWidth}) {
+ ${css.apply(css, args)};
+ }
+`;
+
+export const media = {
+ small: generateMediaWrapper(ScreenWidths.Sm),
+ medium: generateMediaWrapper(ScreenWidths.Md),
+ large: generateMediaWrapper(ScreenWidths.Lg),
+};
diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts
index 3211ddbd0..d9b2ef618 100644
--- a/packages/website/ts/types.ts
+++ b/packages/website/ts/types.ts
@@ -1,5 +1,6 @@
import { ECSignature } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
+import { Provider } from 'ethereum-types';
import * as React from 'react';
export enum Side {
@@ -215,10 +216,11 @@ export interface ContractEvent {
}
export type ValidatedBigNumberCallback = (isValid: boolean, amount?: BigNumber) => void;
+// Associated values are in `em` units
export enum ScreenWidths {
- Sm = 'SM',
- Md = 'MD',
- Lg = 'LG',
+ Sm = 40,
+ Md = 52,
+ Lg = 64,
}
export enum AlertTypes {
@@ -487,6 +489,8 @@ export enum Providers {
Parity = 'PARITY',
Metamask = 'METAMASK',
Mist = 'MIST',
+ Toshi = 'TOSHI',
+ Cipher = 'CIPHER',
}
export interface InjectedProviderUpdate {
@@ -564,4 +568,23 @@ export enum BrowserType {
Opera = 'Opera',
Other = 'Other',
}
+
+export enum AccountState {
+ Disconnected = 'Disconnected',
+ Ready = 'Ready',
+ Loading = 'Loading',
+ Locked = 'Locked',
+}
+
+export interface InjectedProvider extends Provider {
+ publicConfigStore?: InjectedProviderObservable;
+}
+
+// Minimal expected interface for an injected web3 object
+export interface InjectedWeb3 {
+ currentProvider: InjectedProvider;
+ version: {
+ getNetwork(cd: (err: Error, networkId: string) => void): void;
+ };
+}
// tslint:disable:max-file-line-count
diff --git a/packages/website/ts/utils/analytics.ts b/packages/website/ts/utils/analytics.ts
index 928e45bc3..f4bfa083f 100644
--- a/packages/website/ts/utils/analytics.ts
+++ b/packages/website/ts/utils/analytics.ts
@@ -1,8 +1,8 @@
import * as _ from 'lodash';
import * as ReactGA from 'react-ga';
+import { InjectedWeb3 } from 'ts/types';
import { configs } from 'ts/utils/configs';
import { utils } from 'ts/utils/utils';
-import * as Web3 from 'web3';
export const analytics = {
init(): void {
@@ -16,11 +16,12 @@ export const analytics = {
value,
});
},
- async logProviderAsync(web3IfExists: Web3): Promise<void> {
+ async logProviderAsync(web3IfExists: InjectedWeb3): Promise<void> {
await utils.onPageLoadAsync();
- const providerType = !_.isUndefined(web3IfExists)
- ? utils.getProviderType(web3IfExists.currentProvider)
- : 'NONE';
+ const providerType =
+ !_.isUndefined(web3IfExists) && !_.isUndefined(web3IfExists.currentProvider)
+ ? utils.getProviderType(web3IfExists.currentProvider)
+ : 'NONE';
ReactGA.ga('set', 'dimension1', providerType);
},
};
diff --git a/packages/website/ts/utils/configs.ts b/packages/website/ts/utils/configs.ts
index 2f89f8ccb..e8a486c35 100644
--- a/packages/website/ts/utils/configs.ts
+++ b/packages/website/ts/utils/configs.ts
@@ -11,7 +11,7 @@ const INFURA_API_KEY = 'T5WSC8cautR4KXyYgsRs';
export const configs = {
AMOUNT_DISPLAY_PRECSION: 5,
BACKEND_BASE_PROD_URL: 'https://website-api.0xproject.com',
- BACKEND_BASE_STAGING_URL: 'http://ec2-52-91-181-85.compute-1.amazonaws.com',
+ BACKEND_BASE_STAGING_URL: 'https://staging-website-api.0xproject.com',
BASE_URL,
BITLY_ACCESS_TOKEN: 'ffc4c1a31e5143848fb7c523b39f91b9b213d208',
DEFAULT_DERIVATION_PATH: `44'/60'/0'`,
diff --git a/packages/website/ts/utils/constants.ts b/packages/website/ts/utils/constants.ts
index a3f8eacb0..0c4b88780 100644
--- a/packages/website/ts/utils/constants.ts
+++ b/packages/website/ts/utils/constants.ts
@@ -6,7 +6,7 @@ export const constants = {
ETHER_TOKEN_SYMBOL: 'WETH',
ZRX_TOKEN_SYMBOL: 'ZRX',
ETHER_SYMBOL: 'ETH',
- TOKEN_AMOUNT_DISPLAY_PRECISION: 5,
+ TOKEN_AMOUNT_DISPLAY_PRECISION: 4,
GENESIS_ORDER_BLOCK_BY_NETWORK_ID: {
1: 4145578,
42: 3117574,
@@ -26,9 +26,11 @@ export const constants = {
NETWORK_ID_TESTRPC: 50,
NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
PROVIDER_NAME_LEDGER: 'Ledger',
- PROVIDER_NAME_METAMASK: 'Metamask',
+ PROVIDER_NAME_METAMASK: 'MetaMask',
PROVIDER_NAME_PARITY_SIGNER: 'Parity Signer',
PROVIDER_NAME_MIST: 'Mist',
+ PROVIDER_NAME_CIPHER: 'Cipher Browser',
+ PROVIDER_NAME_TOSHI: 'Toshi',
PROVIDER_NAME_GENERIC: 'Injected Web3',
PROVIDER_NAME_PUBLIC: '0x Public',
ROLLBAR_ACCESS_TOKEN: 'a6619002b51c4464928201e6ea94de65',
@@ -38,6 +40,7 @@ export const constants = {
UNAVAILABLE_STATUS: 503,
TAKER_FEE: new BigNumber(0),
TESTNET_NAME: 'Kovan',
+ NUMERAL_USD_FORMAT: '$0,0.00',
PROJECT_URL_ETHFINEX: 'https://www.ethfinex.com/',
PROJECT_URL_AMADEUS: 'http://amadeusrelay.org',
PROJECT_URL_DDEX: 'https://ddex.io',
diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts
index d12ae6a98..2cda41545 100644
--- a/packages/website/ts/utils/utils.ts
+++ b/packages/website/ts/utils/utils.ts
@@ -8,7 +8,10 @@ import * as bowser from 'bowser';
import deepEqual = require('deep-equal');
import * as _ from 'lodash';
import * as moment from 'moment';
+import * as numeral from 'numeral';
+
import {
+ AccountState,
BlockchainCallErrs,
BrowserType,
Environments,
@@ -26,9 +29,6 @@ import { configs } from 'ts/utils/configs';
import { constants } from 'ts/utils/constants';
import * as u2f from 'ts/vendor/u2f_api';
-const LG_MIN_EM = 64;
-const MD_MIN_EM = 52;
-
const isDogfood = (): boolean => _.includes(window.location.href, configs.DOMAIN_DOGFOOD);
export const utils = {
@@ -133,9 +133,9 @@ export const utils = {
// This logic mirrors the CSS media queries in BassCSS for the `lg-`, `md-` and `sm-` CSS
// class prefixes. Do not edit these.
- if (widthInEm > LG_MIN_EM) {
+ if (widthInEm > ScreenWidths.Lg) {
return ScreenWidths.Lg;
- } else if (widthInEm > MD_MIN_EM) {
+ } else if (widthInEm > ScreenWidths.Md) {
return ScreenWidths.Md;
} else {
return ScreenWidths.Sm;
@@ -192,23 +192,37 @@ export const utils = {
const truncatedAddress = `${address.substring(0, 6)}...${address.substr(-4)}`; // 0x3d5a...b287
return truncatedAddress;
},
- getReadableAccountState(
+ getReadableAccountState(accountState: AccountState, userAddress: string): string {
+ switch (accountState) {
+ case AccountState.Loading:
+ return 'Loading...';
+ case AccountState.Ready:
+ return utils.getAddressBeginAndEnd(userAddress);
+ case AccountState.Locked:
+ return 'Please Unlock';
+ case AccountState.Disconnected:
+ return 'Connect a Wallet';
+ default:
+ return '';
+ }
+ },
+ getAccountState(
isBlockchainReady: boolean,
providerType: ProviderType,
injectedProviderName: string,
userAddress?: string,
- ): string {
+ ): AccountState {
const isAddressAvailable = !_.isUndefined(userAddress) && !_.isEmpty(userAddress);
const isExternallyInjectedProvider = utils.isExternallyInjected(providerType, injectedProviderName);
if (!isBlockchainReady) {
- return 'Loading account';
+ return AccountState.Loading;
} else if (isAddressAvailable) {
- return utils.getAddressBeginAndEnd(userAddress);
+ return AccountState.Ready;
// tslint:disable-next-line: prefer-conditional-expression
} else if (isExternallyInjectedProvider) {
- return 'Account locked';
+ return AccountState.Locked;
} else {
- return 'No wallet detected';
+ return AccountState.Disconnected;
}
},
hasUniqueNameAndSymbol(tokens: Token[], token: Token): boolean {
@@ -309,6 +323,7 @@ export const utils = {
getProviderType(provider: Provider): Providers | string {
const constructorName = provider.constructor.name;
let parsedProviderName = constructorName;
+ // https://ethereum.stackexchange.com/questions/24266/elegant-way-to-detect-current-provider-int-web3-js
switch (constructorName) {
case 'EthereumProvider':
parsedProviderName = Providers.Mist;
@@ -322,6 +337,10 @@ export const utils = {
parsedProviderName = Providers.Parity;
} else if ((provider as any).isMetaMask) {
parsedProviderName = Providers.Metamask;
+ } else if (!_.isUndefined(_.get(window, 'SOFA'))) {
+ parsedProviderName = Providers.Toshi;
+ } else if (!_.isUndefined(_.get(window, '__CIPHER__'))) {
+ parsedProviderName = Providers.Cipher;
}
return parsedProviderName;
},
@@ -365,10 +384,20 @@ export const utils = {
},
getFormattedAmount(amount: BigNumber, decimals: number, symbol: string): string {
const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals);
- const precision = Math.min(constants.TOKEN_AMOUNT_DISPLAY_PRECISION, unitAmount.decimalPlaces());
- const formattedAmount = unitAmount.toFixed(precision);
+ // if the unit amount is less than 1, show the natural number of decimal places with a max of 4
+ // if the unit amount is greater than or equal to 1, show only 2 decimal places
+ const precision = unitAmount.lt(1)
+ ? Math.min(constants.TOKEN_AMOUNT_DISPLAY_PRECISION, unitAmount.decimalPlaces())
+ : 2;
+ const format = `0,0.${_.repeat('0', precision)}`;
+ const formattedAmount = numeral(unitAmount).format(format);
return `${formattedAmount} ${symbol}`;
},
+ getUsdValueFormattedAmount(amount: BigNumber, decimals: number, price: BigNumber): string {
+ const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals);
+ const value = unitAmount.mul(price);
+ return numeral(value).format(constants.NUMERAL_USD_FORMAT);
+ },
openUrl(url: string): void {
window.open(url, '_blank');
},
diff --git a/packages/website/ts/utils/wallet_item_styles.ts b/packages/website/ts/utils/wallet_item_styles.ts
deleted file mode 100644
index 9d6033d74..000000000
--- a/packages/website/ts/utils/wallet_item_styles.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Styles } from '@0xproject/react-shared';
-
-import { colors } from 'ts/style/colors';
-
-export const styles: Styles = {
- focusedItem: {
- backgroundColor: colors.walletFocusedItemBackground,
- },
-};