From 6de68f20ad5a3e40804239dcc0ddf41a82c31d96 Mon Sep 17 00:00:00 2001 From: XatMassacrE Date: Tue, 24 Dec 2019 15:42:27 +0800 Subject: [PATCH] Add UI code --- electron.js | 4 +- package.json | 11 + src/assets/extension_logo.svg | 1 + src/assets/iconfont/demo.css | 540 +++++++++++ src/assets/iconfont/demo_index.html | 354 +++++++ src/assets/iconfont/iconfont.css | 52 + src/assets/iconfont/iconfont.eot | Bin 0 -> 2828 bytes src/assets/iconfont/iconfont.js | 66 ++ src/assets/iconfont/iconfont.svg | 50 + src/assets/iconfont/iconfont.ttf | Bin 0 -> 2660 bytes src/assets/iconfont/iconfont.woff | Bin 0 -> 1716 bytes src/assets/iconfont/iconfont.woff2 | Bin 0 -> 1284 bytes src/assets/loading.gif | Bin 0 -> 15025 bytes src/assets/logo.jpg | Bin 0 -> 8741 bytes src/assets/switch.svg | 16 + src/assets/testnet.svg | 13 + src/assets/variables.scss | 16 + src/assets/warning.png | Bin 0 -> 22584 bytes src/components/DotInCenterStr/index.js | 18 + src/components/ErrorMessage/index.js | 14 + src/components/ErrorMessage/index.scss | 12 + src/components/Icon/index.js | 8 + src/components/NameAndPassword/index.js | 104 ++ src/components/StaticWarning/index.js | 21 + src/components/StaticWarning/index.scss | 25 + src/components/WarningMessage/index.js | 12 + src/components/WarningMessage/index.scss | 12 + src/index.js | 2 +- src/messaging/index.js | 37 + src/pages/App.js | 93 ++ src/pages/CreateAccount/createAccount.scss | 81 ++ src/pages/CreateAccount/index.js | 155 +++ src/pages/EnterPassword/enterPassword.scss | 17 + src/pages/EnterPassword/index.js | 75 ++ src/pages/Header/header.scss | 263 +++++ src/pages/Header/index.js | 345 +++++++ src/pages/Home.js | 157 +++ src/pages/ImportAccount/importAccount.scss | 71 ++ src/pages/ImportAccount/index.js | 121 +++ src/pages/NodeAction/index.js | 131 +++ src/pages/NodeAction/nodeAction.scss | 24 + src/pages/RequestSign/AssetsProcess.js | 45 + src/pages/RequestSign/CommonTx.js | 40 + src/pages/RequestSign/Staking.js | 74 ++ src/pages/RequestSign/Trade.js | 72 ++ src/pages/RequestSign/Transfer.js | 27 + src/pages/RequestSign/index.js | 253 +++++ src/pages/RequestSign/requestSign.scss | 125 +++ src/pages/ShowPrivateKey/index.js | 17 + src/pages/ShowPrivateKey/showPrivateKey.scss | 27 + src/pages/index.scss | 275 ++++++ src/shared/chainx.js | 17 + src/shared/constants.js | 3 + src/shared/createStore.js | 36 + src/shared/extensionExtrinsic.js | 28 + src/shared/fetch.js | 52 + src/shared/index.js | 9 + src/shared/shallowEqual.js | 36 + src/shared/signHelper.js | 69 ++ src/shared/toPrecision.js | 8 + src/shared/updateNodeStatus.js | 125 +++ src/shared/useClickOutside.js | 19 + src/shared/useRedux.js | 48 + src/store/reducers/index.js | 6 + src/store/reducers/intentionSlice.js | 45 + src/store/reducers/statusSlice.js | 29 + src/store/reducers/tradeSlice.js | 52 + yarn.lock | 968 ++++++++++++++++++- 68 files changed, 5399 insertions(+), 27 deletions(-) create mode 100755 src/assets/extension_logo.svg create mode 100644 src/assets/iconfont/demo.css create mode 100644 src/assets/iconfont/demo_index.html create mode 100644 src/assets/iconfont/iconfont.css create mode 100644 src/assets/iconfont/iconfont.eot create mode 100644 src/assets/iconfont/iconfont.js create mode 100644 src/assets/iconfont/iconfont.svg create mode 100644 src/assets/iconfont/iconfont.ttf create mode 100644 src/assets/iconfont/iconfont.woff create mode 100644 src/assets/iconfont/iconfont.woff2 create mode 100644 src/assets/loading.gif create mode 100644 src/assets/logo.jpg create mode 100644 src/assets/switch.svg create mode 100755 src/assets/testnet.svg create mode 100644 src/assets/variables.scss create mode 100644 src/assets/warning.png create mode 100644 src/components/DotInCenterStr/index.js create mode 100644 src/components/ErrorMessage/index.js create mode 100644 src/components/ErrorMessage/index.scss create mode 100644 src/components/Icon/index.js create mode 100644 src/components/NameAndPassword/index.js create mode 100644 src/components/StaticWarning/index.js create mode 100644 src/components/StaticWarning/index.scss create mode 100644 src/components/WarningMessage/index.js create mode 100644 src/components/WarningMessage/index.scss create mode 100644 src/messaging/index.js create mode 100644 src/pages/App.js create mode 100644 src/pages/CreateAccount/createAccount.scss create mode 100644 src/pages/CreateAccount/index.js create mode 100644 src/pages/EnterPassword/enterPassword.scss create mode 100644 src/pages/EnterPassword/index.js create mode 100644 src/pages/Header/header.scss create mode 100644 src/pages/Header/index.js create mode 100644 src/pages/Home.js create mode 100644 src/pages/ImportAccount/importAccount.scss create mode 100644 src/pages/ImportAccount/index.js create mode 100644 src/pages/NodeAction/index.js create mode 100644 src/pages/NodeAction/nodeAction.scss create mode 100644 src/pages/RequestSign/AssetsProcess.js create mode 100644 src/pages/RequestSign/CommonTx.js create mode 100644 src/pages/RequestSign/Staking.js create mode 100644 src/pages/RequestSign/Trade.js create mode 100644 src/pages/RequestSign/Transfer.js create mode 100644 src/pages/RequestSign/index.js create mode 100644 src/pages/RequestSign/requestSign.scss create mode 100644 src/pages/ShowPrivateKey/index.js create mode 100644 src/pages/ShowPrivateKey/showPrivateKey.scss create mode 100644 src/pages/index.scss create mode 100644 src/shared/chainx.js create mode 100644 src/shared/constants.js create mode 100644 src/shared/createStore.js create mode 100644 src/shared/extensionExtrinsic.js create mode 100644 src/shared/fetch.js create mode 100644 src/shared/index.js create mode 100644 src/shared/shallowEqual.js create mode 100644 src/shared/signHelper.js create mode 100644 src/shared/toPrecision.js create mode 100644 src/shared/updateNodeStatus.js create mode 100644 src/shared/useClickOutside.js create mode 100644 src/shared/useRedux.js create mode 100644 src/store/reducers/intentionSlice.js create mode 100644 src/store/reducers/statusSlice.js create mode 100644 src/store/reducers/tradeSlice.js diff --git a/electron.js b/electron.js index 865b527..4e937d4 100644 --- a/electron.js +++ b/electron.js @@ -9,8 +9,8 @@ let mainWindow function createWindow() { mainWindow = new BrowserWindow({ - width: 1440, - height: 1000, + width: 358, + height: 600, webPreferences: { preload: path.join(__dirname, 'preload.js') } }) mainWindow.loadURL( diff --git a/package.json b/package.json index 943014c..0ce3ddd 100644 --- a/package.json +++ b/package.json @@ -5,17 +5,26 @@ "main": "electron.js", "dependencies": { "@chainx/types": "^2.22.25", + "@chainx/ui": "^0.0.33", + "@chainx/util": "^2.22.2", "@reduxjs/toolkit": "^1.0.4", + "bignumber.js": "^9.0.0", "chainx.js": "^2.22.28", + "clipboard": "^2.0.4", "electron-is-dev": "^1.1.0", "electron-store": "^5.1.0", "jest": "^24.9.0", + "lodash.shuffle": "^4.2.0", "mock-socket": "^9.0.2", "react": "^16.12.0", "react-dom": "^16.12.0", "react-redux": "^7.1.3", + "react-router": "^5.1.2", + "react-router-dom": "^5.0.1", + "react-tooltip": "^3.11.1", "react-scripts": "3.2.0", "redux": "^4.0.4", + "redux-starter-kit": "^0.9.1", "ws": "^7.2.0" }, "scripts": { @@ -45,7 +54,9 @@ "electron": "^7.1.2", "husky": "^3.1.0", "lint-staged": "^9.4.3", + "node-sass": "^4.13.0", "prettier": "^1.19.1", + "sass-loader": "^8.0.0", "wait-on": "^3.3.0" }, "husky": { diff --git a/src/assets/extension_logo.svg b/src/assets/extension_logo.svg new file mode 100755 index 0000000..59bcf08 --- /dev/null +++ b/src/assets/extension_logo.svg @@ -0,0 +1 @@ +资源 43 \ No newline at end of file diff --git a/src/assets/iconfont/demo.css b/src/assets/iconfont/demo.css new file mode 100644 index 0000000..54f3c06 --- /dev/null +++ b/src/assets/iconfont/demo.css @@ -0,0 +1,540 @@ +/* Logo 字体 */ +@font-face { + font-family: 'iconfont logo'; + src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834'); + src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') + format('embedded-opentype'), + url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') + format('woff'), + url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') + format('truetype'), + url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') + format('svg'); +} + +.logo { + font-family: 'iconfont logo'; + font-size: 160px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* tabs */ +.nav-tabs { + position: relative; +} + +.nav-tabs .nav-more { + position: absolute; + right: 0; + bottom: 0; + height: 42px; + line-height: 42px; + color: #666; +} + +#tabs { + border-bottom: 1px solid #eee; +} + +#tabs li { + cursor: pointer; + width: 100px; + height: 40px; + line-height: 40px; + text-align: center; + font-size: 16px; + border-bottom: 2px solid transparent; + position: relative; + z-index: 1; + margin-bottom: -1px; + color: #666; +} + +#tabs .active { + border-bottom-color: #f00; + color: #222; +} + +.tab-container .content { + display: none; +} + +/* 页面布局 */ +.main { + padding: 30px 100px; + width: 960px; + margin: 0 auto; +} + +.main .logo { + color: #333; + text-align: left; + margin-bottom: 30px; + line-height: 1; + height: 110px; + margin-top: -50px; + overflow: hidden; + *zoom: 1; +} + +.main .logo a { + font-size: 160px; + color: #333; +} + +.helps { + margin-top: 40px; +} + +.helps pre { + padding: 20px; + margin: 10px 0; + border: solid 1px #e7e1cd; + background-color: #fffdef; + overflow: auto; +} + +.icon_lists { + width: 100% !important; + overflow: hidden; + *zoom: 1; +} + +.icon_lists li { + width: 100px; + margin-bottom: 10px; + margin-right: 20px; + text-align: center; + list-style: none !important; + cursor: default; +} + +.icon_lists li .code-name { + line-height: 1.2; +} + +.icon_lists .icon { + display: block; + height: 100px; + line-height: 100px; + font-size: 42px; + margin: 10px auto; + color: #333; + -webkit-transition: font-size 0.25s linear, width 0.25s linear; + -moz-transition: font-size 0.25s linear, width 0.25s linear; + transition: font-size 0.25s linear, width 0.25s linear; +} + +.icon_lists .icon:hover { + font-size: 100px; +} + +.icon_lists .svg-icon { + /* 通过设置 font-size 来改变图标大小 */ + width: 1em; + /* 图标和文字相邻时,垂直对齐 */ + vertical-align: -0.15em; + /* 通过设置 color 来改变 SVG 的颜色/fill */ + fill: currentColor; + /* path 和 stroke 溢出 viewBox 部分在 IE 下会显示 + normalize.css 中也包含这行 */ + overflow: hidden; +} + +.icon_lists li .name, +.icon_lists li .code-name { + color: #666; +} + +/* markdown 样式 */ +.markdown { + color: #666; + font-size: 14px; + line-height: 1.8; +} + +.highlight { + line-height: 1.5; +} + +.markdown img { + vertical-align: middle; + max-width: 100%; +} + +.markdown h1 { + color: #404040; + font-weight: 500; + line-height: 40px; + margin-bottom: 24px; +} + +.markdown h2, +.markdown h3, +.markdown h4, +.markdown h5, +.markdown h6 { + color: #404040; + margin: 1.6em 0 0.6em 0; + font-weight: 500; + clear: both; +} + +.markdown h1 { + font-size: 28px; +} + +.markdown h2 { + font-size: 22px; +} + +.markdown h3 { + font-size: 16px; +} + +.markdown h4 { + font-size: 14px; +} + +.markdown h5 { + font-size: 12px; +} + +.markdown h6 { + font-size: 12px; +} + +.markdown hr { + height: 1px; + border: 0; + background: #e9e9e9; + margin: 16px 0; + clear: both; +} + +.markdown p { + margin: 1em 0; +} + +.markdown > p, +.markdown > blockquote, +.markdown > .highlight, +.markdown > ol, +.markdown > ul { + width: 80%; +} + +.markdown ul > li { + list-style: circle; +} + +.markdown > ul li, +.markdown blockquote ul > li { + margin-left: 20px; + padding-left: 4px; +} + +.markdown > ul li p, +.markdown > ol li p { + margin: 0.6em 0; +} + +.markdown ol > li { + list-style: decimal; +} + +.markdown > ol li, +.markdown blockquote ol > li { + margin-left: 20px; + padding-left: 4px; +} + +.markdown code { + margin: 0 3px; + padding: 0 5px; + background: #eee; + border-radius: 3px; +} + +.markdown strong, +.markdown b { + font-weight: 600; +} + +.markdown > table { + border-collapse: collapse; + border-spacing: 0px; + empty-cells: show; + border: 1px solid #e9e9e9; + width: 95%; + margin-bottom: 24px; +} + +.markdown > table th { + white-space: nowrap; + color: #333; + font-weight: 600; +} + +.markdown > table th, +.markdown > table td { + border: 1px solid #e9e9e9; + padding: 8px 16px; + text-align: left; +} + +.markdown > table th { + background: #f7f7f7; +} + +.markdown blockquote { + font-size: 90%; + color: #999; + border-left: 4px solid #e9e9e9; + padding-left: 0.8em; + margin: 1em 0; +} + +.markdown blockquote p { + margin: 0; +} + +.markdown .anchor { + opacity: 0; + transition: opacity 0.3s ease; + margin-left: 8px; +} + +.markdown .waiting { + color: #ccc; +} + +.markdown h1:hover .anchor, +.markdown h2:hover .anchor, +.markdown h3:hover .anchor, +.markdown h4:hover .anchor, +.markdown h5:hover .anchor, +.markdown h6:hover .anchor { + opacity: 1; + display: inline-block; +} + +.markdown > br, +.markdown > p > br { + clear: both; +} + +.hljs { + display: block; + background: white; + padding: 0.5em; + color: #333333; + overflow-x: auto; +} + +.hljs-comment, +.hljs-meta { + color: #969896; +} + +.hljs-string, +.hljs-variable, +.hljs-template-variable, +.hljs-strong, +.hljs-emphasis, +.hljs-quote { + color: #df5000; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-type { + color: #a71d5d; +} + +.hljs-literal, +.hljs-symbol, +.hljs-bullet, +.hljs-attribute { + color: #0086b3; +} + +.hljs-section, +.hljs-name { + color: #63a35c; +} + +.hljs-tag { + color: #333333; +} + +.hljs-title, +.hljs-attr, +.hljs-selector-id, +.hljs-selector-class, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #795da3; +} + +.hljs-addition { + color: #55a532; + background-color: #eaffea; +} + +.hljs-deletion { + color: #bd2c00; + background-color: #ffecec; +} + +.hljs-link { + text-decoration: underline; +} + +/* 代码高亮 */ +/* PrismJS 1.15.0 +https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */ +/** + * prism.js default theme for JavaScript, CSS and HTML + * Based on dabblet (http://dabblet.com) + * @author Lea Verou + */ +code[class*='language-'], +pre[class*='language-'] { + color: black; + background: none; + text-shadow: 0 1px white; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +pre[class*='language-']::-moz-selection, +pre[class*='language-'] ::-moz-selection, +code[class*='language-']::-moz-selection, +code[class*='language-'] ::-moz-selection { + text-shadow: none; + background: #b3d4fc; +} + +pre[class*='language-']::selection, +pre[class*='language-'] ::selection, +code[class*='language-']::selection, +code[class*='language-'] ::selection { + text-shadow: none; + background: #b3d4fc; +} + +@media print { + code[class*='language-'], + pre[class*='language-'] { + text-shadow: none; + } +} + +/* Code blocks */ +pre[class*='language-'] { + padding: 1em; + margin: 0.5em 0; + overflow: auto; +} + +:not(pre) > code[class*='language-'], +pre[class*='language-'] { + background: #f5f2f0; +} + +/* Inline code */ +:not(pre) > code[class*='language-'] { + padding: 0.1em; + border-radius: 0.3em; + white-space: normal; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} + +.token.punctuation { + color: #999; +} + +.namespace { + opacity: 0.7; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol, +.token.deleted { + color: #905; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #690; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #9a6e3a; + background: hsla(0, 0%, 100%, 0.5); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: #07a; +} + +.token.function, +.token.class-name { + color: #dd4a68; +} + +.token.regex, +.token.important, +.token.variable { + color: #e90; +} + +.token.important, +.token.bold { + font-weight: bold; +} + +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} diff --git a/src/assets/iconfont/demo_index.html b/src/assets/iconfont/demo_index.html new file mode 100644 index 0000000..ffd422e --- /dev/null +++ b/src/assets/iconfont/demo_index.html @@ -0,0 +1,354 @@ + + + + + IconFont Demo + + + + + + + + + + + +
+

+ +
+
+
    + +
  • + +
    Menu
    +
    
    +
  • + +
  • + +
    Edit
    +
    
    +
  • + +
  • + +
    Labelled
    +
    
    +
  • + +
  • + +
    Unselected
    +
    
    +
  • + +
  • + +
    copy
    +
    
    +
  • + +
  • + +
    Arrow down
    +
    
    +
  • + +
  • + +
    Add
    +
    
    +
  • + +
  • + +
    Put in
    +
    
    +
  • + +
+
+

Unicode 引用

+
+ +

Unicode 是字体在网页端最原始的应用方式,特点是:

+
    +
  • 兼容性最好,支持 IE6+,及所有现代浏览器。
  • +
  • 支持按字体的方式去动态调整图标大小,颜色等等。
  • +
  • 但是因为是字体,所以不支持多色。只能使用平台里单色的图标,就算项目里有多色图标也会自动去色。
  • +
+
+

注意:新版 iconfont 支持多色图标,这些多色图标在 Unicode 模式下将不能使用,如果有需求建议使用symbol 的引用方式

+
+

Unicode 使用步骤如下:

+

第一步:拷贝项目下面生成的 @font-face

+
@font-face {
+  font-family: 'iconfont';
+  src: url('iconfont.eot');
+  src: url('iconfont.eot?#iefix') format('embedded-opentype'),
+      url('iconfont.woff2') format('woff2'),
+      url('iconfont.woff') format('woff'),
+      url('iconfont.ttf') format('truetype'),
+      url('iconfont.svg#iconfont') format('svg');
+}
+
+

第二步:定义使用 iconfont 的样式

+
.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+

第三步:挑选相应图标并获取字体编码,应用于页面

+
+<span class="iconfont">&#x33;</span>
+
+
+

"iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。

+
+
+
+
+
    + +
  • + +
    + Menu +
    +
    .iconMenu +
    +
  • + +
  • + +
    + Edit +
    +
    .iconEdit +
    +
  • + +
  • + +
    + Labelled +
    +
    .iconLabelled +
    +
  • + +
  • + +
    + Unselected +
    +
    .iconUnselected +
    +
  • + +
  • + +
    + copy +
    +
    .iconcopy +
    +
  • + +
  • + +
    + Arrow down +
    +
    .iconArrowdown +
    +
  • + +
  • + +
    + Add +
    +
    .iconAdd +
    +
  • + +
  • + +
    + Put in +
    +
    .iconPutin +
    +
  • + +
+
+

font-class 引用

+
+ +

font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。

+

与 Unicode 使用方式相比,具有如下特点:

+
    +
  • 兼容性良好,支持 IE8+,及所有现代浏览器。
  • +
  • 相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。
  • +
  • 因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。
  • +
  • 不过因为本质上还是使用的字体,所以多色图标还是不支持的。
  • +
+

使用步骤如下:

+

第一步:引入项目下面生成的 fontclass 代码:

+
<link rel="stylesheet" href="./iconfont.css">
+
+

第二步:挑选相应图标并获取类名,应用于页面:

+
<span class="iconfont iconxxx"></span>
+
+
+

" + iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。

+
+
+
+
+
    + +
  • + +
    Menu
    +
    #iconMenu
    +
  • + +
  • + +
    Edit
    +
    #iconEdit
    +
  • + +
  • + +
    Labelled
    +
    #iconLabelled
    +
  • + +
  • + +
    Unselected
    +
    #iconUnselected
    +
  • + +
  • + +
    copy
    +
    #iconcopy
    +
  • + +
  • + +
    Arrow down
    +
    #iconArrowdown
    +
  • + +
  • + +
    Add
    +
    #iconAdd
    +
  • + +
  • + +
    Put in
    +
    #iconPutin
    +
  • + +
+
+

Symbol 引用

+
+ +

这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇文章 + 这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:

+
    +
  • 支持多色图标了,不再受单色限制。
  • +
  • 通过一些技巧,支持像字体那样,通过 font-size, color 来调整样式。
  • +
  • 兼容性较差,支持 IE9+,及现代浏览器。
  • +
  • 浏览器渲染 SVG 的性能一般,还不如 png。
  • +
+

使用步骤如下:

+

第一步:引入项目下面生成的 symbol 代码:

+
<script src="./iconfont.js"></script>
+
+

第二步:加入通用 CSS 代码(引入一次就行):

+
<style>
+.icon {
+  width: 1em;
+  height: 1em;
+  vertical-align: -0.15em;
+  fill: currentColor;
+  overflow: hidden;
+}
+</style>
+
+

第三步:挑选相应图标并获取类名,应用于页面:

+
<svg class="icon" aria-hidden="true">
+  <use xlink:href="#icon-xxx"></use>
+</svg>
+
+
+
+ +
+
+ + + diff --git a/src/assets/iconfont/iconfont.css b/src/assets/iconfont/iconfont.css new file mode 100644 index 0000000..667c56d --- /dev/null +++ b/src/assets/iconfont/iconfont.css @@ -0,0 +1,52 @@ +@font-face { + font-family: 'iconfont'; + src: url('iconfont.eot?t=1567997197547'); /* IE9 */ + src: url('iconfont.eot?t=1567997197547#iefix') format('embedded-opentype'), + /* IE6-IE8 */ + url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAUEAAsAAAAACmQAAAS1AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDUgqHJIVzATYCJAMkCxQABCAFhG0HbhvVCFGUTk6b7OdgU/ZwPd2GDbupHH82vstXdtlIOR4e19D3kxwX2LWqY3AApKZQoXDTU5NyylNGRtzmUpUxCdLxbBiUdGw0uYf8v+WTlXW8DBehAtgC1h/eM1+qU3I0k2w+UMnYS49eJ7oYxO844BiR1///9yueOh5pee3ZgIxqZuNQ2fAALVC3Rwr/BUkwW9ph7CbyIG8nMNox07l69/IOYwXYLxDviszAOGFVKsihN7QFp5bPnUF8ffqSbuGr9/Hx38kYkzQZuNPDt7cedvV7/ZupmbI00+jh3FDOIuMwUIiXhd4XekH2sJ7R8vYdr96+COpnFlRQ6WK3IiRZ0ah/eJ2eaAHtAk/6pOmPS4YRQSUiUYXIVCMKNYiGWkRLnUV3ETLeqhcJw95rwAqINRBnyeZIwV4ucKCWe2FmBAVsZXUN9V9EX0dNzbld6/B96p635pEH9MNF0UMhQI2pqIj0msmG0fadtC25g7HHiXa1y1ILnlgSrXpFZsnfKC/Nw/aU2IHYLnIq3kmfYexL/xli0yl93tRpG7WtZ43402dss5l7xry5+z5qRx8ifPqBcp6aVErwJBJcVSYj+XL5AmYHvVNFTSS9LwUeNbmJ2TKN8DfIYtsbms4wwaeokMbTorDmnQfpA45nZMVcuYjgULu2HVNGZfSCXI6EIbn03p0nVTAphS/B8FFKJ2NMpLdggtYlxxnt9GQLgXLpGRVM5N8MGxvTEYxuU1ZnphFBPtUjnlowJaYmx0k5OrlwUszPOk4U3iPc2oWx6G60paJcIikvF4b+ucZNbdUqOArFxRIJKFlRz+5nZJg8MyHG+8HvEt5dXIQH7lSMnZ7Gg/HZWTLwZBDebJs1A/emsERV4atE7zLY1D3Y0W2lwQGOhjmYa1iBFS5ncYiDnBzyLhCfkx2HQDySnY3EAxKXnXON1FxrLdQExxSzW2YpjoBuCNIL2oCCTlxcBizOIvx8QduNhhu7GnbNz4uHr5v3xgMJr8cNAEPfrbHKc2iTYV2FHzW6w5XYOW4J+zR7CbBWnUxIaNZveb5Wf+2790I9IREHpuctmQdJiJ88j/7CrqLCHuGbcyzqpQLoZewXqosCAGw3n+TyFSzFAy5tiT1RSlFEPy0Q42x08ZI+5ZN9VHi2WvD/fuIBAMDjmReYYGa7m/fSGgG9hCL7DYfaJ8yzQKwqABZDQfMMoMCSxtJId2uIHoPzerxisxEGmYXgmr9h9ZuN6wm9zQiSzjZkvV1cIR2GxuQ0tHoXYHTI/dmTdaInyoCDvjgIK74hWfINshU/uEL6hcaWf2itRA9Gz0PrkpO9UUyjhIySR3vBwnEbkEOnfuMrZYfaSFzhcT5JUpHA9WKVLtxTSzLEEemYbVQDBuEG77i7UV0zRuGSnC5y1bhdLkPREy0cNzB1hSCGIh6yLlDBYa1AV6tpBvtXJHNQM6RioiX8iYik2DhaW1g1wN1r20YTl7J1cpTZUCqIewWCNdAd7yY1zDIUi3cqEUct5B3S0dZSmBaaahfT65t7vQYj8ABDpMhRook2uuhjiFG5R+2hH+MLHScbu6Nt5KdEm6je60PI9/M5XiYRInzyaPu2F+H9IP2gRQsAAAAA') + format('woff2'), + url('iconfont.woff?t=1567997197547') format('woff'), + url('iconfont.ttf?t=1567997197547') format('truetype'), + /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ + url('iconfont.svg?t=1567997197547#iconfont') format('svg'); /* iOS 4.1- */ +} + +.iconfont { + font-family: 'iconfont' !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.iconMenu:before { + content: '\e6e2'; +} + +.iconEdit:before { + content: '\e6e3'; +} + +.iconLabelled:before { + content: '\e6e4'; +} + +.iconUnselected:before { + content: '\e6e5'; +} + +.iconcopy:before { + content: '\e6e6'; +} + +.iconArrowdown:before { + content: '\e6e7'; +} + +.iconAdd:before { + content: '\e6e8'; +} + +.iconPutin:before { + content: '\e6e9'; +} diff --git a/src/assets/iconfont/iconfont.eot b/src/assets/iconfont/iconfont.eot new file mode 100644 index 0000000000000000000000000000000000000000..49b7267fa9ffd75588a762d1ddb2c8e757a95d14 GIT binary patch literal 2828 zcmd^B?Qc_67=NC7+pf3kUM}m)_`ue^(2Z?$P`VBZRC#9v5rGYiOQdV>){*wMb{#K? zW;IF-eo#XEU_ww#^phG*O#Gx#zeyrrP(DbI7?dg@=ogI`S?llId&^|NU*KuaIlt%k zoO7S^Jm=hVA9p=b%0wP-Ysw3)K|?$=-g{?(t)XU`rX=M^r%9tN zLeWV3NTVs5r3@vg0LDjSUul{qmPf9knmBKb$uchg!T)%q9tF%~jK z(;!@PjwJABsNLyo@p$iXAmZ@0?bH9P?$tI5H*+*NUZ-pGt6Im^Opz;Dy z-OGqzHlLd*zBBU@#wve6uZy+IrD}=B9ao5|RmXY58>sFT#b7Y*PVe}kXPY+sN%b~E z`Tf~z4FZ^bbJv5;+Ze46a!luk(^0;zK+ zE?UUqf#Q(MKz8_6C%k6it;2g_!BUyI9cVN%_?@Ie8a3FuH~{> z#ZHxfF{?N|&{45Fa9 zpYsi@_sOLNZCs3=FHLy|*7?tRy?o9e8}Qy|v;9GvewbI=*xwQ7VHL|*Z1R=1KIRqe z(k{{WwLM_(G|2f++gAvsF-Yv8*fTS3P1H`fUt2mIH7i4EB-phad-z|^hdYCj=$ejj zo8(nJjocaHA+ebEyTZ)jE{VFr;jRi?*_mKY?5xnpVM#iCVad{^OC;*svoTQb@q6l} zol&VI#x2YmT^KzgW*={cp7B~J9MR#ITa4S?l#|>H{4=zm!k#~6f#x}$` z66^`F{k{3G=P#@*uI%rZ-bN>#_xOCC(5yTlNMC z+pdDXTDc1Dj3t@tp0h27jefDSShYAjkKH?)LhewF$O4!l@9O9b4lU<5cR8JLrB%71 z;NPmmoz7jAbaMl5bt|v(^@e6<)qQt8`lek@LtR$(dMJ((9|Cohc@Nb8O>!}6l0d9Dk zf;oM3(!z`c;UfzphV+GnW$+g*Yy', + t = (e = document.getElementsByTagName('script'))[ + e.length - 1 + ].getAttribute('data-injectcss') + if (t && !d.__iconfont__svg__cssinject__) { + d.__iconfont__svg__cssinject__ = !0 + try { + document.write( + '' + ) + } catch (e) { + console && console.log(e) + } + } + !(function(e) { + if (document.addEventListener) + if (~['complete', 'loaded', 'interactive'].indexOf(document.readyState)) + setTimeout(e, 0) + else { + var t = function() { + document.removeEventListener('DOMContentLoaded', t, !1), e() + } + document.addEventListener('DOMContentLoaded', t, !1) + } + else + document.attachEvent && + ((o = e), + (i = d.document), + (n = !1), + (l = function() { + try { + i.documentElement.doScroll('left') + } catch (e) { + return void setTimeout(l, 50) + } + a() + })(), + (i.onreadystatechange = function() { + 'complete' == i.readyState && ((i.onreadystatechange = null), a()) + })) + function a() { + n || ((n = !0), o()) + } + var o, i, n, l + })(function() { + var e, t + ;((e = document.createElement('div')).innerHTML = a), + (a = null), + (t = e.getElementsByTagName('svg')[0]) && + (t.setAttribute('aria-hidden', 'true'), + (t.style.position = 'absolute'), + (t.style.width = 0), + (t.style.height = 0), + (t.style.overflow = 'hidden'), + (function(e, t) { + t.firstChild + ? (function(e, t) { + t.parentNode.insertBefore(e, t) + })(e, t.firstChild) + : t.appendChild(e) + })(t, document.body)) + }) +})(window) diff --git a/src/assets/iconfont/iconfont.svg b/src/assets/iconfont/iconfont.svg new file mode 100644 index 0000000..8a64671 --- /dev/null +++ b/src/assets/iconfont/iconfont.svg @@ -0,0 +1,50 @@ + + + + + +Created by iconfont + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/iconfont/iconfont.ttf b/src/assets/iconfont/iconfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..264f91c9008d21655e2d3a26ee235e8fc99e3ebc GIT binary patch literal 2660 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 literal 0 HcmV?d00001 diff --git a/src/assets/iconfont/iconfont.woff2 b/src/assets/iconfont/iconfont.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..8b74b80c1d198a1e5c2e2adc3e083f7df2cad38e GIT binary patch literal 1284 zcmV+f1^fDUPew8T0RR9100jg93jhEB019LP00gxF0RR9100000000000000000000 z0000SLIzd3Z3k`})d*3PPEMQb=U`Lza6R3I4ZEov ze>T3$S9aM*IUXL@(D#!Z7udC`V{ibZrcj}9(^Hdj%2P&0+~!i1F$vP~Y#5YuY&5xt z{J-Usm36!f7oh^!0@feiGheDxax{}{K2XWnOOIYBx){XoIN%tO*Z=?bE1o(YX?gA0 zfQ)Kp<51b~04-3rM+*N1NibX5VeBUIAa5ry+A-6)diU}UV+GhR5bui2z&K%5D#)QX zv<0V@&z(VhefpAa;j8!Y@!!c9lQbE))5Ev79(MJ6{hO(ovNY4^;oMNpBI7Vnh?j-l z7kWYVu%6NK_8qU@z6k1P7F1B_Vz(kBS<$F}c%7bT0qp`$`lRWPONJ3aB_dIU$W$W= zHHd~vDGUM_H;mV04l1O;qmbq)1jL|;cu&ADCc z@O|o@x8{)t^utBbLm@zosi=tbn#qRI_MNmXxr4FCiFUQiQVX6~60LegX35_uFU`aD zl-PmTE^;c~NuOctOaB>S(Ft!It+(`Rh6nR~{(x%)n9M;{`5`hjwunp8^g zBobUzCL{TB`2uDKy;DU^B)u;M9yPg%*)omrH^^e!8=B5A!KbK@#?zv(=A8%W1CD28 z#ay|F;844^9aBbS^nzTDgdw@~-km2^Fe!yE!SGQ^Cu2;cw_t+SCC3@<^kfTxa_Jcr zOyu7T8ynLRjBZm_XQmNBKGh?hS}-N1CdWxRI=OIC%+ESb6y77;+J&*`ZnUMMTp}qi z7l!`1#!YS2DmV(o#S#f9Sy9jKXJnW>Ga<(N!S52@U0j3*?o^DOp2mam*;z91WDwqL zn>7RXrm#d+;j2XNGT78T*wJmJ!2ynjIhbo$ffX)iafpMQ9P%y@pOYPjKs=J2jl=^I zmz{Hs)Ld&V)C9*Avs-2ZJncW$YKKivj>_H=pG46)eRAE^UcDQA$xnKP?dBY;^IG zKIM~rRCu;p@c(||0RX_`nHQK~X1kmB(i#E1gd+P5huSC1vp}pW02V_*^9-QClE%_V zcWa0qgY$a4VzUtjnT6n*zhU*8jq3@$%?OgtHe|hBTp=BX#^f}#dKX}H$o<)qb)qNA zfP=m`2rIrJS@I3C6+gH_`h~`pe`u{l4@S>J>ynebQB0$RjFLxt!NPGHkVB__<11x{ z+DKgCaXv{>k-+t0l`h;6mR)0|wy+@3^~BT{A}ElAJDb)wo?QL(vm095*m^ zg&>9^9qE%*^EqB75h8r@X#2Jo;r$@}phXJ+0000@>Q+nu literal 0 HcmV?d00001 diff --git a/src/assets/loading.gif b/src/assets/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..e6a1379eb348b9e4036957d8fb174d48bb492ac9 GIT binary patch literal 15025 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 z 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 + + + swap_horiz-24px + Created with Sketch. + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/testnet.svg b/src/assets/testnet.svg new file mode 100755 index 0000000..9777df9 --- /dev/null +++ b/src/assets/testnet.svg @@ -0,0 +1,13 @@ + + + + 合并形状 + Created with Sketch. + + + + + + + + \ No newline at end of file diff --git a/src/assets/variables.scss b/src/assets/variables.scss new file mode 100644 index 0000000..7baf51a --- /dev/null +++ b/src/assets/variables.scss @@ -0,0 +1,16 @@ + +$gray: #f2f3f4; +$grayDivide: #dce0e2; +$fontgray: #3f3f3f; +$subTitle: #666666; +$placeholderColor: #AFB1B4; +$addressColor: #8E9193; +$itemBgColor: #F2F3F4; +$blue: #3da0d2; +$red: #C54315; +$green: #03AC79; +$yellow: #f6c94a; +$deepYellow: #D8A001; +$deepGray: #DEDEDE; +$winWidth: 1280px; +$winPadding: 0; diff --git a/src/assets/warning.png b/src/assets/warning.png new file mode 100644 index 0000000000000000000000000000000000000000..daf8c87a7692636be8ce5aa4ebcba637ee3eec7a GIT binary patch literal 22584 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 M07*qoM6N<$f@CDsng9R* literal 0 HcmV?d00001 diff --git a/src/components/DotInCenterStr/index.js b/src/components/DotInCenterStr/index.js new file mode 100644 index 0000000..7deb42d --- /dev/null +++ b/src/components/DotInCenterStr/index.js @@ -0,0 +1,18 @@ +import React from 'react' + +function getDotInCenterStr(value) { + if (value && value.length > 13) { + const length = value.length + return value.slice(0, 5) + '...' + value.slice(length - 5, length) + } + return value +} + +function DotInCenterStr(props) { + const { value } = props + const simpleStr = getDotInCenterStr(value) + + return {simpleStr} +} + +export default DotInCenterStr diff --git a/src/components/ErrorMessage/index.js b/src/components/ErrorMessage/index.js new file mode 100644 index 0000000..3a948a0 --- /dev/null +++ b/src/components/ErrorMessage/index.js @@ -0,0 +1,14 @@ +import React from 'react' +import './index.scss' + +function ErrorMessage(props) { + const { msg } = props + + return ( +
+ {msg} +
+ ) +} + +export default ErrorMessage diff --git a/src/components/ErrorMessage/index.scss b/src/components/ErrorMessage/index.scss new file mode 100644 index 0000000..905099b --- /dev/null +++ b/src/components/ErrorMessage/index.scss @@ -0,0 +1,12 @@ +@import '../../assets/variables.scss'; + +.error-message { + display: flex; + align-items: center; + height: 30px; + width: 318px; + span { + color: red; + font-size: 16px; + } +} \ No newline at end of file diff --git a/src/components/Icon/index.js b/src/components/Icon/index.js new file mode 100644 index 0000000..74e5fa8 --- /dev/null +++ b/src/components/Icon/index.js @@ -0,0 +1,8 @@ +import React from 'react' + +function Icon(props) { + const { className = {}, style = {}, name = '' } = props + return +} + +export default Icon diff --git a/src/components/NameAndPassword/index.js b/src/components/NameAndPassword/index.js new file mode 100644 index 0000000..3b18355 --- /dev/null +++ b/src/components/NameAndPassword/index.js @@ -0,0 +1,104 @@ +import React, { useState } from 'react' +import { Account } from 'chainx.js' +import ErrorMessage from '../ErrorMessage' +import WarningMessage from '../WarningMessage' +import { createAccount } from '../../messaging' +import { useRedux } from '../../shared' + +function NameAndPassword(props) { + const { secret, onSuccess } = props + const [obj, setObj] = useState({ name: '', pass: '', repass: '' }) + const [errMsg, setErrMsg] = useState('') + const [{ accounts }] = useRedux('accounts') + const [{ isTestNet }] = useRedux('isTestNet') + Account.setNet(isTestNet ? 'testnet' : 'mainnet') + const account = Account.from(secret) + const address = account.address() + const sameAccount = (accounts || []).find( + account => account.address === address + ) + + const check = () => { + if (!obj.name || !obj.pass || !obj.repass) { + setErrMsg('name and password are required') + return false + } + if (obj.pass.length < 8) { + setErrMsg('password length must great than 8') + return false + } + if (!/(?=.*[a-z])(?=.*[A-Z])/.test(obj.pass)) { + setErrMsg('password must include lower and upper characters') + return false + } + if (obj.pass !== obj.repass) { + setErrMsg('password is not match') + return false + } + return true + } + + const create = async () => { + if (!check()) { + return + } + + const keystore = account.encrypt(obj.pass) + + createAccount(obj.name, account.address(), keystore, isTestNet) + .then(_ => { + onSuccess() + }) + .catch(err => { + setErrMsg(err.message) + }) + } + + return ( + <> + setObj({ ...obj, name: e.target.value })} + placeholder="Name(12 characters max)" + /> + setObj({ ...obj, pass: e.target.value })} + placeholder="Password" + /> + setObj({ ...obj, repass: e.target.value })} + onKeyPress={event => { + if (event.key === 'Enter') { + create() + } + }} + placeholder="Password confirmation" + /> + + {errMsg && } + {sameAccount && ( + + )} + + ) +} + +export default NameAndPassword diff --git a/src/components/StaticWarning/index.js b/src/components/StaticWarning/index.js new file mode 100644 index 0000000..2f2c95b --- /dev/null +++ b/src/components/StaticWarning/index.js @@ -0,0 +1,21 @@ +import React from 'react' +// @ts-ignore +import warningIcon from '../../assets/warning.png' +import './index.scss' + +function StaticWarning(props) { + const { + title = '', + desc = 'Do not store the mnemonic words in your PC or Net. Anybody can take your assets with the mnemonic words.' + } = props + + return ( +
+ warning + {title} +
{desc}
+
+ ) +} + +export default StaticWarning diff --git a/src/components/StaticWarning/index.scss b/src/components/StaticWarning/index.scss new file mode 100644 index 0000000..8de0b08 --- /dev/null +++ b/src/components/StaticWarning/index.scss @@ -0,0 +1,25 @@ +@import '../../assets/variables.scss'; + +.static-warning { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + .warning-title { + margin-top: 16px; + font-size: 16px; + color: $fontgray; + font-weight: 500; + } + .warning-icon { + width: 64px; + height: 56px; + } + .warning-desc { + margin-top: 16px; + font-size: 14px; + width: 246px; + color: $subTitle; + text-align: center; + } +} \ No newline at end of file diff --git a/src/components/WarningMessage/index.js b/src/components/WarningMessage/index.js new file mode 100644 index 0000000..a4465a0 --- /dev/null +++ b/src/components/WarningMessage/index.js @@ -0,0 +1,12 @@ +import React from 'react' +import './index.scss' + +export default function(props) { + const { msg } = props + + return ( +
+ {msg} +
+ ) +} diff --git a/src/components/WarningMessage/index.scss b/src/components/WarningMessage/index.scss new file mode 100644 index 0000000..85a0698 --- /dev/null +++ b/src/components/WarningMessage/index.scss @@ -0,0 +1,12 @@ +@import '../../assets/variables.scss'; + +.warning-message { + display: flex; + align-items: center; + height: 30px; + width: 318px; + span { + color: $yellow; + font-size: 16px; + } +} \ No newline at end of file diff --git a/src/index.js b/src/index.js index 24a32e4..b1b9581 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,6 @@ import React from 'react' import ReactDOM from 'react-dom' -import App from './App' +import App from './pages/App' import { Provider } from 'react-redux' import createStore from './store' diff --git a/src/messaging/index.js b/src/messaging/index.js new file mode 100644 index 0000000..5d73c2b --- /dev/null +++ b/src/messaging/index.js @@ -0,0 +1,37 @@ +function addChainxNode() {} +function createAccount() {} +function createAccountFromPrivateKey() {} +function createChainxNode() {} +function getAllAccounts() {} +function getAllChainxNodes() {} +function getCurrentChainxAccount() {} +function getCurrentChainxNode() {} +function removeChainxAccount() {} +function removeChainxNode() {} +function setChainxCurrentAccount() {} +function setChainxNode() {} +function setNetwork() {} +function getSettings() {} +function getToSign() {} +function rejectSign() {} +function signTransaction() {} + +export { + getAllChainxNodes, + setChainxCurrentAccount, + getCurrentChainxAccount, + removeChainxAccount, + setChainxNode, + addChainxNode, + getCurrentChainxNode, + removeChainxNode, + createChainxNode, + getAllAccounts, + createAccount, + createAccountFromPrivateKey, + signTransaction, + getToSign, + rejectSign, + setNetwork, + getSettings +} diff --git a/src/pages/App.js b/src/pages/App.js new file mode 100644 index 0000000..b0033f2 --- /dev/null +++ b/src/pages/App.js @@ -0,0 +1,93 @@ +import React, { useEffect } from 'react' +import { BrowserRouter as Router } from 'react-router-dom' +import { Redirect, Route, Switch } from 'react-router' +import Home from './Home' +import Header from './Header' +import CreateAccount from './CreateAccount' +import ImportAccount from './ImportAccount' +import RequestSign from './RequestSign' +import ShowPrivateKey from './ShowPrivateKey/index' +import EnterPassword from './EnterPassword' +import NodeAction from './NodeAction' +import { useRedux, setChainx } from '../shared' +import { useDispatch } from 'react-redux' +import { getCurrentChainxNode } from '../messaging' +import { getSettings } from '../messaging/index' +import spinner from '../assets/loading.gif' +import './index.scss' +import { useSelector } from 'react-redux' +import { setInitLoading } from '../store/reducers/statusSlice' + +import { + handleApiResponse, + handlePairedResponse, + setService +} from '../services/socketService' + +window.wallet.socketResponse = data => { + if (typeof data === 'string') data = JSON.parse(data) + switch (data.type) { + case 'api': + return handleApiResponse(data.request, data.id) + case 'pair': + return handlePairedResponse(data.request, data.id) + default: + return + } +} + +setService(window.sockets) + +window.sockets.initialize().then(() => console.log('sockets initialized')) + +export default function App() { + let redirectUrl = '/' + + const dispatch = useDispatch() + const [, setIsTestNet] = useRedux('isTestNet', false) + const loading = useSelector(state => state.status.loading) + const initLoading = useSelector(state => state.status.initLoading) + const homeLoading = useSelector(state => state.status.homeLoading) + useEffect(() => { + getSetting() + // eslint-disable-next-line + }, []) + + const getSetting = async () => { + const settings = { isTestNet: true } + setIsTestNet({ isTestNet: settings.isTestNet }) + const node = { + name: 'testnet.w1.org.cn', + url: 'wss://testnet.w1.chainx.org.cn/ws' + } + await setChainx(node.url) + dispatch(setInitLoading(false)) + } + + return ( + + +
+ {(loading || initLoading || homeLoading) && ( +
+ spinner +
+ )} + {!initLoading && ( +
+ + + + + + + + + + +
+ )} + + + ) +} diff --git a/src/pages/CreateAccount/createAccount.scss b/src/pages/CreateAccount/createAccount.scss new file mode 100644 index 0000000..75dc5cc --- /dev/null +++ b/src/pages/CreateAccount/createAccount.scss @@ -0,0 +1,81 @@ +@import '../../assets/variables.scss'; + +.create-account { + display: flex; + flex-direction: column; + min-height: 86px; + .create-account-title { + font-size: 20px; + margin-top: 16px; + display: flex; + flex-direction: column; + justify-content: space-between; + .create-account-sub-title { + margin-top: 8px; + color: $subTitle; + font-size: 14px; + } + } + .create-account-body { + margin-top: 8px; + display: flex; + flex-direction: column; + .create-account-body-content { + display: flex; + justify-content: space-between; + flex-wrap: wrap; + .word-item { + margin-top: 16px; + width: 100px; + height: 40px; + background: #FFFFFF; + border: 1px solid $grayDivide; + border-radius: 20px; + color: $fontgray; + font-size: 14px; + display: flex; + justify-content: center; + align-items: center; + } + .word-item-click { + cursor: pointer; + transition: filter 0.3s ease 0.3s; + &:hover { + background: #F2F3F4; + } + &:active { + filter: drop-shadow(0 0 2px #000); + transition: filter .3s; + } + } + .word-item-selected { + background: #F2F3F4; + border: 1px solid $grayDivide; + } + } + } + .validate-mnemonic-area { + position: absolute; + bottom: 0; + left: -20px; + width: 378px; + height: 90px; + background-color: $itemBgColor; + display: flex; + justify-content: center; + .validate-mnemonic-area-container { + width: 318px; + min-height: 60px; + display: flex; + flex-wrap: wrap; + margin-top: 8px; + span { + height: 15px; + margin-top: 8px; + margin-left: 10px; + font-size: 14px; + color: $fontgray; + } + } + } +} \ No newline at end of file diff --git a/src/pages/CreateAccount/index.js b/src/pages/CreateAccount/index.js new file mode 100644 index 0000000..5031934 --- /dev/null +++ b/src/pages/CreateAccount/index.js @@ -0,0 +1,155 @@ +import React from 'react' +import { useState } from 'react' +import { Account } from 'chainx.js' +import shuffle from 'lodash.shuffle' +import './createAccount.scss' +import StaticWarning from '../../components/StaticWarning' +import ErrorMessage from '../../components/ErrorMessage' +import NameAndPassword from '../../components/NameAndPassword' + +function CreateAccount(props: any) { + const titleList = [ + 'New Account', + 'Backup Mnemonic', + 'Verify Mnemonic', + 'Name and password setting' + ] + const subTitleList = [ + '', + 'Write down following mnemonic words, and will be used next step.', + 'Mark the words one by one in the order last step shows.', + 'Password contains at lease 8 characters, and at least one upper,lower and number case character.' + ] + const buttonTextList = ['Begin', 'Next', 'Next', 'OK'] + + const [currentStep, setCurrentStep] = useState(0) + const [errMsg, setErrMsg] = useState('') + const [mnemonic] = useState(Account.newMnemonic()) + const mnemonicList = mnemonic.split(' ') + const [wordSelectedList, setWordSelectedList] = useState( + new Array(mnemonicList.length).fill(false) + ) + const [shuffleMnemonicList] = useState(shuffle(mnemonicList)) + const [validateMnemonicList, setValidateMnemonicList] = useState( + new Array(12).fill('') + ) + const mnemonicWords = mnemonicList.map((item: string, index: number) => ({ + value: item, + index: index + })) + + const clearErrMsg = () => { + setErrMsg('') + return true + } + + const checkMnemonic = () => { + if (mnemonic === validateMnemonicList.join(' ')) { + clearErrMsg() + return true + } + setErrMsg('Mnemonic not correct') + return false + } + + return ( +
+
+ {titleList[currentStep]} + + {subTitleList[currentStep]} + +
+
+
+ {currentStep === 0 && } + {currentStep === 1 && + mnemonicWords.map((item: any) => ( +
+ {item.value} +
+ ))} + {currentStep === 2 && + shuffleMnemonicList.map((item: any, index: number) => ( +
{ + const wordSelected = wordSelectedList[index] + let wordIndex = validateMnemonicList.indexOf('') + let replaceWord = item + if (wordSelected) { + // word has selected, remove last word + wordIndex = + 11 - + Array.from(validateMnemonicList) + .reverse() + .indexOf(item) + replaceWord = '' + } + validateMnemonicList.splice(wordIndex, 1, replaceWord) + setValidateMnemonicList(Array.from(validateMnemonicList)) + wordSelectedList.splice(index, 1, !wordSelected) + setWordSelectedList(Array.from(wordSelectedList)) + }} + > + {item} +
+ ))} + {currentStep === 3 && ( + + )} +
+ {currentStep < 2 && ( + + )} + {currentStep === 2 && ( +
+ + +
+ )} + {currentStep > 1 ? errMsg ? : null : null} +
+ {currentStep === 2 && ( +
+
+ {validateMnemonicList.map((item, index) => ( + {item} + ))} +
+
+ )} +
+ ) +} + +export default CreateAccount diff --git a/src/pages/EnterPassword/enterPassword.scss b/src/pages/EnterPassword/enterPassword.scss new file mode 100644 index 0000000..4e2b31f --- /dev/null +++ b/src/pages/EnterPassword/enterPassword.scss @@ -0,0 +1,17 @@ +@import '../../assets/variables.scss'; + +.enter-password { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + .title { + margin-top: 16px; + margin-bottom: 16px; + margin-left: 20px; + font-size: 20px; + font-weight: 500; + color: $fontgray; + align-self: flex-start; + } +} \ No newline at end of file diff --git a/src/pages/EnterPassword/index.js b/src/pages/EnterPassword/index.js new file mode 100644 index 0000000..c3b2194 --- /dev/null +++ b/src/pages/EnterPassword/index.js @@ -0,0 +1,75 @@ +import React from 'react' +import { useState } from 'react' +import { Account } from 'chainx.js' +import './enterPassword.scss' +import { useRedux } from '../../shared' +import ErrorMessage from '../../components/ErrorMessage' + +function EnterPassword(props) { + const [pass, setPass] = useState('') + const [errMsg, setErrMsg] = useState('') + const [{ isTestNet }] = useRedux('isTestNet') + + async function exportPk(keystore, password) { + try { + const pk = Account.fromKeyStore(keystore, password).privateKey() + props.history.push({ + pathname: '/showPrivateKey', + query: { pk: pk } + }) + } catch (error) { + setErrMsg(error.message) + } + } + + async function removeAccount(address, password, keystore) { + try { + Account.setNet(isTestNet ? 'testnet' : 'mainnet') + Account.fromKeyStore(keystore, password) + // await removeChainxAccount(address, isTestNet); + props.history.push('/') + } catch (error) { + setErrMsg(error.message) + } + } + + const enter = async function() { + if (pass) { + const address = props.location.query.address + const keystore = props.location.query.keystore + const type = props.location.query.type + if (type === 'export') { + exportPk(keystore, pass) + } else if (type === 'remove') { + removeAccount(address, pass, keystore) + } + } + } + + return ( +
+ Input password + setPass(e.target.value)} + onKeyPress={event => { + if (event.key === 'Enter') { + enter() + } + }} + placeholder="Password" + /> + + {errMsg ? : null} +
+ ) +} + +export default EnterPassword diff --git a/src/pages/Header/header.scss b/src/pages/Header/header.scss new file mode 100644 index 0000000..bd33448 --- /dev/null +++ b/src/pages/Header/header.scss @@ -0,0 +1,263 @@ +@import '../../assets/variables.scss'; + +.header { + height: 70px; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + border-bottom: 1px solid #DCE0E2; + .container-header { + justify-content: space-between; + .logo { + width: 32px; + height: 32px; + } + .testnet { + position: absolute; + margin-bottom: 24px; + } + .center-title { + font-weight: 500; + font-size: 20px; + color: $fontgray; + } + .right { + display: flex; + .current-node { + height: 32px; + width: 123px; + border-radius: 16px; + border: 1px solid #DCE0E2; + display: flex; + justify-content: center; + align-items: center; + margin-right: 26px; + cursor: pointer; + .dot { + width: 6px; + height: 6px; + border-radius: 3px; + margin-right: 6px; + } + } + .setting { + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + .setting-icon { + color: $fontgray; + } + } + } + + } + .node-list-area { + &.hide { + display: none; + } + position: absolute; + top: 70px; + left: 64px; + padding-top: 10px; + width: 226px; + min-height: 78px; + border-radius: 10px; + background: #fff; + border: 1px solid #DCE0E2; + box-shadow: 0 0 8px 0 rgba(0,0,0,0.08), 0 8px 8px 0 rgba(0,0,0,0.16); + display: flex; + flex-direction: column; + justify-content: flex-end; + .node-list { + display: flex; + flex-direction: column; + border-bottom: 1px solid $grayDivide; + .node-item { + display: flex; + height: 32px; + &:hover { + background-color: $itemBgColor; + cursor: pointer; + } + &.active { + background-color: $itemBgColor; + .node-item-active-flag { + background-color: $yellow; + } + } + .node-item-active-flag { + width: 3px; + height: 100%; + border-radius: 1.5px; + } + .node-item-detail { + margin-left: 13px; + margin-right: 15px; + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + &:hover { + .node-item-detail-url { + .custom { + display: block; + } + } + } + .node-item-detail-url { + display: flex; + align-items: center; + width: 142px; + .url { + width: 122px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .node-item-detail-edit { + font-size: 14px; + color: $yellow; + margin-left: 5px; + width: 20px; + height: 20px; + display: none; + } + } + } + } + } + .auto-select { + height: 36px; + margin-left: 16px; + display: flex; + align-items: center; + } + .add-node { + border-bottom: 1px solid rgba(255, 255, 255, 0.12); + } + .switch-net { + border-radius: 0px 0px 10px 10px; + } + .node-action-item { + height: 48px; + background: #3F3F3F; + display: flex; + align-items: center; + cursor: pointer; + .node-action-item-img { + margin-left: 15px; + color: $yellow; + } + span { + margin-left: 6px; + color: #fff; + } + } + } + .account-area { + position: absolute; + right: 20px; + top: 70px; + min-height: 47px; + width: 256px; + border-radius: 10px; + background: #fff; + border: 1px solid #DCE0E2; + box-shadow: 0 0 8px 0 rgba(0,0,0,0.08), 0 8px 8px 0 rgba(0,0,0,0.16); + .action { + height: 47px; + display: flex; + justify-content: space-around; + border-bottom: 1px solid $grayDivide; + font-size: 14px; + color: $fontgray; + div { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + span { + margin-left: 6px; + } + } + div:first-child { + border-right: 1px solid $grayDivide; + } + } + a { + text-decoration: none; + color: $fontgray; + } + .account-area-icon { + color: $yellow; + } + } + .accounts { + display: flex; + flex-direction: column; + padding-bottom: 10px; + .account-item { + display: flex; + height: 62px; + width: 100%; + font-size: 12px; + color: $addressColor; + cursor: pointer; + &:hover { + background-color: $itemBgColor; + .account-copy { + background-color: $itemBgColor; + } + } + &.active { + background-color: $itemBgColor; + .account-item-active-flag { + background-color: $yellow; + } + .account-copy { + background-color: $itemBgColor; + } + } + .account-item-active-flag { + border-radius: 2px; + width: 4px; + height: 62px; + } + .account-item-detail { + margin-left: 12px; + margin-right: 12px; + display: flex; + flex-direction: column; + justify-content: space-around; + width: 100%; + font-size: 12px; + color: $addressColor; + .name { + font-size: 13px; + color: $fontgray; + font-weight: 500; + } + .address { + display: flex; + justify-content: space-between; + .account-copy { + cursor: pointer; + border: none; + } + + .extension-tooltip { + padding: 8px 10px !important; + } + + .copy-icon { + font-size: 12px; + color: $yellow; + } + } + } + } + } +} diff --git a/src/pages/Header/index.js b/src/pages/Header/index.js new file mode 100644 index 0000000..e4d5ddd --- /dev/null +++ b/src/pages/Header/index.js @@ -0,0 +1,345 @@ +import React, { useEffect, useRef, useState } from 'react' +import { Link, withRouter } from 'react-router-dom' +import ClipboardJS from 'clipboard' +import ReactTooltip from 'react-tooltip' +import { + updateNodeStatus, + useRedux, + useOutsideClick, + isCurrentNodeInit +} from '../../shared' +import { + setChainxCurrentAccount, + setChainxNode, + setNetwork +} from '../../messaging' +import Icon from '../../components/Icon' +import DotInCenterStr from '../../components/DotInCenterStr' +import logo from '../../assets/extension_logo.svg' +import testNetImg from '../../assets/testnet.svg' +import switchImg from '../../assets/switch.svg' +import './header.scss' + +function Header(props) { + const refNodeList = useRef(null) + const refAccountList = useRef(null) + const [showNodeListArea, setShowNodeListArea] = useState(false) + const [showAccountArea, setShowAccountArea] = useState(false) + const [copyText, setCopyText] = useState('Copy') + const [{ currentAccount }, setCurrentAccount] = useRedux('currentAccount') + const [{ accounts }] = useRedux('accounts') + const [{ isTestNet }, setIsTestNet] = useRedux('isTestNet') + const [{ currentNode }, setCurrentNode] = useRedux('currentNode', { + name: '', + url: '', + delay: '' + }) + const [{ nodeList }, setNodeList] = useRedux('nodeList', []) + const [{ delayList }, setDelayList] = useRedux('delayList', []) + const [{ testDelayList }, setTestDelayList] = useRedux('testDelayList', []) + const [{ currentDelay }, setCurrentDelay] = useRedux('currentDelay', 0) + const [{ currentTestDelay }, setCurrentTestDelay] = useRedux( + 'currentTestDelay', + 0 + ) + + useEffect(() => { + updateNodeStatus( + setCurrentNode, + isTestNet ? setCurrentTestDelay : setCurrentDelay, + setNodeList, + isTestNet ? testDelayList : delayList, + isTestNet ? setTestDelayList : setDelayList, + isTestNet + ) + setCopyEvent() + // eslint-disable-next-line + }, [isTestNet]) + + useOutsideClick(refNodeList, () => { + setShowNodeListArea(false) + }) + + useOutsideClick(refAccountList, () => { + setShowAccountArea(false) + }) + + function getCurrentDelay(_isTestNet) { + if (_isTestNet) { + return currentTestDelay + } else { + return currentDelay + } + } + + function getDelayList(_isTestNet) { + if (_isTestNet) { + return testDelayList + } else { + return delayList + } + } + + function setCopyEvent() { + const clipboard = new ClipboardJS('.account-copy') + clipboard.on('success', function() { + setCopyText('Copied!') + }) + } + + async function setNode(url) { + await setChainxNode(url, isTestNet) + updateNodeStatus( + setCurrentNode, + isTestNet ? setCurrentTestDelay : setCurrentDelay, + setNodeList, + isTestNet ? testDelayList : delayList, + isTestNet ? setTestDelayList : setDelayList, + isTestNet + ) + setShowNodeListArea(false) + } + + function getDelayClass(delay) { + if (delay === 'timeout') { + return 'red' + } else if (delay > 300) { + return 'yellow' + } else if (delay <= 300) { + return 'green' + } else { + return 'green' + } + } + + function getDelayText(delay) { + return delay ? (delay === 'timeout' ? 'timeout' : delay + ' ms') : '' + } + + function switchNet() { + setNetwork(!isTestNet) + setIsTestNet({ isTestNet: !isTestNet }) + setShowNodeListArea(false) + props.history.push('/') + } + + return ( +
+
+ + logo + {isTestNet && ( + testNetImg + )} + + {props.history.location.pathname.includes('requestSign') ? ( +
+ + {( + (props.history.location.query && + props.history.location.query.method) || + '' + ) + .replace(/([A-Z])/g, ' $1') + .toLowerCase() || 'Sign Request'} + +
+ ) : ( +
+
{ + setShowNodeListArea(!showNodeListArea) + setShowAccountArea(false) + }} + > + + {currentNode && currentNode.name} +
+
{ + setShowAccountArea(!showAccountArea) + setShowNodeListArea(false) + }} + > + +
+
+ )} + { +
+
+ {currentNode && + (nodeList || []).map((item, index) => ( +
{ + setNode(item.url) + }} + > +
+
+
+ + {item.url.split('//')[1] || item.url} + +
{ + e.stopPropagation() + e.nativeEvent.stopImmediatePropagation() + setShowNodeListArea(false) + const query = { + nodeInfo: item, + type: 'remove' + } + props.history.push({ + pathname: '/addNode', + query: query + }) + }} + > + +
+
+ + {getDelayText( + getDelayList(isTestNet) && + getDelayList(isTestNet)[index] + )} + +
+
+ ))} +
+
{ + props.history.push('/addNode') + }} + > + + Add node +
+
{ + switchNet() + }} + > + switchImg + Switch to {isTestNet ? 'Mainnet' : 'Testnet'} +
+
+ } + {showAccountArea && !showNodeListArea ? ( +
+
+
{ + setShowAccountArea(false) + props.history.push('/importAccount') + }} + > + + Import +
+
{ + setShowAccountArea(false) + props.history.push('/createAccount') + }} + > + + New +
+
+ {accounts.length > 0 ? ( +
+ {accounts.length > 0 && + accounts.map(item => ( +
{ + setChainxCurrentAccount( + item.address, + isTestNet + ).then(d => console.log(d)) + await setCurrentAccount({ currentAccount: item }) + setShowAccountArea(false) + props.history.push('/') + }} + > +
+
+ {item.name} +
+ + + setCopyText('Copy')} + > + {copyText} + +
+
+
+ ))} +
+ ) : null} +
+ ) : null} +
+
+ ) +} + +export default withRouter(Header) diff --git a/src/pages/Home.js b/src/pages/Home.js new file mode 100644 index 0000000..c98bbc4 --- /dev/null +++ b/src/pages/Home.js @@ -0,0 +1,157 @@ +import React, { useEffect, useRef, useState } from 'react' +import { useOutsideClick, useRedux } from '../shared' +import { useSelector, useDispatch } from 'react-redux' +import { setHomeLoading } from '../store/reducers/statusSlice' +import ClipboardJS from 'clipboard' +import { + getAllAccounts, + getCurrentChainxAccount, + getToSign +} from '../messaging' +import Icon from '../components/Icon' +import './index.scss' +import logo from '../assets/extension_logo.svg' + +function Home(props) { + const ref = useRef(null) + const [showAccountAction, setShowAccountAction] = useState(false) + const [{ currentAccount }, setCurrentAccount] = useRedux('currentAccount', { + address: '', + name: '', + keystore: {} + }) + const dispatch = useDispatch() + const homeLoading = useSelector(state => state.status.homeLoading) + const [, setAccounts] = useRedux('accounts') + const [{ isTestNet }] = useRedux('isTestNet') + const [copySuccess, setCopySuccess] = useState('') + + useEffect(() => { + setCopyEvent() + getUnapprovedTxs() + // eslint-disable-next-line + }, [isTestNet]) + + useOutsideClick(ref, () => { + setShowAccountAction(false) + }) + + async function getUnapprovedTxs() { + try { + const toSign = await getToSign() + if (toSign) { + props.history.push({ + pathname: '/requestSign/' + toSign.id, + query: toSign + }) + } + } catch (error) { + console.log('sign request error occurs ', error) + } finally { + await getAccountStatus() + dispatch(setHomeLoading(false)) + } + } + + async function getAccountStatus() { + await getCurrentAccount() + await getAccounts() + } + + async function getCurrentAccount() { + const result = await getCurrentChainxAccount(isTestNet) + setCurrentAccount({ currentAccount: result }) + } + + async function getAccounts() { + const result = (await getAllAccounts(isTestNet)) || [] + setAccounts({ accounts: result }) + } + + function setCopyEvent() { + const clipboard = new ClipboardJS('.copy') + clipboard.on('success', function() { + setCopySuccess('Copied!') + setTimeout(() => { + setCopySuccess('') + }, 2000) + }) + } + + async function operateAccount(type) { + if (currentAccount.address) { + props.history.push({ + pathname: '/enterPassword', + query: { + address: currentAccount.address, + keystore: currentAccount.keystore, + type: type + } + }) + } + setShowAccountAction(false) + } + + if (homeLoading) { + return <> + } + + return ( + <> + {currentAccount ? ( +
+
+ {currentAccount.name} +
{ + setShowAccountAction(!showAccountAction) + }} + > + +
+ {showAccountAction ? ( +
+ operateAccount('export')}> + Export PrivateKey + + operateAccount('remove')}> + Forget Account + +
+ ) : null} +
+
+ {currentAccount.address} +
+ + {copySuccess} +
+ ) : ( +
+
+ logo +
+ + +
+ )} + + ) +} + +export default Home diff --git a/src/pages/ImportAccount/importAccount.scss b/src/pages/ImportAccount/importAccount.scss new file mode 100644 index 0000000..6abebf7 --- /dev/null +++ b/src/pages/ImportAccount/importAccount.scss @@ -0,0 +1,71 @@ +@import '../../assets/variables.scss'; + +.import-account { + display: flex; + flex-direction: column; + min-height: 86px; + .import-account-title { + font-size: 20px; + margin-top: 16px; + display: flex; + flex-direction: column; + justify-content: space-between; + .import-account-title-select { + display: flex; + justify-content: space-between; + .second-choice { + color: $placeholderColor; + cursor: pointer; + } + } + .import-account-sub-title { + margin-top: 8px; + color: $subTitle; + font-size: 14px; + } + } + .import-account-body { + margin-top: 8px; + display: flex; + flex-direction: column; + .import-account-body-content { + .import-mnemonic { + display: flex; + justify-content: space-between; + flex-wrap: wrap; + .word-item { + text-align: center; + margin-top: 16px; + width: 100px; + height: 40px; + background: #FFFFFF; + border: 1px solid $grayDivide; + border-radius: 20px; + color: $fontgray; + caret-color: $yellow; + font-size: 14px; + display: flex; + justify-content: center; + align-items: center; + } + } + .import-private-key { + textarea { + margin-top: 16px; + width: 296px; + height: 64px; + padding: 12px; + font-size: 14px; + color: $subTitle; + border: 1px solid $grayDivide; + resize: none; + border-radius: 6px; + } + } + .word-item-selected { + background: #F2F3F4; + border: 1px solid $grayDivide; + } + } + } +} \ No newline at end of file diff --git a/src/pages/ImportAccount/index.js b/src/pages/ImportAccount/index.js new file mode 100644 index 0000000..a2fe1bb --- /dev/null +++ b/src/pages/ImportAccount/index.js @@ -0,0 +1,121 @@ +import React, { useState } from 'react' +import './importAccount.scss' +import ErrorMessage from '../../components/ErrorMessage' +import NameAndPassword from '../../components/NameAndPassword' + +function ImportAccount(props) { + const [currentStep, setCurrentStep] = useState(0) + const [currentTabIndex, setCurrentTabIndex] = useState(0) + const [pk, setPk] = useState('') + const [errMsg, setErrMsg] = useState('') + const [mnemonicList, setMnemonicList] = useState(new Array(12).fill('')) + + const titleList = [ + ['Mnemonic', 'Private key'], + ['Password', 'Password'] + ] + const subTitleList = [ + ['Input mnemonic words', 'Input private key'], + ['', ''] + ] + + const checkStep1 = () => { + if (currentTabIndex === 0) { + if (mnemonicList.some(item => item === '')) { + setErrMsg('Mnemonic is not correct') + return + } + } else if (currentTabIndex === 1) { + if (!pk) { + setErrMsg('Private key is not correct') + return + } + } + + setErrMsg('') + setCurrentStep(s => s + 1) + } + + return ( +
+
+
+ {titleList[currentStep][currentTabIndex]} + {currentStep === 0 ? ( + { + setErrMsg('') + setCurrentTabIndex(1 - currentTabIndex) + }} + > + {titleList[currentStep][1 - currentTabIndex]} + + ) : null} +
+ + {subTitleList[currentStep][currentTabIndex]} + +
+
+
+ {currentStep === 0 ? ( + currentTabIndex === 0 ? ( +
+ {mnemonicList.map((item, index) => ( + { + mnemonicList.splice(index, 1, e.target.value) + setMnemonicList(Array.from(mnemonicList)) + }} + /> + ))} +
+ ) : ( +
+