src/components/admin/EditProjectForm/EditProjectForm.js
/*!
* Codefolio
* Copyright(c) 2016 MSMFSD
* MIT Licensed
*/
/* eslint no-unused-vars: "off" */
import React, { Component, PropTypes } from 'react'
import { browserHistory } from 'react-router'
import { reduxForm } from 'redux-form'
import slugger from 'slugger'
import CssModules from 'react-css-modules'
import FormLinksEditor from '../FormLinksEditor/FormLinksEditor'
import FormArrayEditor from '../FormArrayEditor/FormArrayEditor'
import FormMediaEditor from '../FormMediaEditor/FormMediaEditor'
import { createValidator, required, maxLength, noSpecialCharacters, ifDisplayYes, requiredArr } from '../../../utils/validate'
import styles from './EditProjectForm.css'
// fields & client validation
const fields = [
'name',
'role',
'description',
'client',
'viewOrder',
'sticky',
'repoDisplay',
'repoUrl',
'repoUser',
'repoName',
'codeDisplay',
'code',
'projectTech',
'linkWeb',
'media'
]
const editProjectValidation = createValidator({
name: [required, maxLength(40), noSpecialCharacters],
role: [required, maxLength(40)],
description: [required],
client: [required, maxLength(80)],
repoUrl: [ifDisplayYes('repoDisplay', 'yes')],
code: [ifDisplayYes('codeDisplay', 'yes')],
projectTech: [requiredArr]
})
/**
* @class EditProjectForm
* @extends {Component}
*/
class EditProjectForm extends Component {
componentDidMount () {
// prevent edit prior to projects state being loaded
if(!this.props.projects.hasLoaded || this.props.projects.loading) {
browserHistory.push('/admin')
} else {
// set form
let currentProject = this.props.projects.data.find((obj) => obj.slug === this.props.params.projectId)
if(currentProject) {
this.props.editProjectReset()
this.props.editProjectSet(currentProject)
} else {
browserHistory.push('/admin')
}
}
}
componentDidUpdate () {
// scroll to first validation error
if(this.props.submitFailed) {
let div = document.getElementsByClassName('input-field-message')[0].offsetParent
if(div) {
window.scrollTo(0, div.offsetTop + 60)
}
}
}
/**
* Allow live form edits to update profile state on blur
* Why? So other state updates dont reset their values pre submit
* @param {object} e - event object
*/
onBlurUpdate (e) {
this.props.editProjectUpdateField(e.currentTarget.name, e.currentTarget.value)
}
render () {
const {
auth,
projects,
editProject,
editProjectAsync,
handleSubmit,
defaultInputClasses,
editProjectOnPushFieldArray,
editProjectOnSpliceFieldArray,
editProjectAddLink,
editProjectRemoveLink,
editProjectRemoveMedia,
editProjectUploadFilesAsync,
fields: {
name,
role,
description,
client,
viewOrder,
sticky,
repoDisplay,
repoUrl,
repoUser,
repoName,
codeDisplay,
code,
projectTech,
linkWeb,
media
}
} = this.props
const currentProject = projects.data.find((obj) => obj.slug === this.props.params.projectId)
return (
<div>
<div styleName="form-container">
<div className="row">
<div styleName="card-padding" className={editProject.editProjectSuccess ? 'card-panel show' : 'card-panel hide'}>
<span>Project edited successfully: <a href={'/projects/' + slugger(name.value)} target="_blank">View project</a></span>
</div>
</div>
<div className="row">
<form className={editProject.editProjectSuccess ? 'hide' : 'show'} onSubmit={handleSubmit(data => editProjectAsync(data, auth.token, currentProject._id))}>
<div className="col s12">
<h3>Edit: <span styleName="hl">{name.value}</span></h3>
<p>Complete fields and submit to update this project.</p>
</div>
<div className={defaultInputClasses}><h5>Project details</h5></div>
<div className={defaultInputClasses}>
<h6>Name:<div className="hint">Cannot be modified</div></h6>
<input type="text" readOnly={true} {...name}/>
{name.touched && name.error && <div className="input-field-message">{name.error}</div>}
</div>
<div className={defaultInputClasses}>
<h6>Your role:<div className="hint">eg. Front-end developer & UX</div></h6>
<input type="text" placeholder="Enter your role" {...role} onBlur={(e) => this.onBlurUpdate(e)}/>
{role.touched && role.error && <div className="input-field-message">{role.error}</div>}
</div>
<div className={defaultInputClasses}>
<h6>Client:<div className="hint">eg. Startup-X & Digital agency Y</div></h6>
<input type="text" placeholder="Enter project client" {...client} onBlur={(e) => this.onBlurUpdate(e)}/>
{client.touched && client.error && <div className="input-field-message">{client.error}</div>}
</div>
<div className={defaultInputClasses}>
<h6>Project description:<div className="hint">Use <a style={{textDecoration: 'underline'}} href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet" target="_blank">Markdown</a> or plain text</div></h6>
<textarea rows="8" placeholder="Enter project description" {...description} onBlur={(e) => this.onBlurUpdate(e)}/>
{description.touched && description.error && <div className="input-field-message">{description.error}</div>}
</div>
<div className={defaultInputClasses + ' m6'}>
<h6>View Order:<div className="hint">Project order</div></h6>
<input type="number" min="0" {...viewOrder} onBlur={(e) => this.onBlurUpdate(e)}/>
{viewOrder.touched && viewOrder.error && <div className="input-field-message">{viewOrder.error}</div>}
</div>
<div className={defaultInputClasses + ' m6'}>
<h6>Featured:<div className="hint">Always appear at top?</div></h6>
<input type="number" min="0" max="1" {...sticky} onBlur={(e) => this.onBlurUpdate(e)}/>
{sticky.touched && sticky.error && <div className="input-field-message">{sticky.error}</div>}
</div>
<div className={defaultInputClasses}><h5>Project media</h5></div>
<div className={defaultInputClasses}>
<h6>Upload project screenshots:<div className="hint">Max 1MB image files allowed</div></h6>
<FormMediaEditor auth={auth} editProject={editProject} uploadAsyncFunc={editProjectUploadFilesAsync} removeMediaItemFunc={editProjectRemoveMedia} />
</div>
<div className={defaultInputClasses}><h5>Project repo</h5></div>
<div className={defaultInputClasses + ' m6'}>
<h6>Display Github repo?:<div className="hint">Repo display is optional</div></h6>
<input type="radio" {...repoDisplay} value="yes" checked={repoDisplay.value === 'yes'} onBlur={(e) => this.onBlurUpdate(e)}/> Yes
<input type="radio" {...repoDisplay} value="no" checked={repoDisplay.value === 'no'} onBlur={(e) => this.onBlurUpdate(e)}/> No
</div>
<div className={repoDisplay.value === 'yes' ? 'show' : 'hide'}>
<div className={defaultInputClasses + ' m6'}>
<h6>Repo URL:<div className="hint">eg. https://github.com/torvalds/linux</div></h6>
<input type="text" placeholder="Enter full URL" {...repoUrl} onBlur={(e) => this.onBlurUpdate(e)}/>
{repoUrl.touched && repoUrl.error && <div className="input-field-message">{repoUrl.error}</div>}
</div>
<div className={defaultInputClasses + ' m6'}>
<h6>Repo user:<div className="hint">eg. torvalds</div></h6>
<input type="text" placeholder="Enter repo user" {...repoUser} onBlur={(e) => this.onBlurUpdate(e)}/>
{repoUser.touched && repoUser.error && <div className="input-field-message">{repoUser.error}</div>}
</div>
<div className={defaultInputClasses + ' m6'}>
<h6>Repo name:<div className="hint">eg. linux</div></h6>
<input type="text" placeholder="Enter repo name" {...repoName} onBlur={(e) => this.onBlurUpdate(e)}/>
{repoName.touched && repoName.error && <div className="input-field-message">{repoName.error}</div>}
</div>
</div>
<div className={defaultInputClasses}><h5>Project code snippet</h5></div>
<div className={defaultInputClasses}>
<h6>Display code snippet?:<div className="hint">Code snippet is optional</div></h6>
<input type="radio" {...codeDisplay} value="yes" checked={codeDisplay.value === 'yes'} onBlur={(e) => this.onBlurUpdate(e)}/> Yes
<input type="radio" {...codeDisplay} value="no" checked={codeDisplay.value === 'no'} onBlur={(e) => this.onBlurUpdate(e)}/> No
</div>
<div className={codeDisplay.value === 'yes' ? 'show' : 'hide'}>
<div className={defaultInputClasses}>
<h6>Code:<div className="hint">Paste raw code here</div></h6>
<textarea rows="8" placeholder="Enter code" {...code} onBlur={(e) => this.onBlurUpdate(e)}/>
{code.touched && code.error && <div className="input-field-message">{code.error}</div>}
</div>
</div>
<div className={defaultInputClasses}><h5>Project links</h5></div>
<div styleName="table-div" className={defaultInputClasses}>
<h6>Add up to 2 project links:<div className="hint">eg. linux.org ~ http://linux.org/v2</div></h6>
<FormLinksEditor fields={linkWeb} linkGroup={'linkWeb'} addLinkFunc={editProjectAddLink} removeLinkFunc={editProjectRemoveLink} max={2} />
</div>
<div className={defaultInputClasses}><h5>Project tech</h5></div>
<div styleName="table-div" className={defaultInputClasses}>
<h6>Add up to 10 project technologies:<div className="hint">eg. C++, NoSQL, jQuery, CSS, UX, Elm, Nginx, Docker, React</div></h6>
<FormArrayEditor fields={projectTech} fieldName={'projectTech'} addItemFunc={editProjectOnPushFieldArray} removeItemFunc={editProjectOnSpliceFieldArray} max={10} />
{projectTech.touched && projectTech.error && <div className="input-field-message">{projectTech.error}</div>}
</div>
<div className={defaultInputClasses}><h5>Save changes</h5></div>
<div styleName="form-messages" className="col s12">{editProject.editProjectError && editProject.editProjectErrMessage}</div>
<div className={defaultInputClasses}>
<button styleName="form-btn" className={editProject.editProjectLoading ? 'waves-effect btn btn-loading' : 'waves-effect btn'} type="submit" disabled={editProject.editProjectLoading || editProject.editProjectFilesLoading}><i className="material-icons">settings</i><span>Update project</span></button>
</div>
</form>
</div>
</div>
</div>
)
}
}
EditProjectForm.propTypes = {
auth: PropTypes.object.isRequired,
projects: PropTypes.object.isRequired,
editProject: PropTypes.object.isRequired,
editProjectAsync: PropTypes.func,
editProjectReset: PropTypes.func,
editProjectSet: PropTypes.func,
editProjectUploadFilesAsync: PropTypes.func,
editProjectOnPushFieldArray: PropTypes.func,
editProjectOnSpliceFieldArray: PropTypes.func,
editProjectAddLink: PropTypes.func,
editProjectRemoveLink: PropTypes.func,
editProjectUpdateField: PropTypes.func.isRequired,
editProjectRemoveMedia: PropTypes.func.isRequired,
fields: PropTypes.object.isRequired,
handleSubmit: PropTypes.func.isRequired,
submitFailed: PropTypes.bool,
defaultInputClasses: PropTypes.string,
params: PropTypes.object
}
EditProjectForm.defaultProps = {
defaultInputClasses: 'input-field col s12'
}
export default reduxForm({
form: 'editproject',
fields,
validate: editProjectValidation
}, state => ({ initialValues: state.editProject }))(CssModules(EditProjectForm, styles))