diff --git a/package.json b/package.json index fd088d7..8d90130 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,9 @@ "passport-linkedin-oauth2": "^1.4.1", "pm2": "^2.0.18", "react": "^15.3.2", + "react-addons-shallow-compare": "^15.3.2", "react-bootstrap": "^0.30.5", + "react-dates": "^4.0.0", "react-dom": "^15.0.2", "react-ga": "^2.1.2", "react-google-recaptcha": "^0.5.4", @@ -125,6 +127,7 @@ "react-router": "^2.3.0", "react-router-active-component": "^4.0.0", "react-router-redux": "^4.0.6", + "react-slider": "^0.7.0", "redux": "^3.5.2", "redux-form": "^6.1.0", "redux-thunk": "^2.1.0", diff --git a/src/common/components/forms/DemoForm.js b/src/common/components/forms/DemoForm.js new file mode 100644 index 0000000..881f49c --- /dev/null +++ b/src/common/components/forms/DemoForm.js @@ -0,0 +1,240 @@ +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { Link } from 'react-router'; +import { Field, reduxForm } from 'redux-form'; +import Alert from 'react-bootstrap/lib/Alert'; +import Button from 'react-bootstrap/lib/Button'; +import FormNames from '../../constants/FormNames'; +import { Form, FormField, FormFooter } from '../utils/BsForm'; +import Head from '../widgets/Head'; + +class DemoForm extends Component { + constructor() { + super(); + this.handleSubmit = this._handleSubmit.bind(this); + } + + _handleSubmit(formData) { + console.log('formData', formData); + // let { dispatch, apiEngine } = this.props; + // + // return someAPI(apiEngine) + // .doSomething(formData) + // .catch((err) => { + // dispatch(pushErrors(err)); + // throw err; + // }) + // .then((json) => { + // console.log('json', json); + // }); + } + + render() { + let { + handleSubmit, + submitFailed, + error, + pristine, + submitting, + invalid, + demoForm: { values }, + } = this.props; + + return ( +
+ + {submitFailed && error && ({error})} + + File object is not going to show here. + Please submit the form and check the console. + +
{JSON.stringify(values, null, 2)}
+ + + + + + + + + + + + + + + + + + + + + + + + + ); + } +}; + +export default reduxForm({ + form: FormNames.DEMO, + initialValues: { + somePassword: 'xxxxxxxxxx', + someRangeSlider: { + min: 10, + max: 30, + }, + }, +})(connect(state => ({ + apiEngine: state.apiEngine, + demoForm: state.form[FormNames.DEMO], +}))(DemoForm)); diff --git a/src/common/components/forms/user/AvatarForm.js b/src/common/components/forms/user/AvatarForm.js index 8e5f4a5..5d24bac 100644 --- a/src/common/components/forms/user/AvatarForm.js +++ b/src/common/components/forms/user/AvatarForm.js @@ -10,6 +10,7 @@ import userAPI from '../../../api/user'; import { pushErrors } from '../../../actions/errorActions'; import { setCookies } from '../../../actions/cookieActions'; import { Form, FormField, FormFooter } from '../../utils/BsForm'; +import Head from '../../widgets/Head'; import toRefreshURL from '../../../utils/toRefreshURL'; const initialValues = { @@ -196,6 +197,11 @@ class AvatarForm extends Component { return (
+ {avatarURL && } { errors.password = 'Required'; } + if (!values.isAgreeTerms) { + errors.isAgreeTerms = 'Required'; + } + if (configs.recaptcha && !values.recaptcha) { errors.recaptcha = 'Required'; } @@ -74,6 +78,7 @@ class RegisterForm extends Component { asyncValidating, submitting, invalid, + registerForm: { values }, } = this.props; return ( @@ -100,6 +105,13 @@ class RegisterForm extends Component { type="password" placeholder="Password" /> + I agree the terms} + /> ({ apiEngine: state.apiEngine, + registerForm: state.form[FormNames.USER_REGISTER], }))(RegisterForm)); diff --git a/src/common/components/pages/demo/FormElementPage.js b/src/common/components/pages/demo/FormElementPage.js new file mode 100644 index 0000000..2110f4e --- /dev/null +++ b/src/common/components/pages/demo/FormElementPage.js @@ -0,0 +1,17 @@ +import React from 'react'; +import PageHeader from 'react-bootstrap/lib/PageHeader'; +import PageLayout from '../../layouts/PageLayout'; +import DemoForm from '../../forms/DemoForm'; + +let FormElementPage = (props) => ( + + Form Elements +

+ There are a rich amount of field types built inside the boilerplate. + You can reuse them to prototype your custom form. +

+ +
+); + +export default FormElementPage; diff --git a/src/common/components/pages/user/ShowPage.js b/src/common/components/pages/user/ShowPage.js index 4e985bd..d05ecab 100644 --- a/src/common/components/pages/user/ShowPage.js +++ b/src/common/components/pages/user/ShowPage.js @@ -9,7 +9,6 @@ import Button from 'react-bootstrap/lib/Button'; import Image from 'react-bootstrap/lib/Image'; import userAPI from '../../../api/user'; import { pushErrors } from '../../../actions/errorActions'; -import Head from '../../widgets/Head'; import PageLayout from '../../layouts/PageLayout'; import Time from '../../widgets/Time'; import VerifyEmailForm from '../../forms/user/VerifyEmailForm'; @@ -76,11 +75,6 @@ class ShowPage extends Component { const { user } = this.state; return ( - {this.renderModal()} diff --git a/src/common/components/utils/AirDateRange.js b/src/common/components/utils/AirDateRange.js new file mode 100644 index 0000000..9797078 --- /dev/null +++ b/src/common/components/utils/AirDateRange.js @@ -0,0 +1,51 @@ +import React, { Component, PropTypes } from 'react'; +import { DateRangePicker } from 'react-dates'; + +let defaultValue = { + startDate: null, + endDate: null, +}; + +class AirDateRange extends Component { + constructor() { + super(); + this.state = { + focusedInput: null, + }; + this.onDatesChange = this._onDatesChange.bind(this); + this.onFocusChange = this._onFocusChange.bind(this); + } + + _onDatesChange({ startDate, endDate }) { + this.props.input.onChange({ startDate, endDate }); + } + + _onFocusChange(focusedInput) { + this.setState({ focusedInput }); + } + + render() { + let { + input, + ...rest + } = this.props; + let { focusedInput } = this.state; + + return ( + + ); + } +} + +AirDateRange.propTypes = { + input: PropTypes.object.isRequired, +}; + +export default AirDateRange; diff --git a/src/common/components/utils/AirSingleDate.js b/src/common/components/utils/AirSingleDate.js new file mode 100644 index 0000000..81494e8 --- /dev/null +++ b/src/common/components/utils/AirSingleDate.js @@ -0,0 +1,48 @@ +import React, { Component, PropTypes } from 'react'; +import { SingleDatePicker } from 'react-dates'; + +let defaultValue = null; + +class AirSingleDate extends Component { + constructor() { + super(); + this.state = { + focused: false, + }; + this.onDateChange = this._onDateChange.bind(this); + this.onFocusChange = this._onFocusChange.bind(this); + } + + _onDateChange(date) { + this.props.input.onChange(date); + } + + _onFocusChange({ focused }) { + this.setState({ focused }); + } + + render() { + let { + input, + ...rest + } = this.props; + let { focused } = this.state; + + return ( + + ); + } +} + +AirSingleDate.propTypes = { + input: PropTypes.object.isRequired, +}; + +export default AirSingleDate; diff --git a/src/common/components/utils/BsForm.js b/src/common/components/utils/BsForm.js index 7ea72a4..c380901 100644 --- a/src/common/components/utils/BsForm.js +++ b/src/common/components/utils/BsForm.js @@ -5,6 +5,9 @@ import Col from 'react-bootstrap/lib/Col'; import ControlLabel from 'react-bootstrap/lib/ControlLabel'; import HelpBlock from 'react-bootstrap/lib/HelpBlock'; import Recaptcha from 'react-google-recaptcha'; +import RangeSlider from './RangeSlider.js'; +import AirSingleDate from './AirSingleDate.js'; +import AirDateRange from './AirDateRange.js'; import configs from '../../../../configs/project/client'; class BsForm extends Component { @@ -62,7 +65,7 @@ BsForm.childContextTypes = { }; let BsFormField = ({ - label, input, type, meta, options, ...rest + label, input, type, meta, options, text, ...rest }, { labelDimensions, fieldDimensions, horizontal, }) => { @@ -82,7 +85,48 @@ let BsFormField = ({
Recaptcha is disabled
); /* eslint-enable */ - } else if (options) { + } else if (type === 'checkbox') { + formControl = ( +
+ +
+ ); + } else if (type === 'checkboxes') { + // ref: + // - + formControl = ( + options.map((option, index) => ( +
+ +
+ )) + ); + } else if (type === 'radiobutton') { // ref: formControl = ( options.map((option) => ( @@ -100,6 +144,54 @@ let BsFormField = ({ )) ); + } else if (type === 'select') { + formControl = ( + + ); + } else if (type === 'rangeSlider') { + formControl = ( + + ); + } else if (type === 'airSingleDate') { + formControl = ( + + ); + } else if (type === 'airDateRange') { + formControl = ( + + ); + } else if (type === 'plaintext') { + formControl = ( +

+ {text} +

+ ); + } else if (type === 'textarea') { + formControl = ( +