Skip to content

Commit

Permalink
Update Events Form to Formik
Browse files Browse the repository at this point in the history
Also add Datetime picker to start and end date

Also add additional validations:
- required fields
- start and end date occur in the future
- end date occurs after start date
- event name warns when over 25 characters

Also fix issue where when 'Image' was focused,
the backend validation would fail because the
form was submitting '' instead of null.

Fixes #159
Fixes #148
Fixes #130
Closes #160
Fixes #161
  • Loading branch information
billdybas authored and vuzDylan committed Jan 15, 2018
1 parent 74ba5d2 commit 6cb66ea
Showing 1 changed file with 212 additions and 142 deletions.
354 changes: 212 additions & 142 deletions app/js/events/components/EventModal.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Field, Form, reduxForm } from 'redux-form';
import { Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
import Button from 'common/components/Button';
import { Modal, ModalHeader, ModalBody, ModalFooter, FormFeedback } from 'reactstrap';
import moment from 'moment';
import { Formik } from 'formik';

import Button from 'common/components/Button';
import FormikDatetime from 'common/components/FormikDatetime';
import yup from 'utils/yup';

class EventModal extends Component {
static propTypes = {
Expand All @@ -17,8 +20,6 @@ class EventModal extends Component {
location: PropTypes.string,
image: PropTypes.string,
}),
handleSubmit: PropTypes.func.isRequired,
initialize: PropTypes.func.isRequired,
isOpen: PropTypes.bool.isRequired,
close: PropTypes.func.isRequired,
create: PropTypes.func.isRequired,
Expand All @@ -27,160 +28,229 @@ class EventModal extends Component {
}

static defaultProps = {
event: null,
}

componentDidUpdate(prevProps) {
const {
isOpen,
initialize,
event,
} = this.props;
if (isOpen && !prevProps.isOpen) {
initialize(event);
}
}

submit = (values) => {
const {
event,
create,
update,
close,
} = this.props;

const newEvent = {
...values,
startDate: moment(values.startDate).toISOString(),
endDate: moment(values.endDate).toISOString(),
};

close();

if (event) update(event.id, newEvent);
else create(newEvent);
event: {},
}

render() {
const {
isOpen,
handleSubmit,
close,
event,
committees,
create,
update,
} = this.props;

const options = committees.map(committee => <option key={committee.name} value={committee.name}>{committee.name}</option>);
const updateOrCreate = event ? 'Update' : 'Create';

const committeeNames = committees.map(committee => committee.name);
const updateOrCreate = event.id ? 'Update' : 'Create';

return (
<Modal isOpen={isOpen} toggle={close}>
<ModalHeader toggle={close}>{updateOrCreate}</ModalHeader>
<Form onSubmit={handleSubmit(this.submit)}>
<ModalBody>
<div className="form-group row">
<label htmlFor="name" className="col-3 col-form-label">Event Name</label>
<div className="col-9">
<Field
id="name"
name="name"
className="form-control"
component="input"
maxLength="25"
required
/>
</div>
</div>
<div className="form-group row">
<label htmlFor="startDate" className="col-3 col-form-label">Start Date</label>
<div className="col-9">
<Field
id="startDate"
name="startDate"
type="datetime-local"
className="form-control"
component="input"
required
/>
</div>
</div>
<div className="form-group row">
<label htmlFor="endDate" className="col-3 col-form-label">End Date</label>
<div className="col-9">
<Field
id="endDate"
name="endDate"
type="datetime-local"
className="form-control"
component="input"
required
/>
</div>
</div>
<div className="form-group row">
<label htmlFor="description" className="col-3 col-form-label">Description</label>
<div className="col-9">
<Field
rows="5"
id="description"
name="description"
className="form-control"
component="textarea"
/>
</div>
</div>
<div className="form-group row">
<label htmlFor="location" className="col-3 col-form-label">Location</label>
<div className="col-9">
<Field
id="location"
name="location"
className="form-control"
placeholder="location"
component="input"
required
/>
</div>
</div>
<div className="form-group row">
<label htmlFor="image" className="col-3 col-form-label">Image URL</label>
<div className="col-9">
<Field
id="image"
name="image"
className="form-control"
placeholder="Image URL"
component="input"
type="url"
/>
</div>
</div>
<div className="form-group row">
<label htmlFor="committeeName" className="col-3 col-form-label">Comittee</label>
<div className="col-9">
<Field
id="committeeName"
name="committeeName"
className="form-control"
component="select"
required
<Formik
initialValues={{
name: event.name || '',
startDate: moment(event.startDate) || moment(),
endDate: moment(event.endDate) || moment(),
description: event.description || '',
location: event.location || '',
image: event.image || '',
committeeName: event.committeeName || '',
}}
validationSchema={() => yup.object()
.shape({
name: yup.string()
.required('Required')
.max(25, 'Event names can have a maximum of 25 characters'),
startDate: yup.date()
.required('Required')
.isFuture('Start Date must be in the future'),
endDate: yup.date()
.required('Required')
.isFuture('End Date must be in the future')
.isAfter(yup.ref('startDate'), 'End Date must come after Start Date'),
description: yup.string(),
location: yup.string().required('Required'),
image: yup.string().url('Must be a valid URL'),
committeeName: yup.string().required('Required').oneOf(committeeNames),
})
}
onSubmit={(
values
) => {
const newEvent = {
...values,
startDate: moment(values.startDate).toISOString(),
endDate: moment(values.endDate).toISOString(),
image: values.image || null, // If an empty string (''), the API expects 'null'
};

close();

if (event.id) {
update(event.id, newEvent);
} else {
create(newEvent);
}
}}
render={({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
setFieldValue,
setFieldTouched,
isSubmitting,
}) => (
<form onSubmit={handleSubmit}>
<ModalBody>
<div className="form-group row">
<label htmlFor="name" className="col-3 col-form-label">Event Name</label>
<div className="col-9">
<input
type="text"
name="name"
id="name"
className="form-control"
onChange={handleChange}
onBlur={handleBlur}
value={values.name}
required
maxLength={25}
/>
{touched.name
&& errors.name
&& <FormFeedback style={{ display: 'block' }}>{errors.name}</FormFeedback>}
</div>
</div>
<div className="form-group row">
<label htmlFor="startDate" className="col-3 col-form-label">Start Date</label>
<div className="col-9">
<FormikDatetime
inputProps={{
name: 'startDate',
id: 'startDate',
required: true,
}}
field="startDate"
onChange={setFieldValue}
onBlur={setFieldTouched}
value={values.startDate}
/>
{touched.startDate
&& errors.startDate
&& <FormFeedback style={{ display: 'block' }}>{errors.startDate}</FormFeedback>}
</div>
</div>
<div className="form-group row">
<label htmlFor="endDate" className="col-3 col-form-label">End Date</label>
<div className="col-9">
<FormikDatetime
inputProps={{
name: 'endDate',
id: 'endDate',
required: true,
}}
field="endDate"
onChange={setFieldValue}
onBlur={setFieldTouched}
value={values.endDate}
/>
{touched.endDate
&& errors.endDate
&& <FormFeedback style={{ display: 'block' }}>{errors.endDate}</FormFeedback>}
</div>
</div>
<div className="form-group row">
<label htmlFor="description" className="col-3 col-form-label">Description</label>
<div className="col-9">
<textarea
name="description"
id="description"
className="form-control"
rows="5"
/>
{touched.description
&& errors.description
&& <FormFeedback style={{ display: 'block' }}>{errors.description}</FormFeedback>}
</div>
</div>
<div className="form-group row">
<label htmlFor="location" className="col-3 col-form-label">Location</label>
<div className="col-9">
<input
type="text"
name="location"
id="location"
className="form-control"
onChange={handleChange}
onBlur={handleBlur}
value={values.location}
placeholder="GOL-1670"
required
/>
{touched.location
&& errors.location
&& <FormFeedback style={{ display: 'block' }}>{errors.location}</FormFeedback>}
</div>
</div>
<div className="form-group row">
<label htmlFor="image" className="col-3 col-form-label">Image</label>
<div className="col-9">
<input
type="url"
name="image"
id="image"
className="form-control"
onChange={handleChange}
onBlur={handleBlur}
value={values.image}
placeholder="https://example.com/image.png"
/>
{touched.image
&& errors.image
&& <FormFeedback style={{ display: 'block' }}>{errors.image}</FormFeedback>}
</div>
</div>
<div className="form-group row">
<label htmlFor="committeeName" className="col-3 col-form-label">Committee</label>
<div className="col-9">
<select
type="text"
name="committeeName"
id="committeeName"
className="form-control"
onChange={handleChange}
onBlur={handleBlur}
value={values.committeeName}
required
>
{committeeNames.map(name => <option key={name} value={name}>{name}</option>)}
</select>
{touched.committeeName
&& errors.committeeName
&& <FormFeedback style={{ display: 'block' }}>{errors.committeeName}</FormFeedback>}
</div>
</div>
</ModalBody>
<ModalFooter>
<Button
type="submit"
className="btn btn-sse"
disabled={isSubmitting}
>
{options}
</Field>
</div>
</div>
</ModalBody>
<ModalFooter>
<Button className="btn btn-sse" type="submit">{updateOrCreate}</Button>
<Button className="btn btn-secondary" type="button" onClick={close}>Cancel</Button>
</ModalFooter>
</Form>
{updateOrCreate}
</Button>
<Button className="btn btn-secondary" type="button" onClick={close}>Cancel</Button>
</ModalFooter>
</form>
)}
/>
</Modal>
);
}
}

export default reduxForm({
form: 'event',
})(EventModal);
export default EventModal;

0 comments on commit 6cb66ea

Please sign in to comment.