Skip to content

Commit

Permalink
feat(redeem): add redeem points dialog
Browse files Browse the repository at this point in the history
 - add createRedemptionRequest and createRedemptionSuccess actions
 - add createRedemption generator
 - add dollarsToPointsConverter util
 - add redemption dialog styles
 - add RedeemPointsDialogComponent tests
 - add RedemptionsContainer tests
 - add action, reducer and saga tests
 - add money svg image
  • Loading branch information
Chris Maina committed Apr 15, 2019
1 parent bdb1a80 commit 91ed520
Show file tree
Hide file tree
Showing 21 changed files with 611 additions and 8 deletions.
155 changes: 155 additions & 0 deletions src/app/Redemptions/components/RedeemPointsDialogComponent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import React from 'react';
import PropTypes from 'prop-types';
import Select from '@material-ui/core/Select';
import Dialog from '@material-ui/core/Dialog';
import CloseIcon from '@material-ui/icons/Close';
import MenuItem from '@material-ui/core/MenuItem';
import TextField from '@material-ui/core/TextField';
import Input from '@material-ui/core/Input';
import InputLabel from '@material-ui/core/InputLabel';
import FormControl from '@material-ui/core/FormControl';
import FormHelperText from '@material-ui/core/FormHelperText';

import { ButtonComponent } from '../../common/components';

import centers from '../contants';

const RedeemPointsDialogComponent = ({
open,
date,
errors,
reason,
center,
points,
usdValue,
onClose,
onChange,
handleRedemptionSubmit,
}) => (
<Dialog
disableEscapeKeyDown
onClose={onClose}
open={open}
scroll='body'
PaperProps={{
style: {
width: '498px',
maxwidth: '498px',
height: '750px',
overflow: 'visible',
padding: '30px 86px 0 86px',
},
}}
>
<div className='redeem-points'>
<div className='redeem-points__icon'>
<CloseIcon aria-label='Close' onClick={onClose} />
</div>
<div className='redeem-points__image' />

{/* Dialog Title */}
<p className='redeem-points__title'>Request Cash</p>

{/* Dialog Content */}
<form className='redeem-points__form'>
<TextField
name='usdValue'
type='number'
value={usdValue}
onChange={onChange}
label='How much is needed (USD)'
className='redeem-points__input'
/>
<FormHelperText error={!!errors.usdValue} className='redeem-points__error'>
{errors.usdValue}
</FormHelperText>
{usdValue && (
<div className='redeem-points__description'>
<i className='fa fa-info-circle' aria-hidden />
&nbsp;
{`${usdValue} USD = ${points} Points`}
</div>
)}
<TextField
name='date'
type='date'
value={date}
onChange={onChange}
label='When ?'
className='redeem-points__input'
InputLabelProps={{
shrink: true,
}}
/>
<FormHelperText error={!!errors.date} className='redeem-points__error'>
{errors.date}
</FormHelperText>
<TextField
name='reason'
value={reason}
onChange={onChange}
label='What for ?'
className='redeem-points__input'
/>
<FormHelperText error={!!errors.reason} className='redeem-points__error'>
{errors.reason}
</FormHelperText>
<FormControl>
<InputLabel className='redeem-points__label' htmlFor='age-simple'>
Which center ?
</InputLabel>
<Select
fullWidth
name='center'
value={center}
onChange={onChange}
error={!!errors.center}
className='redeem-points__input'
input={<Input name='center' id='age-simple' />}
>
{centers.map(item => (
<MenuItem value={item.id} key={item.id}>
{item.name}
</MenuItem>
))}
</Select>
<FormHelperText error={!!errors.center} className='redeem-points__error'>
{errors.center}
</FormHelperText>
</FormControl>
{/* Action buttons */}
<ButtonComponent className='redeem-points__button' onClick={handleRedemptionSubmit}>
<span>Redeem</span>
</ButtonComponent>
</form>
</div>
</Dialog>
);

RedeemPointsDialogComponent.defaultProps = {
date: '',
reason: '',
points: '',
center: '',
errors: {},
open: false,
usdValue: null,
onClose: null,
onChange: null,
handleRedemptionSubmit: null,
};

RedeemPointsDialogComponent.propTypes = {
open: PropTypes.bool,
date: PropTypes.string,
onClose: PropTypes.func,
center: PropTypes.string,
points: PropTypes.number,
onChange: PropTypes.func,
reason: PropTypes.string,
errors: PropTypes.shape({}),
handleRedemptionSubmit: PropTypes.func,
usdValue: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
};

export default RedeemPointsDialogComponent;
97 changes: 95 additions & 2 deletions src/app/Redemptions/components/RedemptionsContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import dashboardActions from '../../Dashboard/operations/actions';
import societyActions from '../../Societies/operations/actions';
import { getUserInfo, getToken } from '../../utils/tokenIsValid';
import { getUserInfo, getToken, dollarsToPointsConverter } from '../../utils';

import RedeemPointsModal from './RedeemPointsDialogComponent';
import RedemptionsComponent from './RedemptionsComponent';
import { ButtonComponent, LoaderComponent, SocietyStatsComponent } from '../../common/components';

export class RedemptionsContainer extends Component {
static defaultProps = {
society: {},
societyName: '',
createRedemption: null,
fetchUserActivites: null,
fetchSocietyInfoRequest: null,
fetchSocietyRedemptionsRequest: null,
Expand All @@ -20,11 +22,26 @@ export class RedemptionsContainer extends Component {
static propTypes = {
society: PropTypes.shape({}),
societyName: PropTypes.string,
createRedemption: PropTypes.func,
fetchUserActivites: PropTypes.func,
fetchSocietyInfoRequest: PropTypes.func,
fetchSocietyRedemptionsRequest: PropTypes.func,
};

initialState = {
date: '',
errors: {},
reason: '',
points: null,
center: '',
usdValue: '',
openRedeemPointsModal: false,
}

state = {
...this.initialState,
};

componentDidMount() {
const {
fetchSocietyRedemptionsRequest, fetchSocietyInfoRequest, societyName, fetchUserActivites,
Expand All @@ -44,7 +61,67 @@ export class RedemptionsContainer extends Component {
}
}

showRedeemPointsModal = (bool) => {
this.setState({ openRedeemPointsModal: bool });
};

handleChange = (event) => {
const { name, value } = event.target;
if (name === 'usdValue') {
this.setState({ points: dollarsToPointsConverter(value) });
}
this.setState(prevState => ({
[name]: value,
errors: {
...prevState.errors,
[name]: '',
},
}));
};

validateFormFields = (values) => {
const errors = {};
const required = ['date', 'reason', 'usdValue', 'center'];
required.forEach((name) => {
if (!values[name] || values[name] === '' || values[name] === 0) {
errors[name] = 'This field is required';
}
});
return errors;
};

handleRedemptionSubmit = () => {
// action to create a redemption
const {
date, reason, center, points, usdValue,
} = this.state;
const { createRedemption, societyName } = this.props;
const errors = this.validateFormFields({
date,
reason,
center,
usdValue,
});
if (Object.keys(errors) && Object.keys(errors).length > 0) {
this.setState({ errors });
return;
}
createRedemption(
{
date,
reason,
points,
center,
},
societyName.toLowerCase(),
);
this.setState({ ...this.initialState });
};

render() {
const {
date, errors, center, points, reason, usdValue, openRedeemPointsModal,
} = this.state;
const { society, societyName } = this.props;
let redemptionsHtml = <LoaderComponent className='loader' />;
if (societyName) {
Expand Down Expand Up @@ -72,7 +149,10 @@ export class RedemptionsContainer extends Component {
<span className='fa fa-plus' />
<span>Log Points</span>
</ButtonComponent>
<ButtonComponent className='button__add button__redemption'>
<ButtonComponent
className='button__add button__redemption'
onClick={() => this.showRedeemPointsModal(true)}
>
<span className='fa fa-plus' />
<span>New Redemption</span>
</ButtonComponent>
Expand All @@ -82,6 +162,18 @@ export class RedemptionsContainer extends Component {
</ButtonComponent>
</div>
</div>
<RedeemPointsModal
date={date}
errors={errors}
points={points}
reason={reason}
center={center}
usdValue={usdValue}
open={openRedeemPointsModal}
onChange={this.handleChange}
onClose={() => this.showRedeemPointsModal(false)}
handleRedemptionSubmit={this.handleRedemptionSubmit}
/>
<RedemptionsComponent activities={redemptions} />
</div>
);
Expand All @@ -96,6 +188,7 @@ const mapStateToProps = ({ society, dashboard }) => ({
});

const mapDispatchToProps = {
createRedemption: societyActions.createRedemptionRequest,
fetchUserActivites: dashboardActions.fetchUserActivitiesRequest,
fetchSocietyInfoRequest: societyActions.fetchSocietyInfoRequest,
fetchSocietyRedemptionsRequest: societyActions.fetchSocietyRedemptionsRequest,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import { shallow } from 'enzyme';
import RedeemPointsDialogComponent from '../RedeemPointsDialogComponent';

describe('RedeemPointsDialogComponent', () => {
const props = {
open: true,
date: null,
reason: '',
points: '',
errors: {},
usdValue: 0,
onClose: jest.fn(),
onChange: jest.fn(),
handleRedemptionSubmit: jest.fn(),
}
const wrapper = shallow(<RedeemPointsDialogComponent {...props} />);

it('has 3 TextField components', () => {
expect(wrapper.find('TextField')).toHaveLength(3);
});

it('has ButtonComponent component', () => {
expect(wrapper.find('ButtonComponent')).toHaveLength(1);
});
});
Loading

0 comments on commit 91ed520

Please sign in to comment.