diff --git a/features/login.feature b/features/login.feature index ed5188940..ba3189ef2 100644 --- a/features/login.feature +++ b/features/login.feature @@ -32,7 +32,6 @@ Feature: Login page Then I should be logged in And I should see text "Testnet" in "peer network" element - @ignore Scenario: should allow to create a new account Given I'm on login page When I click "new account button" diff --git a/src/components/app/index.js b/src/components/app/index.js index 8f516fa21..25f392af0 100644 --- a/src/components/app/index.js +++ b/src/components/app/index.js @@ -31,6 +31,7 @@ const App = () => ( )} /> + diff --git a/src/components/dialog/dialog.js b/src/components/dialog/dialog.js index e1bdfa7ac..65adec27b 100644 --- a/src/components/dialog/dialog.js +++ b/src/components/dialog/dialog.js @@ -10,56 +10,81 @@ class DialogElement extends Component { constructor() { super(); this.state = {}; - this.path = { - name: '/', + this.routesReg = [ + { + regex: /\/main\/transactions(?:\/[^/]*)?$/, + path: '/main/transactions/', + params: 'dialog', + name: 'transactions', + }, { + regex: /\/main\/voting(?:\/[^/]*)?$/, + path: '/main/voting/', + params: 'dialog', + name: 'voting', + }, { + regex: /\/main\/forging(?:\/[^/]*)?$/, + path: '/main/forging/', + params: 'dialog', + name: 'forging', + }, { + regex: /\/(\w+)?$/, + path: '/', + params: 'dialog', + name: 'login', + }, + ]; + this.current = { + pathname: '/', + reg: this.routesReg[3], list: [], dialog: '', }; } componentDidMount() { - this.checkForDialog(this.props.history.location); + this.checkForDialog(); } componentDidUpdate() { - if (this.path.name !== this.props.history.location.pathname) { - this.path.name = this.props.history.location.pathname; - this.checkForDialog(this.props.history.location); + this.checkForDialog(); + } + + checkForDialog() { + if (this.current.pathname !== this.props.history.location.pathname) { + this.current.reg = this.routesReg.find(item => + item.regex.test(this.props.history.location.pathname)); + this.current.pathname = this.props.history.location.pathname; + const dialogName = this.props.history.location.pathname.replace(this.current.reg.path, ''); + if (dialogs[dialogName] !== undefined) { + this.open(this.current.reg, dialogs[dialogName]); + } else { + this.close(); + } } } - checkForDialog(location) { - const parseParams = search => search.replace(/^\?/, '').split('&&').reduce((acc, param) => { + // eslint-disable-next-line class-methods-use-this + parseParams(search) { + return search.replace(/^\?/, '').split('&&').reduce((acc, param) => { const keyValue = param.split('='); if (keyValue[0] !== '' && keyValue[1] !== 'undefined') { acc[keyValue[0]] = keyValue[1]; } return acc; }, {}); - - this.path.list = location.pathname.replace(/\/$/, '').split('/'); - this.dialog = this.path.list[3]; - - if (this.path.list.length === 5) { - this.props.history.push(`/${this.path.list[1]}/${this.path.list[2]}/${this.path.list[4]}`); - } else if (this.path.list.length === 4 && Object.keys(dialogs).includes(this.dialog)) { - this.routeWithDialog(dialogs[this.dialog], parseParams(location.search)); - } else { - this.routeWithOutDialog(); - } } - routeWithDialog(dialog, childComponentProps) { + open(config, dialog) { clearTimeout(this.timeout); this.setState({ hidden: false }); this.props.dialogDisplayed({ title: dialog.title, childComponent: dialog.component, - childComponentProps, + childComponentProps: this.parseParams(this.props.history.location.search), }); } - routeWithOutDialog() { + close() { this.timeout = setTimeout(() => { this.props.dialogHidden(); this.setState({ hidden: false }); @@ -67,13 +92,8 @@ class DialogElement extends Component { this.setState({ hidden: true }); } - closeDialog() { - if (this.props.dialog.childComponentProps.noRouter) { - this.routeWithOutDialog(); - } else { - const upperRoute = this.path.name.replace(/\/$/, '').replace(this.dialog, ''); - this.props.history.push(upperRoute); - } + goBack() { + this.props.history.push(this.current.reg.path); } render() { @@ -85,14 +105,14 @@ class DialogElement extends Component { - +
{this.props.dialog.childComponent ? : null } diff --git a/src/components/dialog/dialogs.js b/src/components/dialog/dialogs.js index 89f81eeda..9256dbb27 100644 --- a/src/components/dialog/dialogs.js +++ b/src/components/dialog/dialogs.js @@ -6,6 +6,7 @@ import SecondPassphrase from '../secondPassphrase'; import VoteDialog from '../voteDialog'; import ReceiveDialog from '../receiveDialog'; import SaveAccount from '../saveAccount'; +import Register from '../register'; export default { send: { @@ -36,6 +37,10 @@ export default { title: 'Receive LSK', component: ReceiveDialog, }, + register: { + title: 'New Account', + component: Register, + }, 'save-account': { title: 'Remember this account', component: SaveAccount, diff --git a/src/components/login/login.js b/src/components/login/login.js index 83439f1fa..0b54a5b18 100644 --- a/src/components/login/login.js +++ b/src/components/login/login.js @@ -7,7 +7,7 @@ import networksRaw from './networks'; import PassphraseInput from '../passphraseInput'; import styles from './login.css'; import env from '../../constants/env'; -import Passphrase from '../passphrase'; +import RelativeLink from '../relativeLink'; /** * The container component containing login @@ -188,25 +188,13 @@ class Login extends React.Component { onChange={this.changeHandler.bind(this, 'passphrase')} />
-
diff --git a/src/components/register/index.js b/src/components/register/index.js new file mode 100644 index 000000000..299aed481 --- /dev/null +++ b/src/components/register/index.js @@ -0,0 +1,15 @@ +import { connect } from 'react-redux'; +import { translate } from 'react-i18next'; +import { dialogDisplayed } from '../../actions/dialog'; +import { activePeerSet } from '../../actions/peers'; +import Register from './register'; + +const mapDispatchToProps = dispatch => ({ + setActiveDialog: data => dispatch(dialogDisplayed(data)), + activePeerSet: data => dispatch(activePeerSet(data)), +}); + +export default connect( + null, + mapDispatchToProps, +)(translate()(Register)); diff --git a/src/components/register/index.test.js b/src/components/register/index.test.js new file mode 100644 index 000000000..9d4631d4c --- /dev/null +++ b/src/components/register/index.test.js @@ -0,0 +1,31 @@ +import React from 'react'; +import { expect } from 'chai'; +import { mount } from 'enzyme'; +import { Provider } from 'react-redux'; +import { I18nextProvider } from 'react-i18next'; +import configureMockStore from 'redux-mock-store'; +import i18n from '../../i18n'; +import Register from './index'; + + +describe('RegisterHOC', () => { + let wrapper; + const peers = {}; + const account = {}; + const store = configureMockStore([])({ + peers, + account, + }); + + beforeEach(() => { + wrapper = mount( + + + + ); + }); + + it('should render Register', () => { + expect(wrapper.find('Register')).to.have.lengthOf(1); + }); +}); diff --git a/src/components/register/register.css b/src/components/register/register.css new file mode 100644 index 000000000..f4d336fdf --- /dev/null +++ b/src/components/register/register.css @@ -0,0 +1,3 @@ +.hidden { + display: none; +} diff --git a/src/components/register/register.js b/src/components/register/register.js new file mode 100644 index 000000000..0b2a95901 --- /dev/null +++ b/src/components/register/register.js @@ -0,0 +1,60 @@ +import React from 'react'; +import Passphrase from '../passphrase'; +import networksRaw from '../login/networks'; + +const Register = ({ + activePeerSet, closeDialog, t, +}) => { + const validateUrl = (value) => { + const addHttp = (url) => { + const reg = /^(?:f|ht)tps?:\/\//i; + return reg.test(url) ? url : `http://${url}`; + }; + + const errorMessage = 'URL is invalid'; + + const isValidLocalhost = url => url.hostname === 'localhost' && url.port.length > 1; + const isValidRemote = url => /(([a-z\d]([a-z\d-]*[a-z\d])*)\.)+[a-z]{2,}|((\d{1,3}\.){3}\d{1,3})/.test(url.hostname); + + let addressValidity = ''; + try { + const url = new URL(addHttp(value)); + addressValidity = url && (isValidRemote(url) || isValidLocalhost(url)) ? '' : errorMessage; + } catch (e) { + addressValidity = errorMessage; + } + + return addressValidity === ''; + }; + + const onLoginSubmission = (passphrase) => { + let NetworkIndex = parseInt(localStorage.getItem('network'), 10) || 0; + const address = localStorage.getItem('address') || ''; + if (!NetworkIndex || (NetworkIndex === 2 && !validateUrl(address))) { + NetworkIndex = 0; + } + + const network = Object.assign({}, networksRaw[NetworkIndex]); + if (NetworkIndex === 2) { + network.address = address; + } + + // set active peer + activePeerSet({ + passphrase, + network, + }); + }; + + return ( + + ); +}; + +export default Register; diff --git a/src/components/register/register.test.js b/src/components/register/register.test.js new file mode 100644 index 000000000..3c820897d --- /dev/null +++ b/src/components/register/register.test.js @@ -0,0 +1,56 @@ +import React from 'react'; +import { expect } from 'chai'; +import { mount } from 'enzyme'; +import { spy } from 'sinon'; +import PropTypes from 'prop-types'; +import configureMockStore from 'redux-mock-store'; +import Register from './register'; + +describe('Register', () => { + let wrapper; + const peers = { data: {} }; + const account = {}; + const store = configureMockStore([])({ + peers, + account, + activePeerSet: () => {}, + }); + const options = { + context: { store }, + childContextTypes: { + store: PropTypes.object.isRequired, + }, + }; + const prop = { + account, + peers, + activePeerSet: spy(), + t: key => key, + }; + + beforeEach(() => { + wrapper = mount(, options); + }); + + it('renders Passphrase component', () => { + expect(wrapper.find('Passphrase')).to.have.length(1); + }); + + it('should mount Register with appropriate properties', () => { + const props = wrapper.find('Passphrase').props(); + expect(props.useCaseNote).to.be.equal('your passphrase will be required for logging in to your account.'); + expect(props.securityNote).to.be.equal('This passphrase is not recoverable and if you lose it, you will lose access to your account forever.'); + expect(props.confirmButton).to.be.equal('Login'); + expect(props.keepModal).to.be.equal(false); + expect(typeof props.onPassGenerated).to.be.equal('function'); + }); + + it('should call activePeerSet if props.onPassGenerated is called', () => { + const props = wrapper.find('Passphrase').props(); + props.onPassGenerated('sample passphrase'); + expect(prop.activePeerSet).to.have.been.calledWith({ + network: { name: 'Mainnet', port: 443, ssl: true }, + passphrase: 'sample passphrase', + }); + }); +});