Initial commit
This commit is contained in:
177
website/src/Profile/Profile.js
Normal file
177
website/src/Profile/Profile.js
Normal file
@@ -0,0 +1,177 @@
|
||||
import React from 'react'
|
||||
import { Container } from 'semantic-ui-react'
|
||||
import { ProfileForm } from './ProfileForm'
|
||||
import { Constants, api } from '../helpers'
|
||||
import { WaitDialog, MessageDialog, ChangePasswordDialog, ProgressDialog, ChangeEmailDialog } from '../Dialog'
|
||||
import { autoBind } from 'auto-bind2'
|
||||
|
||||
export class Profile extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
autoBind(this, (name) => (name.startsWith('handle')))
|
||||
|
||||
const user = api.loggedInUser
|
||||
this.state = {
|
||||
messageDialog: null,
|
||||
waitDialog: null,
|
||||
changePasswordDialog: null,
|
||||
changeEmailDialog: null,
|
||||
progressDialog: null,
|
||||
uploadPercent: 0,
|
||||
user,
|
||||
userImageUrl: api.makeImageUrl(user.imageId, Constants.bigUserImageSize)
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
api.addListener('newProfileImage', this.handleNewProfileImage)
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
api.removeListener('newProfileImage', this.handleNewProfileImage)
|
||||
}
|
||||
|
||||
handleNewProfileImage(data) {
|
||||
this.setState({ userImageUrl: api.makeImageUrl(data.imageId, Constants.bigUserImageSize) })
|
||||
}
|
||||
|
||||
handleSaved(user) {
|
||||
this.setState({ waitDialog: { message: 'Updating Profile' } })
|
||||
api.updateUser(user).then((updatedUser) => {
|
||||
this.setState({
|
||||
waitDialog: null,
|
||||
user: updatedUser
|
||||
})
|
||||
}).catch((error) => {
|
||||
this.setState({
|
||||
waitDialog: null,
|
||||
messageDialog: { title: 'Update Error...', message: `Unable to save the profile changes. ${error.message}` }
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
handleMessageDialogDismiss() {
|
||||
this.setState({ messageDialog: null })
|
||||
}
|
||||
|
||||
handleChangePassword() {
|
||||
this.setState({ changePasswordDialog: true })
|
||||
}
|
||||
|
||||
handleSelectImage(file) {
|
||||
this.setState({ progressDialog: { message: `Uploading image '${file.name}'...`, file }, uploadPercent: 0 })
|
||||
api.upload(file, this.handleProgress).then((uploadData) => {
|
||||
this.setState({ progressDialog: null })
|
||||
return api.setUserImage({
|
||||
_id: api.loggedInUser._id,
|
||||
imageId: uploadData.assetId,
|
||||
bigSize: Profile.bigUserImageSize,
|
||||
smallSize: Constants.smallUserImageSize
|
||||
})
|
||||
}).catch((error) => {
|
||||
// TODO: if the upload succeeds but the setUserImage fails, delete the uploaded image
|
||||
this.setState({
|
||||
progressDialog: null,
|
||||
messageDialog: { title: 'Upload Error...', message: `Unable to upload the file. ${error.message}` }
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
handleProgress(uploadData) {
|
||||
if (this.state.progressDialog) {
|
||||
this.setState({ uploadPercent: Math.round(uploadData.uploadedChunks / uploadData.numberOfChunks * 100) })
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
handleUploadCancel(result) {
|
||||
this.setState({ progressDialog: null })
|
||||
}
|
||||
|
||||
handleChangePasswordDismiss(passwords) {
|
||||
this.setState({ changePasswordDialog: false })
|
||||
|
||||
if (passwords) {
|
||||
this.setState({
|
||||
waitDialog: { message: 'Changing Password' }
|
||||
})
|
||||
api.changePassword(passwords).then(() => {
|
||||
this.setState({ waitDialog: false })
|
||||
}).catch((error) => {
|
||||
this.setState({
|
||||
waitDialog: false,
|
||||
messageDialog: {
|
||||
title: 'Changing Password Error',
|
||||
message: `Unable to change password. ${error.message}.`
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handleChangeEmail() {
|
||||
this.setState({ changeEmailDialog: {} })
|
||||
}
|
||||
|
||||
handleChangeEmailDismiss(newEmail) {
|
||||
this.setState({ changeEmailDialog: null })
|
||||
if (!newEmail) {
|
||||
return
|
||||
}
|
||||
this.setState({
|
||||
waitDialog: { message: 'Requesting Email Change...' }
|
||||
})
|
||||
api.sendConfirmEmail({ newEmail }).then(() => {
|
||||
this.setState({
|
||||
waitDialog: null,
|
||||
messageDialog: {
|
||||
error: false,
|
||||
title: 'Email Change Requested...',
|
||||
message: `An email has been sent to '${newEmail}' with a link that you need to click on to finish changing your email.`
|
||||
}
|
||||
})
|
||||
}).catch((error) => {
|
||||
this.setState({
|
||||
error: true,
|
||||
waitDialog: null,
|
||||
messageDialog: {
|
||||
error: true,
|
||||
title: 'Email Change Error...',
|
||||
message: `Unable to request email change. ${error ? error.message : ''}`
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Container>
|
||||
<ProfileForm
|
||||
user={this.state.user}
|
||||
onSaved={this.handleSaved}
|
||||
onSelectImage={this.handleSelectImage}
|
||||
onChangePassword={this.handleChangePassword}
|
||||
onChangeEmail={this.handleChangeEmail}
|
||||
userImageUrl={this.state.userImageUrl} />
|
||||
|
||||
<MessageDialog error open={!!this.state.messageDialog}
|
||||
title={this.state.messageDialog ? this.state.messageDialog.title : ''}
|
||||
message={this.state.messageDialog ? this.state.messageDialog.message : ''}
|
||||
onDismiss={this.handleMessageDialogDismiss} />
|
||||
|
||||
<ChangeEmailDialog open={!!this.state.changeEmailDialog} onDismiss={this.handleChangeEmailDismiss} />
|
||||
|
||||
<WaitDialog active={!!this.state.waitDialog} message={this.state.waitDialog ? this.state.waitDialog.message : ''} />
|
||||
|
||||
<ChangePasswordDialog open={!!this.state.changePasswordDialog} onDismiss={this.handleChangePasswordDismiss} />
|
||||
|
||||
<ProgressDialog open={!!this.state.progressDialog}
|
||||
message={this.state.progressDialog ? this.state.progressDialog.message : ''}
|
||||
percent={this.state.uploadPercent}
|
||||
onCancel={this.handleUploadCancel} />
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
}
|
||||
170
website/src/Profile/ProfileForm.js
Normal file
170
website/src/Profile/ProfileForm.js
Normal file
@@ -0,0 +1,170 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Grid, Form, Image } from 'semantic-ui-react'
|
||||
import { regExpPattern } from 'regexp-pattern'
|
||||
import './ProfileForm.scss'
|
||||
import { Constants } from '../helpers'
|
||||
import { FilePicker } from '../FilePicker'
|
||||
import { Validator, ValidatedInput, ValidatedDropdown, ValidatedButton, ValidatedDatePicker } from '../Validated'
|
||||
|
||||
export class ProfileForm extends React.Component {
|
||||
static propTypes = {
|
||||
user: PropTypes.object.isRequired,
|
||||
onSaved: PropTypes.func.isRequired,
|
||||
onModifiedChanged: PropTypes.func,
|
||||
onChangePassword: PropTypes.func,
|
||||
onChangeEmail: PropTypes.func,
|
||||
onSelectImage: PropTypes.func,
|
||||
userImageUrl: PropTypes.string
|
||||
}
|
||||
|
||||
static validations = {
|
||||
email: {
|
||||
isValid: (r, v) => (v !== ''),
|
||||
isDisabled: (r) => (!!r._id)
|
||||
},
|
||||
firstName: {
|
||||
isValid: (r, v) => (v !== '')
|
||||
},
|
||||
lastName: {
|
||||
isValid: (r, v) => (v !== '')
|
||||
},
|
||||
zip: {
|
||||
isValid: (r, v) => (v === '' || regExpPattern.zip.test(v))
|
||||
},
|
||||
state: {
|
||||
isValid: (r, v) => (v === '' || regExpPattern.state.test(v))
|
||||
},
|
||||
city: {
|
||||
isValid: true
|
||||
},
|
||||
address1: {
|
||||
isValid: true
|
||||
},
|
||||
address2: {
|
||||
isValid: true
|
||||
},
|
||||
homePhone: {
|
||||
isValid: (r, v) => (v === '' || regExpPattern.phone.test(v))
|
||||
},
|
||||
cellPhone: {
|
||||
isValid: (r, v) => (v === '' || regExpPattern.phone.test(v))
|
||||
},
|
||||
dateOfBirth: {
|
||||
isValid: true
|
||||
},
|
||||
dateOfHire: {
|
||||
isValid: true
|
||||
},
|
||||
ssn: {
|
||||
isValid: (r, v) => (v === '' || regExpPattern.ssn.test(v))
|
||||
},
|
||||
role: {
|
||||
isDisabled: true
|
||||
},
|
||||
save: {
|
||||
nonValue: true,
|
||||
isDisabled: (r) => (!r.anyModified || !r.allValid)
|
||||
}
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.handleSubmit = this.handleSubmit.bind(this)
|
||||
this.state = {
|
||||
validator: new Validator(
|
||||
this.props.user, ProfileForm.validations, this.props.onModifiedChanged)
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.user !== this.props.user) {
|
||||
this.setState({
|
||||
validator: new Validator(
|
||||
nextProps.user, ProfileForm.validations, nextProps.onModifiedChanged)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
|
||||
let obj = this.state.validator.getValues()
|
||||
|
||||
if (obj && this.props.onSaved) {
|
||||
this.props.onSaved(obj)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Form className='profile-form' onSubmit={this.handleSubmit}>
|
||||
<Grid stackable>
|
||||
<Grid.Column width={3}>
|
||||
<Image id='userImage' shape='circular' size='medium' src={this.props.userImageUrl} centered />
|
||||
<FilePicker validExtensions={['.jpg', '.jpeg', '.png']} content='Select Image' onFileSelect={this.props.onSelectImage} />
|
||||
</Grid.Column>
|
||||
|
||||
<Grid.Column width={13}>
|
||||
<Form.Group>
|
||||
<ValidatedInput label='First Name' name='firstName' width={8}
|
||||
validator={this.state.validator} />
|
||||
<ValidatedInput label='Last Name' name='lastName' width={8}
|
||||
validator={this.state.validator} />
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group>
|
||||
<ValidatedInput label='Email' name='email' width={8} message='Required. Must be a valid email address.'
|
||||
validator={this.state.validator} />
|
||||
<Form.Button fluid content={'Change Email'} label=' '
|
||||
width={4} onClick={this.props.onChangeEmail} />
|
||||
<Form.Button fluid content={'Change Password'} label=' '
|
||||
width={4} onClick={this.props.onChangePassword} />
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group>
|
||||
<ValidatedInput label='Zip' name='zip' width={4}
|
||||
validator={this.state.validator} message='5 Character U.S. Zip Code. Optional.' />
|
||||
<ValidatedDropdown label='State' name='state' width={6} message='Type or select a U.S. State or Province.'
|
||||
placeholder='Select State' options={Constants.stateOptions}
|
||||
validator={this.state.validator} searchable />
|
||||
<ValidatedInput label='City' name='city' width={6}
|
||||
validator={this.state.validator} message='U.S. City. Optional.' />
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group>
|
||||
<ValidatedInput label='Address' name='address1' width={12}
|
||||
validator={this.state.validator} message='Primary Street Address. Optional.' />
|
||||
<ValidatedInput label='Apt. #' name='address2' width={4}
|
||||
validator={this.state.validator} message='Apartment/Unit number. Optional.' />
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group>
|
||||
<ValidatedInput label='Home Phone' name='homePhone' width={8}
|
||||
validator={this.state.validator} message='A valid U.S. phone number. IE: (555)123-4567. Optional.' />
|
||||
<ValidatedInput label='Cell Phone' name='cellPhone' width={8}
|
||||
validator={this.state.validator} message='A valid U.S. phone number. IE: (555)123-4567. Optional.' />
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group>
|
||||
<ValidatedDatePicker label='Date of Birth' name='dateOfBirth' width={5}
|
||||
validator={this.state.validator} message='Select a date.' />
|
||||
<ValidatedInput label='SSN' name='ssn' width={6}
|
||||
validator={this.state.validator} message='U.S. Social Security Number. IE: 123-45-6789' />
|
||||
<ValidatedDatePicker label='Hire Date' name='dateOfHire' width={5}
|
||||
validator={this.state.validator} message='Select a date.' />
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group>
|
||||
<Form.Field width={12} />
|
||||
<ValidatedButton submit primary width={4} size='medium' content='Save' label=' ' name='save'
|
||||
validator={this.state.validator} />
|
||||
</Form.Group>
|
||||
|
||||
</Grid.Column>
|
||||
</Grid>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
}
|
||||
12
website/src/Profile/ProfileForm.scss
Normal file
12
website/src/Profile/ProfileForm.scss
Normal file
@@ -0,0 +1,12 @@
|
||||
.profile-form {
|
||||
text-align: left;
|
||||
margin: 3em auto 4em auto;
|
||||
}
|
||||
|
||||
.profile-form > .fields {
|
||||
margin-bottom: 1.5em !important;
|
||||
}
|
||||
|
||||
#userImage {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
1
website/src/Profile/index.js
Normal file
1
website/src/Profile/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { Profile } from './Profile'
|
||||
Reference in New Issue
Block a user