diff --git a/app/components/Contacts/ContactsForm.js b/app/components/Contacts/ContactsForm.js index 5a0955a2..51a8d597 100644 --- a/app/components/Contacts/ContactsForm.js +++ b/app/components/Contacts/ContactsForm.js @@ -10,29 +10,30 @@ class ContactsForm extends React.Component { super(props) this.state = { - editing: false, - manualFormInput: '' + editing: false } } render() { const { contactsform, + contactsform: { showErrors }, closeContactsForm, updateContactFormSearchQuery, + updateManualFormSearchQuery, updateContactCapacity, openChannel, - + updateManualFormErrors, activeChannelPubkeys, nonActiveChannelPubkeys, pendingOpenChannelPubkeys, filteredNetworkNodes, loadingChannelPubkeys, - showManualForm + showManualForm, + manualFormIsValid } = this.props - const { editing, manualFormInput } = this.state - + const { editing } = this.state const renderRightSide = (node) => { if (loadingChannelPubkeys.includes(node.pub_key)) { @@ -94,15 +95,21 @@ class ContactsForm extends React.Component { this.setState({ editing: true }) } + const manualFormSubmit = () => { - if (!manualFormInput.length) { return } - if (!manualFormInput.includes('@')) { return } + if (!manualFormIsValid.isValid) { + updateManualFormErrors(manualFormIsValid.errors) + updateManualFormSearchQuery('') + return + } + // clear any existing errors - const [pubkey, host] = manualFormInput && manualFormInput.split('@') + updateManualFormErrors({ manualInput: null }) + const [pubkey, host] = contactsform.manualSearchQuery && contactsform.manualSearchQuery.split('@') openChannel({ pubkey, host, local_amt: contactsform.contactCapacity }) - this.setState({ manualFormInput: '' }) + updateManualFormSearchQuery('') } return ( @@ -170,8 +177,8 @@ class ContactsForm extends React.Component { this.setState({ manualFormInput: event.target.value })} + value={contactsform.manualSearchQuery} + onChange={event => updateManualFormSearchQuery(event.target.value)} />
Submit
@@ -184,6 +191,12 @@ class ContactsForm extends React.Component { } + +
+ {showErrors.manualInput && + {manualFormIsValid && manualFormIsValid.errors.manualInput} + } +
} @@ -220,14 +233,18 @@ class ContactsForm extends React.Component { } } - ContactsForm.propTypes = { contactsform: PropTypes.object.isRequired, closeContactsForm: PropTypes.func.isRequired, updateContactFormSearchQuery: PropTypes.func.isRequired, + updateManualFormSearchQuery: PropTypes.func.isRequired, + manualFormIsValid: PropTypes.shape({ + errors: PropTypes.object, + isValid: PropTypes.bool + }).isRequired, updateContactCapacity: PropTypes.func.isRequired, + updateManualFormErrors: PropTypes.func.isRequired, openChannel: PropTypes.func.isRequired, - activeChannelPubkeys: PropTypes.array.isRequired, nonActiveChannelPubkeys: PropTypes.array.isRequired, pendingOpenChannelPubkeys: PropTypes.array.isRequired, diff --git a/app/components/Contacts/ContactsForm.scss b/app/components/Contacts/ContactsForm.scss index 6b33b320..3c6b7a45 100644 --- a/app/components/Contacts/ContactsForm.scss +++ b/app/components/Contacts/ContactsForm.scss @@ -161,6 +161,18 @@ } } +.errorMessage { + margin: 10px 0; + min-height: 20px; + color: $red; + opacity: 0; + transition: all 0.25s ease; + + &.active { + opacity: 1; + } +} + .footer { padding: 10px 15px; border-top: 1px solid $darkgrey; @@ -243,5 +255,4 @@ -moz-animation: animation-rotate 1000ms linear infinite; -o-animation: animation-rotate 1000ms linear infinite; animation: animation-rotate 1000ms linear infinite; -} - +} \ No newline at end of file diff --git a/app/reducers/contactsform.js b/app/reducers/contactsform.js index 40e2a777..5e5dd17b 100644 --- a/app/reducers/contactsform.js +++ b/app/reducers/contactsform.js @@ -1,12 +1,16 @@ import { createSelector } from 'reselect' - import filter from 'lodash/filter' +import isEmpty from 'lodash/isEmpty' // Initial State const initialState = { isOpen: false, searchQuery: '', - contactCapacity: 0.1 + manualSearchQuery: '', + contactCapacity: 0.1, + showErrors: { + manualInput: false + } } // Constants @@ -18,6 +22,10 @@ export const UPDATE_CONTACT_FORM_SEARCH_QUERY = 'UPDATE_CONTACT_FORM_SEARCH_QUER export const UPDATE_CONTACT_CAPACITY = 'UPDATE_CONTACT_CAPACITY' +export const UPDATE_MANUAL_FORM_ERRORS = 'UPDATE_MANUAL_FORM_ERRORS' + +export const UPDATE_MANUAL_FORM_SEARCH_QUERY = 'UPDATE_MANUAL_FORM_SEARCH_QUERY' + // ------------------------------------ // Actions // ------------------------------------ @@ -40,6 +48,13 @@ export function updateContactFormSearchQuery(searchQuery) { } } +export function updateManualFormSearchQuery(manualSearchQuery) { + return { + type: UPDATE_MANUAL_FORM_SEARCH_QUERY, + manualSearchQuery + } +} + export function updateContactCapacity(contactCapacity) { return { type: UPDATE_CONTACT_CAPACITY, @@ -47,6 +62,13 @@ export function updateContactCapacity(contactCapacity) { } } +export function updateManualFormErrors(errorsObject) { + return { + type: UPDATE_MANUAL_FORM_ERRORS, + errorsObject + } +} + // ------------------------------------ // Action Handlers // ------------------------------------ @@ -56,7 +78,13 @@ const ACTION_HANDLERS = { [UPDATE_CONTACT_FORM_SEARCH_QUERY]: (state, { searchQuery }) => ({ ...state, searchQuery }), - [UPDATE_CONTACT_CAPACITY]: (state, { contactCapacity }) => ({ ...state, contactCapacity }) + [UPDATE_MANUAL_FORM_SEARCH_QUERY]: (state, { searchQuery }) => ({ ...state, searchQuery }), + + [UPDATE_CONTACT_CAPACITY]: (state, { contactCapacity }) => ({ ...state, contactCapacity }), + + [UPDATE_MANUAL_FORM_ERRORS]: (state, { errorsObject }) => ({ ...state, showErrors: Object.assign(state.showErrors, errorsObject) }), + + [UPDATE_MANUAL_FORM_SEARCH_QUERY]: (state, { manualSearchQuery }) => ({ ...state, manualSearchQuery }) } // ------------------------------------ @@ -65,6 +93,7 @@ const ACTION_HANDLERS = { const contactFormSelectors = {} const networkNodesSelector = state => state.network.nodes const searchQuerySelector = state => state.contactsform.searchQuery +const manualSearchQuerySelector = state => state.contactsform.manualSearchQuery contactFormSelectors.filteredNetworkNodes = createSelector( @@ -97,6 +126,21 @@ contactFormSelectors.showManualForm = createSelector( } ) +contactFormSelectors.manualFormIsValid = createSelector( + manualSearchQuerySelector, + (input) => { + const errors = {} + if (!input.length || !input.includes('@')) { + errors.manualInput = 'Invalid format' + } + return { + errors, + isValid: isEmpty(errors) + } + } +) + + export { contactFormSelectors } // ------------------------------------ @@ -106,4 +150,4 @@ export default function contactFormReducer(state = initialState, action) { const handler = ACTION_HANDLERS[action.type] return handler ? handler(state, action) : state -} +} \ No newline at end of file diff --git a/app/routes/contacts/containers/ContactsContainer.js b/app/routes/contacts/containers/ContactsContainer.js index b0afb19a..040d389a 100644 --- a/app/routes/contacts/containers/ContactsContainer.js +++ b/app/routes/contacts/containers/ContactsContainer.js @@ -24,8 +24,10 @@ import { openContactsForm, closeContactsForm, updateContactFormSearchQuery, + updateManualFormSearchQuery, updateContactCapacity, - contactFormSelectors + contactFormSelectors, + updateManualFormErrors } from 'reducers/contactsform' import Contacts from '../components/Contacts' @@ -36,13 +38,14 @@ const mapDispatchToProps = { openContactModal, closeContactModal, updateContactFormSearchQuery, + updateManualFormSearchQuery, updateContactCapacity, openChannel, closeChannel, updateChannelSearchQuery, toggleFilterPulldown, changeFilter, - + updateManualFormErrors, fetchChannels, fetchPeers, fetchDescribeNetwork @@ -66,7 +69,8 @@ const mapStateToProps = state => ({ channelNodes: channelsSelectors.channelNodes(state), filteredNetworkNodes: contactFormSelectors.filteredNetworkNodes(state), - showManualForm: contactFormSelectors.showManualForm(state) + showManualForm: contactFormSelectors.showManualForm(state), + manualFormIsValid: contactFormSelectors.manualFormIsValid(state) }) const mergeProps = (stateProps, dispatchProps, ownProps) => { @@ -77,23 +81,25 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { isOpen: stateProps.channels.contactModal.isOpen, channel: stateProps.channels.contactModal.channel, channelNodes: stateProps.channelNodes, - closingChannelIds: stateProps.channels.closingChannelIds + closingChannelIds: stateProps.channels.closingChannelIds, + manualFormIsValid: stateProps.manualFormIsValid } const contactsFormProps = { closeContactsForm: dispatchProps.closeContactsForm, updateContactFormSearchQuery: dispatchProps.updateContactFormSearchQuery, + updateManualFormSearchQuery: dispatchProps.updateManualFormSearchQuery, updateContactCapacity: dispatchProps.updateContactCapacity, openChannel: dispatchProps.openChannel, - contactsform: stateProps.contactsform, filteredNetworkNodes: stateProps.filteredNetworkNodes, loadingChannelPubkeys: stateProps.channels.loadingChannelPubkeys, showManualForm: stateProps.showManualForm, - + manualFormIsValid: stateProps.manualFormIsValid, activeChannelPubkeys: stateProps.activeChannelPubkeys, nonActiveChannelPubkeys: stateProps.nonActiveChannelPubkeys, - pendingOpenChannelPubkeys: stateProps.pendingOpenChannelPubkeys + pendingOpenChannelPubkeys: stateProps.pendingOpenChannelPubkeys, + updateManualFormErrors: dispatchProps.updateManualFormErrors } return { @@ -106,4 +112,4 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { } } -export default withRouter(connect(mapStateToProps, mapDispatchToProps, mergeProps)(Contacts)) +export default withRouter(connect(mapStateToProps, mapDispatchToProps, mergeProps)(Contacts)) \ No newline at end of file