diff --git a/_raw/locales/en/messages.json b/_raw/locales/en/messages.json index db3289504bf..e485e876fa3 100644 --- a/_raw/locales/en/messages.json +++ b/_raw/locales/en/messages.json @@ -2314,7 +2314,7 @@ "label": "Password", "placeholder": "8 characters min", "required": "Please input Password", - "min": "8 characters min" + "min": "Password must be at least 8 characters long" }, "confirmPassword": { "label": "Confirm Password", diff --git a/src/ui/assets/icon-checked-success-cc.svg b/src/ui/assets/icon-checked-success-cc.svg new file mode 100644 index 00000000000..d0bc58626ac --- /dev/null +++ b/src/ui/assets/icon-checked-success-cc.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/ui/views/MainRoute.tsx b/src/ui/views/MainRoute.tsx index 2e85204784e..0c38d1df843 100644 --- a/src/ui/views/MainRoute.tsx +++ b/src/ui/views/MainRoute.tsx @@ -71,6 +71,7 @@ import { ImportOrCreatedSuccess } from './NewUserImport/Success'; import { ReadyToUse } from './NewUserImport/ReadyToUse'; import { ImportSeedPhrase } from './NewUserImport/ImportSeedPhrase'; import { NewUserImportHardware } from './NewUserImport/ImportHardWare'; +import { KEYRING_CLASS } from '@/constant'; declare global { interface Window { @@ -129,16 +130,22 @@ const Main = () => { - - + + - - + + - - + + diff --git a/src/ui/views/NewUserImport/ImportList.tsx b/src/ui/views/NewUserImport/ImportList.tsx index ca29e5696c9..0d15c084971 100644 --- a/src/ui/views/NewUserImport/ImportList.tsx +++ b/src/ui/views/NewUserImport/ImportList.tsx @@ -68,11 +68,7 @@ export const ImportWalletList = () => { history.push('/new-user/import/seed-phrase'); break; case KEYRING_CLASS.HARDWARE.LEDGER: - history.push('/new-user/import/ledger/set-password'); - break; case KEYRING_CLASS.HARDWARE.KEYSTONE: - history.push('/new-user/import/keystone/set-password'); - break; case KEYRING_CLASS.HARDWARE.ONEKEY: case KEYRING_CLASS.HARDWARE.TREZOR: case KEYRING_CLASS.HARDWARE.GRIDPLUS: diff --git a/src/ui/views/NewUserImport/ImportPrivateKey.tsx b/src/ui/views/NewUserImport/ImportPrivateKey.tsx index 5b7c8661e9b..24368806d4c 100644 --- a/src/ui/views/NewUserImport/ImportPrivateKey.tsx +++ b/src/ui/views/NewUserImport/ImportPrivateKey.tsx @@ -1,87 +1,124 @@ import { Card } from '@/ui/component/NewUserImport'; -import { useMemoizedFn } from 'ahooks'; -import { Button, Form, Input } from 'antd'; +import { useMemoizedFn, useRequest } from 'ahooks'; +import { Button, Form, Input, message } from 'antd'; import clsx from 'clsx'; -import React from 'react'; +import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useHistory } from 'react-router-dom'; import { useNewUserGuideStore } from './hooks/useNewUserGuideStore'; +import { clearClipboard } from '@/ui/utils/clipboard'; +import IconSuccess from 'ui/assets/success.svg'; +import styled from 'styled-components'; +import { useWallet } from '@/ui/utils'; + +const Container = styled.div` + .ant-input { + border-radius: 8px; + border: 1px solid var(--r-neutral-line, #e0e5ec); + } + + .ant-input:focus, + .ant-input-focused { + border-color: var(--r-blue-default, #7084ff); + } + + .ant-form-item-has-error .ant-input { + border: 1px solid var(--r-red-default, #e34935); + } +`; export const NewUserImportPrivateKey = () => { const { t } = useTranslation(); - const { store, setStore, clearStore } = useNewUserGuideStore(); + const { setStore, clearStore } = useNewUserGuideStore(); + const [value, setValue] = useState(''); const history = useHistory(); + const wallet = useWallet(); const [form] = Form.useForm<{ privateKey: string; }>(); - const handleSubmit = useMemoizedFn(() => { - // todo - const { privateKey } = form.getFieldsValue(); - if (!privateKey) { - form.setFields([ - { - name: 'privateKey', - errors: ['Please input your private key'], - }, - ]); - return; - } + const handleSubmit = useMemoizedFn(async () => { + const { privateKey } = await form.validateFields(); setStore({ privateKey: privateKey, }); history.push('/new-user/import/private-key/set-password'); }); + const { runAsync: privateKeyValidator, error, loading } = useRequest( + async (_, value: string) => { + if (!value) { + throw new Error('Please input Private key'); + } + return wallet.validatePrivateKey(value); + }, + { + manual: true, + } + ); + return ( - { - history.goBack(); - clearStore(); - }} - step={1} - className="flex flex-col" - > -
-
- {t('page.newUserImport.importPrivateKey.title')} + + { + history.goBack(); + clearStore(); + }} + step={1} + className="flex flex-col" + > +
+
+ {t('page.newUserImport.importPrivateKey.title')} +
+
+ + { + setValue(e.target.value); + }} + onPaste={() => { + clearClipboard(); + message.success({ + icon: ( + + ), + content: t('page.newAddress.seedPhrase.pastedAndClear'), + duration: 2, + }); + }} + /> + +
-
- - - -
-
- - + + + ); }; diff --git a/src/ui/views/NewUserImport/PasswordCard.tsx b/src/ui/views/NewUserImport/PasswordCard.tsx index 4e1e17c9f26..497d87947e1 100644 --- a/src/ui/views/NewUserImport/PasswordCard.tsx +++ b/src/ui/views/NewUserImport/PasswordCard.tsx @@ -7,7 +7,10 @@ import React, { useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { useHistory } from 'react-router-dom'; import styled from 'styled-components'; -import IconCheck from 'ui/assets/check.svg'; +import { ReactComponent as RcIconCheckCC } from 'ui/assets/icon-checked-cc.svg'; +import { ReactComponent as RcIconUnCheckCC } from 'ui/assets/icon-unchecked-cc.svg'; +import { ReactComponent as RcIconSuccessCC } from 'ui/assets/icon-checked-success-cc.svg'; +import { sum } from 'lodash'; const MINIMUM_PASSWORD_LENGTH = 8; @@ -24,7 +27,7 @@ const Container = styled.div` margin-bottom: 24px; } - .ant-input { + /* .ant-input { border-radius: 8px; border: 1px solid var(--r-neutral-line, #e0e5ec); } @@ -34,8 +37,51 @@ const Container = styled.div` border-color: var(--r-blue-default, #7084ff); } - .ant-form-item-has-error .ant-input { - border: 1px solid var(--r-red-default, #e34935); + .ant-form-item-has-error { + margin-bottom: 20px; + .ant-input { + border: 1px solid var(--r-red-default, #e34935); + } + } */ + + .ant-input-affix-wrapper { + &-focused { + border: 1px solid var(--r-blue-default, #7084ff); + } + border-radius: 8px; + border: 1px solid var(--r-neutral-line, #e0e5ec); + .ant-input { + border: none !important; + border-radius: 0 !important; + } + } + .ant-form-item-has-error { + .ant-input-affix-wrapper { + border: 1px solid var(--r-red-default, #e34935); + } + } + + .ant-form-item-explain { + transition: none; + } + + .ant-form-item-explain.ant-form-item-explain-error { + color: var(--r-red-default, #e34935); + font-size: 13px; + font-weight: 400; + line-height: 16px; + min-height: unset; + margin-top: 10px; + } + + .ant-input-suffix { + display: none; + } + + .ant-form-item-has-success { + .ant-input-suffix { + display: flex; + } } `; @@ -73,24 +119,25 @@ export const PasswordCard: React.FC = ({ onSubmit, step, onBack }) => { return ( -
-
-

- {t('page.newUserImport.PasswordCard.title')} -

-

- It will be used to unlock wallet and encrypt data -

-
-
+ +
+
+

+ {t('page.newUserImport.PasswordCard.title')} +

+

+ It will be used to unlock wallet and encrypt data +

+
+ = ({ onSubmit, step, onBack }) => { type="password" autoFocus spellCheck={false} + suffix={ + + + + } /> = ({ onSubmit, step, onBack }) => { )} type="password" spellCheck={false} + suffix={ + + + + } /> - -
-
-
{ - setAgreeTerm((prev) => !prev); - }} - > -
- -
-
- - I agree to the{' '} - { - e.stopPropagation(); - gotoTermsOfUse(); - }} - > - Terms of Use - - and - { - e.stopPropagation(); - gotoPrivacy(); - }} - > - Privacy Policy - - -
+ + {(form) => { + const isDisabled = + form.isFieldsValidating([['password'], ['confirmPassword']]) || + sum(form.getFieldsError().map((item) => item.errors.length)) > + 0 || + !form.isFieldsTouched(); + return ( +
+
{ + setAgreeTerm((prev) => !prev); + }} + > + {agreeTerm ? ( +
+ +
+ ) : ( +
+ +
+ )} +
+ + I agree to the{' '} + { + e.stopPropagation(); + gotoTermsOfUse(); + }} + > + Terms of Use + + and + { + e.stopPropagation(); + gotoPrivacy(); + }} + > + Privacy Policy + + +
+
- -
+ +
+ ); + }} + + ); diff --git a/src/ui/views/NewUserImport/SetPassword.tsx b/src/ui/views/NewUserImport/SetPassword.tsx index 971335bf3b3..c42e675de7c 100644 --- a/src/ui/views/NewUserImport/SetPassword.tsx +++ b/src/ui/views/NewUserImport/SetPassword.tsx @@ -1,16 +1,14 @@ -import { Card } from '@/ui/component/NewUserImport'; +import { KEYRING_CLASS } from '@/constant'; +import { useRabbyDispatch } from '@/ui/store'; +import { useWallet } from '@/ui/utils'; +import { query2obj } from '@/ui/utils/url'; import { useMemoizedFn } from 'ahooks'; -import { Button, Form, Input, message } from 'antd'; -import clsx from 'clsx'; -import React from 'react'; +import { message } from 'antd'; +import React, { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useHistory, useLocation, useParams } from 'react-router-dom'; import { useNewUserGuideStore } from './hooks/useNewUserGuideStore'; import { PasswordCard } from './PasswordCard'; -import { useWallet } from '@/ui/utils'; -import { KEYRING_CLASS, KEYRING_TYPE } from '@/constant'; -import { useRabbyDispatch } from '@/ui/store'; -import { obj2query, query2obj } from '@/ui/utils/url'; export const NewUserSetPassword = () => { const { t } = useTranslation(); @@ -26,87 +24,6 @@ export const NewUserSetPassword = () => { const wallet = useWallet(); const dispatch = useRabbyDispatch(); - const config = { - 'seed-phrase': { - onSubmit: (password: string) => { - return handleSeedPhrase(password); - }, - }, - 'private-key': { - onSubmit: useMemoizedFn(async (password: string) => { - try { - if (!store.privateKey) { - throw new Error('empty private key'); - } - await wallet.boot(password); - await wallet.importPrivateKey(store.privateKey); - // todo - history.push('/new-user/import-success'); - } catch (e) { - console.error(e); - message.error(e.message); - throw e; - } - }), - step: 2, - onBack: useMemoizedFn(() => { - history.goBack(); - }), - }, - 'gnosis-address': { - onSubmit: useMemoizedFn(async (password: string) => { - try { - if (!store.gnosis?.address) { - throw new Error('empty safe address'); - } - await wallet.boot(password); - await wallet.importGnosisAddress( - store.gnosis.address, - store.gnosis.chainList.map((item) => item.network) - ); - // todo - history.push('/new-user/import-success'); - } catch (e) { - console.error(e); - message.error(e.message); - throw e; - } - }), - step: 1, - onBack: useMemoizedFn(() => { - history.goBack(); - }), - }, - ledger: { - onSubmit: useMemoizedFn((password: string) => { - setStore({ - password, - }); - history.push('/new-user/import/ledger'); - }), - step: 1, - onBack: useMemoizedFn(() => { - history.goBack(); - }), - }, - keystone: { - onSubmit: useMemoizedFn((password: string) => { - setStore({ - password, - }); - history.push('/new-user/import/keystone'); - }), - step: 1, - onBack: useMemoizedFn(() => { - history.goBack(); - }), - }, - }; - - const props = config[type]; - if (!props) { - return null; - } const handlePrivateKey = useMemoizedFn(async (password: string) => { try { if (!store.privateKey) { @@ -180,6 +97,25 @@ export const NewUserSetPassword = () => { } }); + const handleGnosis = useMemoizedFn(async (password: string) => { + try { + if (!store.gnosis?.address) { + throw new Error('empty safe address'); + } + await wallet.boot(password); + await wallet.importGnosisAddress( + store.gnosis.address, + store.gnosis.chainList.map((item) => item.network) + ); + // todo + history.push('/new-user/success'); + } catch (e) { + console.error(e); + message.error(e.message); + throw e; + } + }); + const handleSubmit = useMemoizedFn(async (password: string) => { // todo different type if (type === 'private-key') { @@ -189,11 +125,17 @@ export const NewUserSetPassword = () => { handleSeedPhrase(password); } + if (type === 'gnosis-address') { + handleGnosis(password); + } + if ( ([ KEYRING_CLASS.HARDWARE.TREZOR, KEYRING_CLASS.HARDWARE.ONEKEY, KEYRING_CLASS.HARDWARE.GRIDPLUS, + KEYRING_CLASS.HARDWARE.LEDGER, + KEYRING_CLASS.HARDWARE.KEYSTONE, ] as string[]).includes(type) ) setStore({ @@ -203,5 +145,27 @@ export const NewUserSetPassword = () => { return; }); - return ; + const handleBack = useMemoizedFn(() => { + if (history.length > 1) { + history.goBack(); + } else { + window.close(); + } + }); + + const step = useMemo(() => { + return ([ + KEYRING_CLASS.HARDWARE.TREZOR, + KEYRING_CLASS.HARDWARE.ONEKEY, + KEYRING_CLASS.HARDWARE.GRIDPLUS, + KEYRING_CLASS.HARDWARE.LEDGER, + KEYRING_CLASS.HARDWARE.KEYSTONE, + ] as string[]).includes(type) + ? 1 + : 2; + }, [type]); + + return ( + + ); };