Merge branch 'account-lookup' into 'develop'
Check username availability See merge request soapbox-pub/soapbox-fe!932
This commit is contained in:
commit
6eae0dc07f
3 changed files with 68 additions and 2 deletions
|
@ -59,6 +59,10 @@ export const ACCOUNT_SEARCH_REQUEST = 'ACCOUNT_SEARCH_REQUEST';
|
||||||
export const ACCOUNT_SEARCH_SUCCESS = 'ACCOUNT_SEARCH_SUCCESS';
|
export const ACCOUNT_SEARCH_SUCCESS = 'ACCOUNT_SEARCH_SUCCESS';
|
||||||
export const ACCOUNT_SEARCH_FAIL = 'ACCOUNT_SEARCH_FAIL';
|
export const ACCOUNT_SEARCH_FAIL = 'ACCOUNT_SEARCH_FAIL';
|
||||||
|
|
||||||
|
export const ACCOUNT_LOOKUP_REQUEST = 'ACCOUNT_LOOKUP_REQUEST';
|
||||||
|
export const ACCOUNT_LOOKUP_SUCCESS = 'ACCOUNT_LOOKUP_SUCCESS';
|
||||||
|
export const ACCOUNT_LOOKUP_FAIL = 'ACCOUNT_LOOKUP_FAIL';
|
||||||
|
|
||||||
export const FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST';
|
export const FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST';
|
||||||
export const FOLLOWERS_FETCH_SUCCESS = 'FOLLOWERS_FETCH_SUCCESS';
|
export const FOLLOWERS_FETCH_SUCCESS = 'FOLLOWERS_FETCH_SUCCESS';
|
||||||
export const FOLLOWERS_FETCH_FAIL = 'FOLLOWERS_FETCH_FAIL';
|
export const FOLLOWERS_FETCH_FAIL = 'FOLLOWERS_FETCH_FAIL';
|
||||||
|
@ -961,3 +965,17 @@ export function accountSearch(params, cancelToken) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function accountLookup(acct, cancelToken) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
dispatch({ type: ACCOUNT_LOOKUP_REQUEST, acct });
|
||||||
|
return api(getState).get('/api/v1/accounts/lookup', { params: { acct }, cancelToken }).then(({ data: account }) => {
|
||||||
|
if (account && account.id) dispatch(importFetchedAccount(account));
|
||||||
|
dispatch({ type: ACCOUNT_LOOKUP_SUCCESS, account });
|
||||||
|
return account;
|
||||||
|
}).catch(error => {
|
||||||
|
dispatch({ type: ACCOUNT_LOOKUP_FAIL });
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { injectIntl, FormattedMessage, defineMessages } from 'react-intl';
|
import { injectIntl, FormattedMessage, defineMessages } from 'react-intl';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
import { CancelToken } from 'axios';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
import ShowablePassword from 'soapbox/components/showable_password';
|
import ShowablePassword from 'soapbox/components/showable_password';
|
||||||
import {
|
import {
|
||||||
SimpleForm,
|
SimpleForm,
|
||||||
|
@ -20,6 +22,7 @@ import { v4 as uuidv4 } from 'uuid';
|
||||||
import { getSettings } from 'soapbox/actions/settings';
|
import { getSettings } from 'soapbox/actions/settings';
|
||||||
import { openModal } from 'soapbox/actions/modal';
|
import { openModal } from 'soapbox/actions/modal';
|
||||||
import { getFeatures } from 'soapbox/utils/features';
|
import { getFeatures } from 'soapbox/utils/features';
|
||||||
|
import { accountLookup } from 'soapbox/actions/accounts';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
username: { id: 'registration.fields.username_placeholder', defaultMessage: 'Username' },
|
username: { id: 'registration.fields.username_placeholder', defaultMessage: 'Username' },
|
||||||
|
@ -39,6 +42,7 @@ const mapStateToProps = (state, props) => ({
|
||||||
needsConfirmation: state.getIn(['instance', 'pleroma', 'metadata', 'account_activation_required']),
|
needsConfirmation: state.getIn(['instance', 'pleroma', 'metadata', 'account_activation_required']),
|
||||||
needsApproval: state.getIn(['instance', 'approval_required']),
|
needsApproval: state.getIn(['instance', 'approval_required']),
|
||||||
supportsEmailList: getFeatures(state.get('instance')).emailList,
|
supportsEmailList: getFeatures(state.get('instance')).emailList,
|
||||||
|
supportsAccountLookup: getFeatures(state.get('instance')).accountLookup,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default @connect(mapStateToProps)
|
export default @connect(mapStateToProps)
|
||||||
|
@ -52,6 +56,7 @@ class RegistrationForm extends ImmutablePureComponent {
|
||||||
needsConfirmation: PropTypes.bool,
|
needsConfirmation: PropTypes.bool,
|
||||||
needsApproval: PropTypes.bool,
|
needsApproval: PropTypes.bool,
|
||||||
supportsEmailList: PropTypes.bool,
|
supportsEmailList: PropTypes.bool,
|
||||||
|
supportsAccountLookup: PropTypes.bool,
|
||||||
inviteToken: PropTypes.string,
|
inviteToken: PropTypes.string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,10 +69,19 @@ class RegistrationForm extends ImmutablePureComponent {
|
||||||
submissionLoading: false,
|
submissionLoading: false,
|
||||||
params: ImmutableMap(),
|
params: ImmutableMap(),
|
||||||
captchaIdempotencyKey: uuidv4(),
|
captchaIdempotencyKey: uuidv4(),
|
||||||
|
usernameUnavailable: false,
|
||||||
passwordConfirmation: '',
|
passwordConfirmation: '',
|
||||||
passwordMismatch: false,
|
passwordMismatch: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
source = CancelToken.source();
|
||||||
|
|
||||||
|
refreshCancelToken = () => {
|
||||||
|
this.source.cancel();
|
||||||
|
this.source = CancelToken.source();
|
||||||
|
return this.source;
|
||||||
|
}
|
||||||
|
|
||||||
setParams = map => {
|
setParams = map => {
|
||||||
this.setState({ params: this.state.params.merge(ImmutableMap(map)) });
|
this.setState({ params: this.state.params.merge(ImmutableMap(map)) });
|
||||||
}
|
}
|
||||||
|
@ -76,6 +90,14 @@ class RegistrationForm extends ImmutablePureComponent {
|
||||||
this.setParams({ [e.target.name]: e.target.value });
|
this.setParams({ [e.target.name]: e.target.value });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onUsernameChange = e => {
|
||||||
|
this.setParams({ username: e.target.value });
|
||||||
|
this.setState({ usernameUnavailable: false });
|
||||||
|
this.source.cancel();
|
||||||
|
|
||||||
|
this.usernameAvailable(e.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
onCheckboxChange = e => {
|
onCheckboxChange = e => {
|
||||||
this.setParams({ [e.target.name]: e.target.checked });
|
this.setParams({ [e.target.name]: e.target.checked });
|
||||||
}
|
}
|
||||||
|
@ -145,6 +167,25 @@ class RegistrationForm extends ImmutablePureComponent {
|
||||||
return params.get('password', '') === passwordConfirmation;
|
return params.get('password', '') === passwordConfirmation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
usernameAvailable = debounce(username => {
|
||||||
|
const { dispatch, supportsAccountLookup } = this.props;
|
||||||
|
|
||||||
|
if (!supportsAccountLookup) return;
|
||||||
|
|
||||||
|
const source = this.refreshCancelToken();
|
||||||
|
|
||||||
|
dispatch(accountLookup(username, source.token))
|
||||||
|
.then(account => {
|
||||||
|
this.setState({ usernameUnavailable: !!account });
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
if (error.response && error.response.status === 404) {
|
||||||
|
this.setState({ usernameUnavailable: false });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}, 1000, { trailing: true });
|
||||||
|
|
||||||
onSubmit = e => {
|
onSubmit = e => {
|
||||||
const { dispatch, inviteToken } = this.props;
|
const { dispatch, inviteToken } = this.props;
|
||||||
|
|
||||||
|
@ -196,7 +237,7 @@ class RegistrationForm extends ImmutablePureComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { instance, intl, supportsEmailList } = this.props;
|
const { instance, intl, supportsEmailList } = this.props;
|
||||||
const { params, passwordConfirmation, passwordMismatch } = this.state;
|
const { params, usernameUnavailable, passwordConfirmation, passwordMismatch } = this.state;
|
||||||
const isLoading = this.state.captchaLoading || this.state.submissionLoading;
|
const isLoading = this.state.captchaLoading || this.state.submissionLoading;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -204,6 +245,11 @@ class RegistrationForm extends ImmutablePureComponent {
|
||||||
<fieldset disabled={isLoading}>
|
<fieldset disabled={isLoading}>
|
||||||
<div className='simple_form__overlay-area'>
|
<div className='simple_form__overlay-area'>
|
||||||
<div className='fields-group'>
|
<div className='fields-group'>
|
||||||
|
{usernameUnavailable && (
|
||||||
|
<div className='error'>
|
||||||
|
<FormattedMessage id='registration.username_unavailable' defaultMessage='Username is already taken.' />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<TextInput
|
<TextInput
|
||||||
placeholder={intl.formatMessage(messages.username)}
|
placeholder={intl.formatMessage(messages.username)}
|
||||||
name='username'
|
name='username'
|
||||||
|
@ -212,8 +258,9 @@ class RegistrationForm extends ImmutablePureComponent {
|
||||||
autoCorrect='off'
|
autoCorrect='off'
|
||||||
autoCapitalize='off'
|
autoCapitalize='off'
|
||||||
pattern='^[a-zA-Z\d_-]+'
|
pattern='^[a-zA-Z\d_-]+'
|
||||||
onChange={this.onInputChange}
|
onChange={this.onUsernameChange}
|
||||||
value={params.get('username', '')}
|
value={params.get('username', '')}
|
||||||
|
error={usernameUnavailable}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<SimpleInput
|
<SimpleInput
|
||||||
|
|
|
@ -70,6 +70,7 @@ export const getFeatures = createSelector([
|
||||||
v.software === MASTODON && gte(v.compatVersion, '3.0.0'),
|
v.software === MASTODON && gte(v.compatVersion, '3.0.0'),
|
||||||
features.includes('profile_directory'),
|
features.includes('profile_directory'),
|
||||||
]),
|
]),
|
||||||
|
accountLookup: v.software === MASTODON && gte(v.version, '3.4.0'),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue