2018-10-30 07:40:21 -07:00
|
|
|
import React from 'react';
|
|
|
|
import lodash from 'lodash';
|
2019-01-02 10:23:02 -08:00
|
|
|
import qs from 'query-string';
|
|
|
|
import { withRouter, RouteComponentProps, Redirect } from 'react-router-dom';
|
|
|
|
import { connect } from 'react-redux';
|
|
|
|
import { compose } from 'recompose';
|
2018-11-16 19:33:25 -08:00
|
|
|
import axios from 'api/axios';
|
2019-01-02 10:23:02 -08:00
|
|
|
import { getSocialAuthUrl, verifySocial } from 'api/api';
|
|
|
|
import { usersActions } from 'modules/users';
|
|
|
|
import { AppState } from 'store/reducers';
|
|
|
|
import { Input, Form, Col, Row, Button, Alert, Icon } from 'antd';
|
|
|
|
import { SOCIAL_INFO } from 'utils/social';
|
2018-11-16 15:05:17 -08:00
|
|
|
import { SOCIAL_SERVICE, User } from 'types';
|
2018-10-30 07:40:21 -07:00
|
|
|
import { UserState } from 'modules/users/reducers';
|
2019-02-17 12:36:17 -08:00
|
|
|
import { validateUserProfile } from 'modules/create/utils';
|
2018-11-16 19:33:25 -08:00
|
|
|
import AvatarEdit from './AvatarEdit';
|
2018-10-30 07:40:21 -07:00
|
|
|
import './ProfileEdit.less';
|
|
|
|
|
2019-01-02 10:23:02 -08:00
|
|
|
interface OwnProps {
|
2018-10-30 07:40:21 -07:00
|
|
|
user: UserState;
|
|
|
|
}
|
|
|
|
|
2019-01-02 10:23:02 -08:00
|
|
|
interface StateProps {
|
|
|
|
authUser: AppState['auth']['user'];
|
|
|
|
hasCheckedAuthUser: AppState['auth']['hasCheckedUser'];
|
|
|
|
}
|
|
|
|
|
|
|
|
interface DispatchProps {
|
|
|
|
fetchUser: typeof usersActions['fetchUser'];
|
|
|
|
updateUser: typeof usersActions['updateUser'];
|
|
|
|
}
|
|
|
|
|
|
|
|
type Props = OwnProps & StateProps & DispatchProps & RouteComponentProps;
|
|
|
|
|
2018-10-30 07:40:21 -07:00
|
|
|
interface State {
|
2018-11-16 15:05:17 -08:00
|
|
|
fields: User;
|
2018-10-30 07:40:21 -07:00
|
|
|
isChanged: boolean;
|
|
|
|
showError: boolean;
|
2019-01-02 10:23:02 -08:00
|
|
|
isDone: boolean;
|
|
|
|
socialVerificationMessage: string;
|
|
|
|
socialVerificationError: string;
|
|
|
|
activeSocialService: SOCIAL_SERVICE | null;
|
2018-10-30 07:40:21 -07:00
|
|
|
}
|
|
|
|
|
2019-01-02 10:23:02 -08:00
|
|
|
class ProfileEdit extends React.PureComponent<Props, State> {
|
2018-10-30 07:40:21 -07:00
|
|
|
state: State = {
|
2018-11-16 15:05:17 -08:00
|
|
|
fields: { ...this.props.user } as User,
|
2018-10-30 07:40:21 -07:00
|
|
|
isChanged: false,
|
|
|
|
showError: false,
|
2019-01-02 10:23:02 -08:00
|
|
|
isDone: false,
|
|
|
|
socialVerificationError: '',
|
|
|
|
socialVerificationMessage: '',
|
|
|
|
activeSocialService: null,
|
2018-10-30 07:40:21 -07:00
|
|
|
};
|
|
|
|
|
2019-01-02 10:23:02 -08:00
|
|
|
componentDidMount() {
|
|
|
|
this.verifySocial();
|
|
|
|
}
|
|
|
|
|
2018-10-30 07:40:21 -07:00
|
|
|
componentDidUpdate(prevProps: Props, _: State) {
|
|
|
|
if (
|
|
|
|
prevProps.user.isUpdating &&
|
|
|
|
!this.props.user.isUpdating &&
|
|
|
|
!this.state.showError
|
|
|
|
) {
|
|
|
|
this.setState({ showError: true });
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
prevProps.user.isUpdating &&
|
|
|
|
!this.props.user.isUpdating &&
|
|
|
|
!this.props.user.updateError
|
|
|
|
) {
|
2019-01-02 10:23:02 -08:00
|
|
|
this.handleDone();
|
2018-10-30 07:40:21 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
const { fields } = this.state;
|
2019-01-02 10:23:02 -08:00
|
|
|
const {
|
|
|
|
user,
|
|
|
|
user: { userid },
|
|
|
|
} = this.props;
|
|
|
|
const {
|
|
|
|
socialVerificationMessage,
|
|
|
|
socialVerificationError,
|
|
|
|
activeSocialService,
|
|
|
|
} = this.state;
|
2019-02-17 12:36:17 -08:00
|
|
|
const error = validateUserProfile(fields);
|
2019-02-15 11:31:08 -08:00
|
|
|
const isMissingField = !fields.displayName || !fields.title;
|
2019-01-02 10:23:02 -08:00
|
|
|
const isDisabled =
|
|
|
|
!!error ||
|
|
|
|
isMissingField ||
|
|
|
|
!this.state.isChanged ||
|
|
|
|
!!this.state.activeSocialService;
|
|
|
|
|
|
|
|
if (this.state.isDone) {
|
|
|
|
return <Redirect to={`/profile/${userid}`} />;
|
|
|
|
}
|
2018-10-30 07:40:21 -07:00
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<div className="ProfileEdit">
|
2018-11-16 19:33:25 -08:00
|
|
|
<AvatarEdit
|
|
|
|
user={fields}
|
|
|
|
onDone={this.handleChangePhoto}
|
|
|
|
onDelete={this.handleDeletePhoto}
|
|
|
|
/>
|
|
|
|
|
2018-10-30 07:40:21 -07:00
|
|
|
<div className="ProfileEdit-info">
|
|
|
|
<Form
|
|
|
|
className="ProfileEdit-info-form"
|
|
|
|
layout="vertical"
|
|
|
|
onSubmit={this.handleSave}
|
|
|
|
>
|
|
|
|
<Form.Item>
|
|
|
|
<Input
|
2018-12-14 11:36:22 -08:00
|
|
|
name="displayName"
|
2018-10-30 07:40:21 -07:00
|
|
|
autoComplete="off"
|
|
|
|
placeholder="Display name (Required)"
|
2018-11-16 15:05:17 -08:00
|
|
|
value={fields.displayName}
|
2018-10-30 07:40:21 -07:00
|
|
|
onChange={this.handleChangeField}
|
|
|
|
/>
|
|
|
|
</Form.Item>
|
|
|
|
|
|
|
|
<Form.Item>
|
|
|
|
<Input
|
|
|
|
name="title"
|
|
|
|
autoComplete="off"
|
|
|
|
placeholder="Title (Required)"
|
|
|
|
value={fields.title}
|
|
|
|
onChange={this.handleChangeField}
|
|
|
|
/>
|
|
|
|
</Form.Item>
|
|
|
|
|
|
|
|
<Row gutter={12}>
|
2018-11-16 15:05:17 -08:00
|
|
|
{Object.values(SOCIAL_INFO).map(s => {
|
|
|
|
const field = fields.socialMedias.find(sm => sm.service === s.service);
|
2019-01-02 10:23:02 -08:00
|
|
|
const loading = s.service === activeSocialService;
|
2018-11-16 15:05:17 -08:00
|
|
|
return (
|
2019-01-02 10:23:02 -08:00
|
|
|
<Col xs={24} md={8} key={s.service}>
|
2018-11-16 15:05:17 -08:00
|
|
|
<Form.Item>
|
2019-01-02 10:23:02 -08:00
|
|
|
{field &&
|
|
|
|
field.username && (
|
|
|
|
<Button
|
|
|
|
className="ProfileEdit-socialButton is-delete"
|
|
|
|
type="primary"
|
|
|
|
ghost
|
|
|
|
onClick={() => this.handleSocialDelete(s.service)}
|
|
|
|
loading={loading}
|
|
|
|
block
|
|
|
|
>
|
|
|
|
<div className="ProfileEdit-socialButton-text">
|
|
|
|
{!loading && s.icon} <strong>{field.username}</strong>
|
|
|
|
</div>
|
|
|
|
<div className="ProfileEdit-socialButton-delete">
|
|
|
|
<Icon type="delete" /> Unlink account
|
|
|
|
</div>
|
|
|
|
</Button>
|
|
|
|
)}
|
|
|
|
{!field && (
|
|
|
|
<Button
|
|
|
|
className="ProfileEdit-socialButton is-add"
|
|
|
|
onClick={() => this.handleSocialAdd(s.service)}
|
|
|
|
loading={loading}
|
|
|
|
block
|
|
|
|
>
|
2019-02-25 08:41:00 -08:00
|
|
|
{!loading && s.icon} <>Connect to {s.name}</>
|
2019-01-02 10:23:02 -08:00
|
|
|
</Button>
|
|
|
|
)}
|
2018-11-16 15:05:17 -08:00
|
|
|
</Form.Item>
|
|
|
|
</Col>
|
|
|
|
);
|
|
|
|
})}
|
2018-10-30 07:40:21 -07:00
|
|
|
</Row>
|
2019-01-02 10:23:02 -08:00
|
|
|
{socialVerificationError && (
|
|
|
|
<Alert type="error" message={socialVerificationError} closable />
|
|
|
|
)}
|
|
|
|
{socialVerificationMessage && (
|
|
|
|
<Alert type="success" message={socialVerificationMessage} closable />
|
|
|
|
)}
|
2018-10-30 07:40:21 -07:00
|
|
|
|
|
|
|
{!isMissingField &&
|
2019-01-02 10:23:02 -08:00
|
|
|
error && <Alert type="error" message={error} showIcon />}
|
2018-10-30 07:40:21 -07:00
|
|
|
|
|
|
|
<Row>
|
|
|
|
<Button
|
|
|
|
type="primary"
|
|
|
|
htmlType="submit"
|
|
|
|
disabled={isDisabled}
|
2019-01-02 10:23:02 -08:00
|
|
|
loading={user.isUpdating}
|
2018-10-30 07:40:21 -07:00
|
|
|
>
|
|
|
|
Save changes
|
|
|
|
</Button>
|
|
|
|
<Button type="ghost" htmlType="button" onClick={this.handleCancel}>
|
|
|
|
Cancel
|
|
|
|
</Button>
|
|
|
|
</Row>
|
|
|
|
</Form>
|
|
|
|
{this.state.showError &&
|
2019-01-02 10:23:02 -08:00
|
|
|
user.updateError && (
|
2018-10-30 07:40:21 -07:00
|
|
|
<Alert
|
|
|
|
className="ProfileEdit-alert"
|
|
|
|
message={`There was an error attempting to update your profile. (code ${
|
2019-01-02 10:23:02 -08:00
|
|
|
user.updateError
|
2018-10-30 07:40:21 -07:00
|
|
|
})`}
|
|
|
|
type="error"
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="ProfileEditShade" />
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-01-02 10:23:02 -08:00
|
|
|
private verifySocial = () => {
|
|
|
|
const args = qs.parse(this.props.location.search);
|
|
|
|
const { userid } = this.props.user;
|
|
|
|
if (args.code && args.service) {
|
|
|
|
this.setState({ activeSocialService: args.service });
|
|
|
|
verifySocial(args.service, args.code)
|
|
|
|
.then(async res => {
|
|
|
|
// refresh user data
|
|
|
|
await this.props.fetchUser(userid.toString());
|
|
|
|
// update just the socialMedias on state.fields
|
|
|
|
const socialMedias = this.props.user.socialMedias;
|
|
|
|
const fields = {
|
|
|
|
...this.state.fields,
|
|
|
|
socialMedias,
|
|
|
|
};
|
|
|
|
this.setState({
|
|
|
|
fields,
|
|
|
|
activeSocialService: null,
|
|
|
|
socialVerificationMessage: `
|
|
|
|
Verified ${res.data.username} on ${args.service.toLowerCase()}.
|
|
|
|
`,
|
|
|
|
});
|
|
|
|
// remove search query from url
|
|
|
|
this.props.history.push({ pathname: `/profile/${userid}/edit` });
|
|
|
|
})
|
|
|
|
.catch(e => {
|
|
|
|
this.setState({
|
|
|
|
activeSocialService: null,
|
|
|
|
socialVerificationError: e.message || e.toString(),
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-10-30 07:40:21 -07:00
|
|
|
private handleSave = (evt: React.SyntheticEvent<any>) => {
|
|
|
|
evt.preventDefault();
|
2019-01-02 10:23:02 -08:00
|
|
|
this.props.updateUser(this.state.fields);
|
2018-10-30 07:40:21 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
private handleCancel = () => {
|
2018-11-26 17:14:00 -08:00
|
|
|
const propsAvatar = this.props.user.avatar;
|
|
|
|
const stateAvatar = this.state.fields.avatar;
|
2018-11-16 19:33:25 -08:00
|
|
|
// cleanup uploaded file if we cancel
|
2018-12-14 11:36:22 -08:00
|
|
|
if (
|
|
|
|
stateAvatar &&
|
|
|
|
stateAvatar.imageUrl &&
|
|
|
|
(!propsAvatar || propsAvatar.imageUrl !== stateAvatar.imageUrl)
|
|
|
|
) {
|
2018-11-16 19:33:25 -08:00
|
|
|
axios.delete('/api/v1/users/avatar', {
|
2018-11-26 17:14:00 -08:00
|
|
|
params: { url: stateAvatar.imageUrl },
|
2018-11-16 19:33:25 -08:00
|
|
|
});
|
|
|
|
}
|
2019-01-02 10:23:02 -08:00
|
|
|
this.handleDone();
|
2018-10-30 07:40:21 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
private handleChangeField = (ev: React.ChangeEvent<HTMLInputElement>) => {
|
|
|
|
const { name, value } = ev.currentTarget;
|
|
|
|
const fields = {
|
|
|
|
...this.state.fields,
|
|
|
|
[name as any]: value,
|
|
|
|
};
|
|
|
|
const isChanged = this.isChangedCheck(fields);
|
|
|
|
this.setState({
|
|
|
|
isChanged,
|
|
|
|
fields,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2019-01-02 10:23:02 -08:00
|
|
|
private handleSocialAdd = async (service: SOCIAL_SERVICE) => {
|
|
|
|
this.setState({ activeSocialService: service });
|
|
|
|
if (this.state.isChanged) {
|
|
|
|
// save any changes first
|
|
|
|
await this.props.updateUser(this.state.fields);
|
|
|
|
}
|
|
|
|
getSocialAuthUrl(service)
|
|
|
|
.then(res => {
|
|
|
|
window.location.href = res.data.url;
|
|
|
|
})
|
|
|
|
.catch(e => {
|
|
|
|
this.setState({
|
|
|
|
activeSocialService: null,
|
|
|
|
socialVerificationError: e.message || e.toString(),
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
2018-11-16 15:05:17 -08:00
|
|
|
|
2019-01-02 10:23:02 -08:00
|
|
|
private handleSocialDelete = (service: SOCIAL_SERVICE) => {
|
2018-11-16 15:05:17 -08:00
|
|
|
const socialMedias = this.state.fields.socialMedias.filter(
|
|
|
|
sm => sm.service !== service,
|
|
|
|
);
|
2018-10-30 07:40:21 -07:00
|
|
|
const fields = {
|
|
|
|
...this.state.fields,
|
2018-11-16 15:05:17 -08:00
|
|
|
socialMedias,
|
2018-10-30 07:40:21 -07:00
|
|
|
};
|
|
|
|
this.setState({
|
|
|
|
fields,
|
2019-01-02 10:23:02 -08:00
|
|
|
isChanged: this.isChangedCheck(fields),
|
2018-10-30 07:40:21 -07:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2018-11-16 19:33:25 -08:00
|
|
|
private handleChangePhoto = (url: string) => {
|
2018-10-30 07:40:21 -07:00
|
|
|
const fields = {
|
|
|
|
...this.state.fields,
|
2018-11-16 15:05:17 -08:00
|
|
|
avatar: {
|
2018-11-26 17:14:00 -08:00
|
|
|
imageUrl: url,
|
2018-11-16 15:05:17 -08:00
|
|
|
},
|
2018-10-30 07:40:21 -07:00
|
|
|
};
|
|
|
|
const isChanged = this.isChangedCheck(fields);
|
|
|
|
this.setState({
|
|
|
|
isChanged,
|
|
|
|
fields,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
private handleDeletePhoto = () => {
|
2018-11-16 15:05:17 -08:00
|
|
|
const fields = {
|
|
|
|
...this.state.fields,
|
|
|
|
avatar: null,
|
|
|
|
};
|
2018-10-30 07:40:21 -07:00
|
|
|
const isChanged = this.isChangedCheck(fields);
|
|
|
|
this.setState({ isChanged, fields });
|
|
|
|
};
|
|
|
|
|
2018-11-16 15:05:17 -08:00
|
|
|
private isChangedCheck = (a: User) => {
|
2018-10-30 07:40:21 -07:00
|
|
|
return !lodash.isEqual(a, this.props.user);
|
|
|
|
};
|
2019-01-02 10:23:02 -08:00
|
|
|
|
|
|
|
private handleDone = () => {
|
|
|
|
this.setState({ isDone: true });
|
|
|
|
};
|
2018-10-30 07:40:21 -07:00
|
|
|
}
|
2019-01-02 10:23:02 -08:00
|
|
|
|
|
|
|
const withConnect = connect<StateProps, DispatchProps, OwnProps, AppState>(
|
|
|
|
state => ({
|
|
|
|
authUser: state.auth.user,
|
|
|
|
hasCheckedAuthUser: state.auth.hasCheckedUser,
|
|
|
|
}),
|
|
|
|
{
|
|
|
|
fetchUser: usersActions.fetchUser,
|
|
|
|
updateUser: usersActions.updateUser,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
export default compose<Props, OwnProps>(
|
|
|
|
withRouter,
|
|
|
|
withConnect,
|
|
|
|
)(ProfileEdit);
|