add support for profile
This commit is contained in:
parent
779e3ee978
commit
252ebf50cc
7 changed files with 268 additions and 1 deletions
|
|
@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor';
|
||||||
import { check } from 'meteor/check';
|
import { check } from 'meteor/check';
|
||||||
import Documents from '../Documents';
|
import Documents from '../Documents';
|
||||||
|
|
||||||
Meteor.publish('documents', function documentsView() {
|
Meteor.publish('documents', function documents() {
|
||||||
return Documents.find({ owner: this.userId });
|
return Documents.find({ owner: this.userId });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
33
imports/api/Users/server/methods.js
Normal file
33
imports/api/Users/server/methods.js
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
import { check, Match } from 'meteor/check';
|
||||||
|
import editProfile from '../../../modules/server/edit-profile';
|
||||||
|
import rateLimit from '../../../modules/rate-limit';
|
||||||
|
|
||||||
|
Meteor.methods({
|
||||||
|
'users.editProfile': function usersEditProfile(profile) {
|
||||||
|
check(profile, {
|
||||||
|
emailAddress: String,
|
||||||
|
password: Match.Optional(Object),
|
||||||
|
profile: {
|
||||||
|
name: {
|
||||||
|
first: String,
|
||||||
|
last: String,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return editProfile({ userId: this.userId, profile })
|
||||||
|
.then(response => response)
|
||||||
|
.catch((exception) => {
|
||||||
|
throw new Meteor.Error('500', exception);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
rateLimit({
|
||||||
|
methods: [
|
||||||
|
'users.editProfile',
|
||||||
|
],
|
||||||
|
limit: 5,
|
||||||
|
timeRange: 1000,
|
||||||
|
});
|
||||||
10
imports/api/Users/server/publications.js
Normal file
10
imports/api/Users/server/publications.js
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
|
||||||
|
Meteor.publish('users.editProfile', function usersProfile() {
|
||||||
|
return Meteor.users.find(this.userId, {
|
||||||
|
fields: {
|
||||||
|
emails: 1,
|
||||||
|
profile: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
43
imports/modules/server/edit-profile.js
Normal file
43
imports/modules/server/edit-profile.js
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
/* eslint-disable consistent-return */
|
||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
import { Accounts } from 'meteor/accounts-base';
|
||||||
|
|
||||||
|
let action;
|
||||||
|
|
||||||
|
const updatePassword = (userId, newPassword) => {
|
||||||
|
try {
|
||||||
|
Accounts.setPassword(userId, newPassword, { logout: false });
|
||||||
|
} catch (exception) {
|
||||||
|
action.reject(`[editProfile.updatePassword] ${exception}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateUser = (userId, { emailAddress, profile }) => {
|
||||||
|
try {
|
||||||
|
Meteor.users.update(userId, {
|
||||||
|
$set: {
|
||||||
|
'emails.0.address': emailAddress,
|
||||||
|
profile,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (exception) {
|
||||||
|
action.reject(`[editProfile.updateUser] ${exception}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const editProfile = ({ userId, profile }, promise) => {
|
||||||
|
try {
|
||||||
|
action = promise;
|
||||||
|
|
||||||
|
updateUser(userId, profile);
|
||||||
|
if (profile.password) updatePassword(userId, profile.password);
|
||||||
|
|
||||||
|
action.resolve();
|
||||||
|
} catch (exception) {
|
||||||
|
action.reject(`[editProfile.handler] ${exception}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default options =>
|
||||||
|
new Promise((resolve, reject) =>
|
||||||
|
editProfile(options, { resolve, reject }));
|
||||||
|
|
@ -1,2 +1,5 @@
|
||||||
import '../../api/Documents/methods';
|
import '../../api/Documents/methods';
|
||||||
import '../../api/Documents/server/publications';
|
import '../../api/Documents/server/publications';
|
||||||
|
|
||||||
|
import '../../api/Users/server/methods';
|
||||||
|
import '../../api/Users/server/publications';
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import Signup from '../../pages/Signup/Signup';
|
||||||
import Login from '../../pages/Login/Login';
|
import Login from '../../pages/Login/Login';
|
||||||
import RecoverPassword from '../../pages/RecoverPassword/RecoverPassword';
|
import RecoverPassword from '../../pages/RecoverPassword/RecoverPassword';
|
||||||
import ResetPassword from '../../pages/ResetPassword/ResetPassword';
|
import ResetPassword from '../../pages/ResetPassword/ResetPassword';
|
||||||
|
import Profile from '../../pages/Profile/Profile';
|
||||||
import NotFound from '../../pages/NotFound/NotFound';
|
import NotFound from '../../pages/NotFound/NotFound';
|
||||||
|
|
||||||
const App = props => (
|
const App = props => (
|
||||||
|
|
@ -30,6 +31,7 @@ const App = props => (
|
||||||
<Authenticated exact path="/documents/new" component={NewDocument} {...props} />
|
<Authenticated exact path="/documents/new" component={NewDocument} {...props} />
|
||||||
<Authenticated exact path="/documents/:_id" component={ViewDocument} {...props} />
|
<Authenticated exact path="/documents/:_id" component={ViewDocument} {...props} />
|
||||||
<Authenticated exact path="/documents/:_id/edit" component={EditDocument} {...props} />
|
<Authenticated exact path="/documents/:_id/edit" component={EditDocument} {...props} />
|
||||||
|
<Authenticated exact path="/profile" component={Profile} {...props} />
|
||||||
<Public path="/signup" component={Signup} {...props} />
|
<Public path="/signup" component={Signup} {...props} />
|
||||||
<Public path="/login" component={Login} {...props} />
|
<Public path="/login" component={Login} {...props} />
|
||||||
<Route name="recover-password" path="/recover-password" component={RecoverPassword} />
|
<Route name="recover-password" path="/recover-password" component={RecoverPassword} />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,176 @@
|
||||||
|
/* eslint-disable no-underscore-dangle */
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Row, Col, FormGroup, ControlLabel, Button } from 'react-bootstrap';
|
||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
import { Accounts } from 'meteor/accounts-base';
|
||||||
|
import { Bert } from 'meteor/themeteorchef:bert';
|
||||||
|
import { createContainer } from 'meteor/react-meteor-data';
|
||||||
|
import InputHint from '../../components/InputHint/InputHint';
|
||||||
|
import validate from '../../../modules/validate';
|
||||||
|
|
||||||
|
class Profile extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const component = this;
|
||||||
|
|
||||||
|
validate(component.form, {
|
||||||
|
rules: {
|
||||||
|
firstName: {
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
lastName: {
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
emailAddress: {
|
||||||
|
required: true,
|
||||||
|
email: true,
|
||||||
|
},
|
||||||
|
currentPassword: {
|
||||||
|
required() {
|
||||||
|
// Only required if newPassword field has a value.
|
||||||
|
return component.newPassword.value.length > 0;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
newPassword: {
|
||||||
|
required() {
|
||||||
|
// Only required if currentPassword field has a value.
|
||||||
|
return component.currentPassword.value.length > 0;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
messages: {
|
||||||
|
firstName: {
|
||||||
|
required: 'What\'s your first name?',
|
||||||
|
},
|
||||||
|
lastName: {
|
||||||
|
required: 'What\'s your last name?',
|
||||||
|
},
|
||||||
|
emailAddress: {
|
||||||
|
required: 'Need an email address here.',
|
||||||
|
email: 'Is this email address correct?',
|
||||||
|
},
|
||||||
|
currentPassword: {
|
||||||
|
required: 'Need your current password if changing.',
|
||||||
|
},
|
||||||
|
newPassword: {
|
||||||
|
required: 'Need your new password if changing.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
submitHandler() { component.handleSubmit(); },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit() {
|
||||||
|
const profile = {
|
||||||
|
emailAddress: this.emailAddress.value,
|
||||||
|
profile: {
|
||||||
|
name: {
|
||||||
|
first: this.firstName.value,
|
||||||
|
last: this.lastName.value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.newPassword.value) profile.password = Accounts._hashPassword(this.newPassword.value);
|
||||||
|
|
||||||
|
Meteor.call('users.editProfile', profile, (error) => {
|
||||||
|
if (error) {
|
||||||
|
Bert.alert(error.reason, 'danger');
|
||||||
|
} else {
|
||||||
|
Bert.alert('Profile updated!', 'success');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { loading, user } = this.props;
|
||||||
|
return (<div className="Profile">
|
||||||
|
<Row>
|
||||||
|
<Col xs={12} sm={6} md={4}>
|
||||||
|
<h4 className="page-header">Edit Profile</h4>
|
||||||
|
<form ref={form => (this.form = form)} onSubmit={event => event.preventDefault()}>
|
||||||
|
{!loading ? (<div>
|
||||||
|
<Row>
|
||||||
|
<Col xs={6}>
|
||||||
|
<FormGroup>
|
||||||
|
<ControlLabel>First Name</ControlLabel>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="firstName"
|
||||||
|
defaultValue={user.profile.name.first}
|
||||||
|
ref={firstName => (this.firstName = firstName)}
|
||||||
|
className="form-control"
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</Col>
|
||||||
|
<Col xs={6}>
|
||||||
|
<FormGroup>
|
||||||
|
<ControlLabel>Last Name</ControlLabel>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="lastName"
|
||||||
|
defaultValue={user.profile.name.last}
|
||||||
|
ref={lastName => (this.lastName = lastName)}
|
||||||
|
className="form-control"
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<FormGroup>
|
||||||
|
<ControlLabel>Email Address</ControlLabel>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
name="emailAddress"
|
||||||
|
defaultValue={user.emails[0].address}
|
||||||
|
ref={emailAddress => (this.emailAddress = emailAddress)}
|
||||||
|
className="form-control"
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<h5 className="page-header">Change Password</h5>
|
||||||
|
<FormGroup>
|
||||||
|
<ControlLabel>Current Password</ControlLabel>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="currentPassword"
|
||||||
|
ref={currentPassword => (this.currentPassword = currentPassword)}
|
||||||
|
className="form-control"
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup>
|
||||||
|
<ControlLabel>New Password</ControlLabel>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="newPassword"
|
||||||
|
ref={newPassword => (this.newPassword = newPassword)}
|
||||||
|
className="form-control"
|
||||||
|
/>
|
||||||
|
<InputHint>Use at least six characters.</InputHint>
|
||||||
|
</FormGroup>
|
||||||
|
<Button type="submit" bsStyle="success">Save Profile</Button>
|
||||||
|
</div>) : <div />}
|
||||||
|
</form>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Profile.propTypes = {
|
||||||
|
loading: PropTypes.bool.isRequired,
|
||||||
|
user: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default createContainer(() => {
|
||||||
|
const subscription = Meteor.subscribe('users.editProfile');
|
||||||
|
|
||||||
|
return {
|
||||||
|
loading: !subscription.ready(),
|
||||||
|
user: Meteor.user(),
|
||||||
|
};
|
||||||
|
}, Profile);
|
||||||
Loading…
Add table
Add a link
Reference in a new issue