Skip to content

Commit

Permalink
ft(Member invitation): Invite member to a team
Browse files Browse the repository at this point in the history
- Converts mock-up to react component
- customized react react-select to hold image
- Added a method that get values from multiple select
[Finishes #161055725]
  • Loading branch information
Kevin Eze authored and Kevin Eze committed Oct 9, 2018
1 parent 09908a0 commit ad8616c
Show file tree
Hide file tree
Showing 9 changed files with 487 additions and 5 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"env": {
"node": true,
"es6": true,
"mocha": true
"mocha": true,
"jest": true
},
"globals": {
"document": true,
Expand Down
Binary file added client/public/resources/images/github.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/public/resources/images/select.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
136 changes: 133 additions & 3 deletions client/src/modules/Teams/components/InviteMembers.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import ReactTooltip from 'react-tooltip';
import Select, { components } from 'react-select';

// Actions
import { searchUser } from '../../../redux/actions/users';
Expand All @@ -11,19 +12,26 @@ import MemberCard from './MemberCard';

export class InviteMember extends Component {
static propTypes = {
// prop: PropTypes
searchUser: PropTypes.func.isRequired,
addMember: PropTypes.func.isRequired
};

constructor(props) {
super(props);
this.state = {
searchInput: ''
searchInput: '',
accounts: { value: 'ghoullies-bot', label: 'ghoullies-bot', type: 'slack_channel' },
ismultiSelectDisabled: false,
selectAllDisabled: true
};

this.handleSearch = this.handleSearch.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.renderSearchOutput = this.renderSearchOutput.bind(this);
// this.addMember = this.addMember.bind(this);
this.handleSelected = this.handleSelected.bind(this);
this.inviteMember = this.inviteMember.bind(this);
this.isDisabled = this.isDisabled.bind(this);
}

handleSearch(event) {
Expand All @@ -32,6 +40,7 @@ export class InviteMember extends Component {

handleSubmit(event) {
event.preventDefault();
this.setState({ searchInput: event.target.value });
const { searchInput } = this.state;
this.props.searchUser(searchInput);
this.setState({ searchInput: '' });
Expand All @@ -55,15 +64,87 @@ export class InviteMember extends Component {
});
}

handleSelected(selectedOption) {
this.setState({ accounts: selectedOption });
console.log(selectedOption);
}

inviteMember(e) {
e.preventDefault();
if (!this.state.ismultiSelectDisabled && this.state.selectAllDisabled) {
const { accounts } = this.state;
console.log(accounts);
} else if (this.state.ismultiSelectDisabled && !this.state.selectAllDisabled) {
// const { addMember } = this.props;
}
}

isDisabled(value) {
if (value === 'select') {
this.setState({ ismultiSelectDisabled: false, selectAllDisabled: true });
} else {
this.setState({ ismultiSelectDisabled: true, selectAllDisabled: false });
}
}

render() {
const { searchInput } = this.state;
const { users } = this.props;

const MultiValueLabel = (props) => (
<components.MultiValueLabel {...props}>
{props.data.type === 'github_repo' ?
<label className="select-account-label">
<img src="/resources/images/github.png" className="account-icon" alt="github" />
{` ${props.data.label}`}
</label> :
<label>
{props.data.type === 'slack_channel' ?
<label className="select-account-label">
<img src="/resources/images/slack.png" className="account-icon" alt="slack" />
{` ${props.data.label}`}
</label> :
<label className="select-account-label">
<img src="/resources/images/pt.jpg" className="account-icon" alt="pt" />
{` ${props.data.label}`}
</label>}
</label>}
</components.MultiValueLabel>
);

const dropDown = (props) => (
<components.Option {...props}>
{props.data.type === 'github_repo' ?
<label className="select-account-dropdown">
<img src="/resources/images/github.png" className="account-icon" alt="github" />
{` ${props.data.label}`}
</label> :
<label>
{props.data.type === 'slack_channel' ?
<label className="select-account-dropdown">
<img src="/resources/images/slack.png" className="account-icon" alt="slack" />
{` ${props.data.label}`}
</label> :
<label className="select-account-dropdown">
<img src="/resources/images/pt.jpg" className="account-icon" alt="pt" />
{` ${props.data.label}`}
</label>}
</label>}
</components.Option>
);

const options = [
{ value: 'ah-tap', label: 'ah-tap', type: 'github_repo' },
{ value: 'ghoullies-bot', label: 'ghoullies-bot', type: 'slack_channel' },
{ value: 'Ghoullies-taps', label: 'Ghoullies-taps', type: 'pt_project' }
];

return (
<React.Fragment>
<div className="row">
<div className="col s12">
<form onSubmit={this.handleSubmit}>
<div className="input-field inline col s11 custom-form">
<div className="input-field inline col s11 custom-form team-account-wrapper">
<i className="material-icons prefix">search</i>
<input
id="invite-members"
Expand All @@ -79,6 +160,55 @@ export class InviteMember extends Component {
</div>
{users && this.renderSearchOutput(users)}
<ReactTooltip />
{this.state.searchInput &&
<div className="row account-row">
<div className="col s2" />
<div className="col s7">
<h5 className="center-align">
Add
<span className="member-username">
{` ${this.state.searchInput}`}
</span> to any of these tools
</h5>
<div className="col s12 team-account-wrapper">
<form className="row team-accout-form">
<div className="">
<label>
<input className="with-gap select-all" name="select" type="radio" onClick={() => this.isDisabled('all')} />
<span><img src="/resources/images/select.png" alt="icon" height="30px" width="30px" /></span>
</label>
<span className="span">Add user to all account</span>
</div>
<br />
<div>
<label>
<input onClick={() => this.isDisabled('select')} className="with-gap select-some" name="select" type="radio" defaultChecked />
<span />
<label className="select">
<Select
closeMenuOnSelect={false}
components={{ MultiValueLabel, Option: dropDown }}
styles={{
multiValueRemove: (base) => ({ ...base, fontSize: '15px', color: '#385cd7' })
}}
defaultValue={[options[1]]}
isMulti
options={options}
onChange={this.handleSelected}
isDisabled={this.state.ismultiSelectDisabled}
/>
</label>
</label>
</div>
<button onClick={this.inviteMember} className="btn account-btn" type="submit" name="action">
Invite
</button>
</form>
</div>
</div>
<div className="col s3" />
</div>
}
</React.Fragment>
);
}
Expand Down
52 changes: 52 additions & 0 deletions client/src/tests/modules/teams/components/InviteMembers.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react';
import { shallow } from 'enzyme';
import { InviteMember } from '../../../../modules/Teams/components/InviteMembers';
import { props, emptyProps } from '../teamsMockData';

describe('<Test Component />', () => {
it('should mount without crashing', () => {
const wrapper = shallow(<InviteMember {...props} />);
expect(wrapper).toMatchSnapshot();
});

it('should mount with empty props', () => {
const wrapper = shallow(<InviteMember {...emptyProps} />);
expect(wrapper).toMatchSnapshot();
});

it('Should respond to an onChange event of search input', () => {
const wrapper = shallow(<InviteMember {...props} />);
wrapper.find('#invite-members').simulate('change', { target: { value: 'kevin' } });
expect(wrapper).toMatchSnapshot();
});

it('Radio button should respond to an onClick event', () => {
const wrapper = shallow(<InviteMember {...props} />);
wrapper.setState({ searchInput: 'kevin' });
wrapper.find('.select-all').simulate('click');
expect(wrapper).toMatchSnapshot();
wrapper.find('.select-some').simulate('click');
expect(wrapper).toMatchSnapshot();
});

it('handleSelected class methods should be called', () => {
const handleSelected = jest.spyOn(InviteMember.prototype, 'handleSelected');
const wrapper = shallow(<InviteMember {...props} />);
wrapper.instance().handleSelected();
expect(handleSelected).toHaveBeenCalled();
});

it('inviteMember class methods should be called', () => {
const inviteMember = jest.spyOn(InviteMember.prototype, 'inviteMember');
const wrapper = shallow(<InviteMember {...props} />);
wrapper.instance().inviteMember({ preventDefault: jest.fn() });
expect(inviteMember).toHaveBeenCalled();
});

it('isDisabled class methods should be called', () => {
const isDisabled = jest.spyOn(InviteMember.prototype, 'isDisabled');
const wrapper = shallow(<InviteMember {...props} />);
wrapper.instance().isDisabled();
expect(isDisabled).toHaveBeenCalled();
});
});
15 changes: 15 additions & 0 deletions client/src/tests/modules/teams/teamsMockData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

export const props = {
searchUser: jest.fn(),
addMember: jest.fn(),
users: [{
role: 'admin',
email: 'eze.kevin@andela.com',
githubUsername: 'kenware'
}]
};

export const emptyProps = {
searchUser: jest.fn(),
addMember: jest.fn()
};
76 changes: 76 additions & 0 deletions client/styles/components/_teams.scss
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,80 @@
width: 4rem;
height: 4rem;
border-radius: 50%;
}

.account-row {
margin-top: 4rem;
.team-account-wrapper {
background-color: #F1F1F1; //#EBEBEB;
border-radius: 10px;
.team-accout-form {
padding: 3rem 1rem 4rem 4rem;
.select {
cursor: pointer;
display: inline-block;
width: 70%;
}
.account-btn {
margin: 5rem 21% 1rem;
float: right;
background-color: #385cd7 !important;
}
}
.span {
font-size: 18px;
margin: 0.5rem 1rem 0 1rem;
position: absolute;
}
}
.member-username {
margin-top: 2rem;
color:#385cd7 !important
}
}

input[type="radio"]:checked+span:after, [type="radio"].with-gap:checked+span:after {
background-color: #385cd7 !important;
}

input[type="radio"]:checked+span:after, [type="radio"].with-gap:checked+span:before, [type="radio"].with-gap:checked+span:after {
border: 2px solid #385cd7 !important;
}

.account-select-label {
color: '#385cd7';
font-size: '15px';
}

.account-icon {
height: 15px;
width: 15px;
}

.team-account-wrapper {
background-color: #F1F1F1; //#EBEBEB;
border-radius: 10px;
}

.swal-button, .swal-button-confirm {
background-color: #385cd7 !important;
color: #fff;
border: none;
box-shadow: none;
border-radius: 5px;
font-weight: 600;
font-size: 14px;
padding: 10px 24px;
margin: 0;
cursor: pointer;
}

.select-account-label {
font-size: 15px;
color: #385cd7;
}

.select-account-dropdown{
font-size: 15px;
display: 1px solid bold;
}
Loading

0 comments on commit ad8616c

Please sign in to comment.