Added a setting to update user’s profile
This commit is contained in:
@ -1,17 +1,65 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { observable, runInAction } from 'mobx';
|
||||||
import { observer, inject } from 'mobx-react';
|
import { observer, inject } from 'mobx-react';
|
||||||
|
import invariant from 'invariant';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { color, size } from 'shared/styles/constants';
|
||||||
|
|
||||||
|
import { client } from 'utils/ApiClient';
|
||||||
import AuthStore from 'stores/AuthStore';
|
import AuthStore from 'stores/AuthStore';
|
||||||
|
import ErrorsStore from 'stores/ErrorsStore';
|
||||||
import Input from 'components/Input';
|
import Input from 'components/Input';
|
||||||
|
import Button from 'components/Button';
|
||||||
import CenteredContent from 'components/CenteredContent';
|
import CenteredContent from 'components/CenteredContent';
|
||||||
import PageTitle from 'components/PageTitle';
|
import PageTitle from 'components/PageTitle';
|
||||||
import HelpText from 'components/HelpText';
|
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class Settings extends Component {
|
class Settings extends Component {
|
||||||
|
timeout: number;
|
||||||
props: {
|
props: {
|
||||||
auth: AuthStore,
|
auth: AuthStore,
|
||||||
|
errors: ErrorsStore,
|
||||||
|
};
|
||||||
|
|
||||||
|
@observable name: string;
|
||||||
|
@observable updated: boolean;
|
||||||
|
@observable isSaving: boolean;
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
if (this.props.auth.user) {
|
||||||
|
this.name = this.props.auth.user.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
clearTimeout(this.timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit = async (ev: SyntheticEvent) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
this.isSaving = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await client.post(`/user.update`, {
|
||||||
|
name: this.name,
|
||||||
|
});
|
||||||
|
invariant(res && res.data, 'Document list not available');
|
||||||
|
const { data } = res;
|
||||||
|
runInAction('Settings#handleSubmit', () => {
|
||||||
|
this.props.auth.user = data;
|
||||||
|
this.updated = true;
|
||||||
|
this.timeout = setTimeout(() => (this.updated = false), 2500);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
this.props.errors.add('Failed to load documents');
|
||||||
|
} finally {
|
||||||
|
this.isSaving = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleNameChange = (ev: SyntheticInputEvent) => {
|
||||||
|
this.name = ev.target.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -22,22 +70,32 @@ class Settings extends Component {
|
|||||||
<CenteredContent>
|
<CenteredContent>
|
||||||
<PageTitle title="Profile" />
|
<PageTitle title="Profile" />
|
||||||
<h1>Profile</h1>
|
<h1>Profile</h1>
|
||||||
<HelpText>
|
|
||||||
You’re signed in to Outline with Slack. To update your profile
|
|
||||||
information here please{' '}
|
|
||||||
<a href="https://slack.com/account/profile" target="_blank">
|
|
||||||
update your profile on Slack
|
|
||||||
</a>{' '}
|
|
||||||
and re-login to refresh.
|
|
||||||
</HelpText>
|
|
||||||
|
|
||||||
<form>
|
<form onSubmit={this.handleSubmit}>
|
||||||
<Input label="Name" value={user.name} disabled />
|
<Input
|
||||||
<Input label="Email" value={user.email} disabled />
|
label="Name"
|
||||||
|
value={this.name}
|
||||||
|
onChange={this.handleNameChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Button type="submit" disabled={this.isSaving || !this.name}>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
<SuccessMessage visible={this.updated}>
|
||||||
|
Profile updated!
|
||||||
|
</SuccessMessage>
|
||||||
</form>
|
</form>
|
||||||
</CenteredContent>
|
</CenteredContent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default inject('auth')(Settings);
|
const SuccessMessage = styled.span`
|
||||||
|
margin-left: ${size.large};
|
||||||
|
color: ${color.slate};
|
||||||
|
opacity: ${props => (props.visible ? 1 : 0)};
|
||||||
|
|
||||||
|
transition: opacity 0.25s;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default inject('auth', 'errors', 'auth')(Settings);
|
||||||
|
@ -22,3 +22,26 @@ Object {
|
|||||||
"status": 200,
|
"status": 200,
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`#user.update should require authentication 1`] = `
|
||||||
|
Object {
|
||||||
|
"error": "authentication_required",
|
||||||
|
"message": "Authentication required",
|
||||||
|
"ok": false,
|
||||||
|
"status": 401,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`#user.update should update user profile information 1`] = `
|
||||||
|
Object {
|
||||||
|
"data": Object {
|
||||||
|
"avatarUrl": "http://example.com/avatar.png",
|
||||||
|
"email": "user1@example.com",
|
||||||
|
"id": "46fde1d4-0050-428f-9f0b-0bf77f4bdf61",
|
||||||
|
"name": "New name",
|
||||||
|
"username": "user1",
|
||||||
|
},
|
||||||
|
"ok": true,
|
||||||
|
"status": 200,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
@ -11,6 +11,12 @@ export default function validation() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ctx.assertNotEmpty = function assertNotEmpty(value, message) {
|
||||||
|
if (value === '') {
|
||||||
|
throw apiError(400, 'validation_error', message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
ctx.assertEmail = (value, message) => {
|
ctx.assertEmail = (value, message) => {
|
||||||
if (!validator.isEmail(value)) {
|
if (!validator.isEmail(value)) {
|
||||||
throw apiError(400, 'validation_error', message);
|
throw apiError(400, 'validation_error', message);
|
||||||
|
@ -11,6 +11,17 @@ router.post('user.info', auth(), async ctx => {
|
|||||||
ctx.body = { data: await presentUser(ctx, ctx.state.user) };
|
ctx.body = { data: await presentUser(ctx, ctx.state.user) };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.post('user.update', auth(), async ctx => {
|
||||||
|
const { user } = ctx.state;
|
||||||
|
const { name } = ctx.body;
|
||||||
|
ctx.assertNotEmpty(name, "name can't be empty");
|
||||||
|
|
||||||
|
if (name) user.name = name;
|
||||||
|
await user.save();
|
||||||
|
|
||||||
|
ctx.body = { data: await presentUser(ctx, user) };
|
||||||
|
});
|
||||||
|
|
||||||
router.post('user.s3Upload', auth(), async ctx => {
|
router.post('user.s3Upload', auth(), async ctx => {
|
||||||
const { filename, kind, size } = ctx.body;
|
const { filename, kind, size } = ctx.body;
|
||||||
ctx.assertPresent(filename, 'filename is required');
|
ctx.assertPresent(filename, 'filename is required');
|
||||||
|
@ -38,3 +38,31 @@ describe('#user.info', async () => {
|
|||||||
expect(body).toMatchSnapshot();
|
expect(body).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#user.update', async () => {
|
||||||
|
it('should update user profile information', async () => {
|
||||||
|
await seed();
|
||||||
|
const user = await User.findOne({
|
||||||
|
where: {
|
||||||
|
email: 'user1@example.com',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await server.post('/api/user.update', {
|
||||||
|
body: { token: user.getJwtToken(), name: 'New name' },
|
||||||
|
});
|
||||||
|
const body = await res.json();
|
||||||
|
|
||||||
|
expect(res.status).toEqual(200);
|
||||||
|
expect(body).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
await seed();
|
||||||
|
const res = await server.post('/api/user.update');
|
||||||
|
const body = await res.json();
|
||||||
|
|
||||||
|
expect(res.status).toEqual(401);
|
||||||
|
expect(body).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Reference in New Issue
Block a user