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 Documents from '../Documents';
|
||||
|
||||
Meteor.publish('documents', function documentsView() {
|
||||
Meteor.publish('documents', function documents() {
|
||||
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/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 RecoverPassword from '../../pages/RecoverPassword/RecoverPassword';
|
||||
import ResetPassword from '../../pages/ResetPassword/ResetPassword';
|
||||
import Profile from '../../pages/Profile/Profile';
|
||||
import NotFound from '../../pages/NotFound/NotFound';
|
||||
|
||||
const App = props => (
|
||||
|
|
@ -30,6 +31,7 @@ const App = props => (
|
|||
<Authenticated exact path="/documents/new" component={NewDocument} {...props} />
|
||||
<Authenticated exact path="/documents/:_id" component={ViewDocument} {...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="/login" component={Login} {...props} />
|
||||
<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