diff --git a/app/soapbox/actions/aliases.js b/app/soapbox/actions/aliases.js index cdeb7208c..e28817681 100644 --- a/app/soapbox/actions/aliases.js +++ b/app/soapbox/actions/aliases.js @@ -1,14 +1,19 @@ import { defineMessages } from 'react-intl'; import { isLoggedIn } from 'soapbox/utils/auth'; +import { getFeatures } from 'soapbox/utils/features'; import api from '../api'; import { showAlertForError } from './alerts'; -import { importFetchedAccount, importFetchedAccounts } from './importer'; -import { ME_PATCH_SUCCESS } from './me'; +import { importFetchedAccounts } from './importer'; +import { patchMeSuccess } from './me'; import snackbar from './snackbar'; +export const ALIASES_FETCH_REQUEST = 'ALIASES_FETCH_REQUEST'; +export const ALIASES_FETCH_SUCCESS = 'ALIASES_FETCH_SUCCESS'; +export const ALIASES_FETCH_FAIL = 'ALIASES_FETCH_FAIL'; + export const ALIASES_SUGGESTIONS_CHANGE = 'ALIASES_SUGGESTIONS_CHANGE'; export const ALIASES_SUGGESTIONS_READY = 'ALIASES_SUGGESTIONS_READY'; export const ALIASES_SUGGESTIONS_CLEAR = 'ALIASES_SUGGESTIONS_CLEAR'; @@ -26,6 +31,38 @@ const messages = defineMessages({ removeSuccess: { id: 'aliases.success.remove', defaultMessage: 'Account alias removed successfully' }, }); +export const fetchAliases = (dispatch, getState) => { + if (!isLoggedIn(getState)) return; + const state = getState(); + + const instance = state.get('instance'); + const features = getFeatures(instance); + + if (!features.accountMoving) return; + + dispatch(fetchAliasesRequest()); + + api(getState).get('/api/pleroma/aliases') + .then(response => { + dispatch(fetchAliasesSuccess(response.data.aliases)); + }) + .catch(err => dispatch(fetchAliasesFail(err))); +}; + +export const fetchAliasesRequest = () => ({ + type: ALIASES_FETCH_REQUEST, +}); + +export const fetchAliasesSuccess = aliases => ({ + type: ALIASES_FETCH_SUCCESS, + value: aliases, +}); + +export const fetchAliasesFail = error => ({ + type: ALIASES_FETCH_FAIL, + error, +}); + export const fetchAliasesSuggestions = q => (dispatch, getState) => { if (!isLoggedIn(getState)) return; @@ -56,80 +93,104 @@ export const changeAliasesSuggestions = value => ({ value, }); -export const addToAliases = (intl, apId) => (dispatch, getState) => { +export const addToAliases = (intl, account) => (dispatch, getState) => { if (!isLoggedIn(getState)) return; const state = getState(); - const me = state.get('me'); - const alsoKnownAs = state.getIn(['accounts_meta', me, 'pleroma', 'also_known_as']); + const instance = state.get('instance'); + const features = getFeatures(instance); - dispatch(addToAliasesRequest(apId)); + if (!features.accountMoving) { + const me = state.get('me'); + const alsoKnownAs = state.getIn(['accounts_meta', me, 'pleroma', 'also_known_as']); - api(getState).patch('/api/v1/accounts/update_credentials', { also_known_as: [...alsoKnownAs, apId] }) - .then((response => { + dispatch(addToAliasesRequest()); + + api(getState).patch('/api/v1/accounts/update_credentials', { also_known_as: [...alsoKnownAs, account.getIn(['pleroma', 'ap_id'])] }) + .then((response => { + dispatch(snackbar.success(intl.formatMessage(messages.createSuccess))); + dispatch(addToAliasesSuccess); + dispatch(patchMeSuccess(response.data)); + })) + .catch(err => dispatch(addToAliasesFail(err))); + + return; + } + + dispatch(addToAliasesRequest()); + + api(getState).put('/api/pleroma/aliases', { + alias: account.get('acct'), + }) + .then(response => { dispatch(snackbar.success(intl.formatMessage(messages.createSuccess))); - dispatch(addToAliasesSuccess(response.data)); - })) - .catch(err => dispatch(addToAliasesFail(err))); + dispatch(addToAliasesSuccess); + dispatch(fetchAliases); + }) + .catch(err => dispatch(fetchAliasesFail(err))); }; -export const addToAliasesRequest = (apId) => ({ +export const addToAliasesRequest = () => ({ type: ALIASES_ADD_REQUEST, - apId, }); -export const addToAliasesSuccess = me => dispatch => { - dispatch(importFetchedAccount(me)); - dispatch({ - type: ME_PATCH_SUCCESS, - me, - }); - dispatch({ - type: ALIASES_ADD_SUCCESS, - }); -}; +export const addToAliasesSuccess = () => ({ + type: ALIASES_ADD_SUCCESS, +}); -export const addToAliasesFail = (apId, error) => ({ +export const addToAliasesFail = error => ({ type: ALIASES_ADD_FAIL, - apId, error, }); -export const removeFromAliases = (intl, apId) => (dispatch, getState) => { +export const removeFromAliases = (intl, account) => (dispatch, getState) => { if (!isLoggedIn(getState)) return; const state = getState(); - const me = state.get('me'); - const alsoKnownAs = state.getIn(['accounts_meta', me, 'pleroma', 'also_known_as']); + const instance = state.get('instance'); + const features = getFeatures(instance); - dispatch(removeFromAliasesRequest(apId)); + if (!features.accountMoving) { + const me = state.get('me'); + const alsoKnownAs = state.getIn(['accounts_meta', me, 'pleroma', 'also_known_as']); - api(getState).patch('/api/v1/accounts/update_credentials', { also_known_as: alsoKnownAs.filter(id => id !== apId) }) + dispatch(removeFromAliasesRequest()); + + api(getState).patch('/api/v1/accounts/update_credentials', { also_known_as: alsoKnownAs.filter(id => id !== account) }) + .then(response => { + dispatch(snackbar.success(intl.formatMessage(messages.removeSuccess))); + dispatch(removeFromAliasesSuccess); + dispatch(patchMeSuccess(response.data)); + }) + .catch(err => dispatch(removeFromAliasesFail(err))); + + return; + } + + dispatch(addToAliasesRequest()); + + api(getState).delete('/api/pleroma/aliases', { + data: { + alias: account, + }, + }) .then(response => { dispatch(snackbar.success(intl.formatMessage(messages.removeSuccess))); - dispatch(removeFromAliasesSuccess(response.data)); + dispatch(removeFromAliasesSuccess); + dispatch(fetchAliases); }) - .catch(err => dispatch(removeFromAliasesFail(apId, err))); + .catch(err => dispatch(fetchAliasesFail(err))); }; -export const removeFromAliasesRequest = (apId) => ({ +export const removeFromAliasesRequest = () => ({ type: ALIASES_REMOVE_REQUEST, - apId, }); -export const removeFromAliasesSuccess = me => dispatch => { - dispatch(importFetchedAccount(me)); - dispatch({ - type: ME_PATCH_SUCCESS, - me, - }); - dispatch({ - type: ALIASES_REMOVE_SUCCESS, - }); -}; +export const removeFromAliasesSuccess = () => ({ + type: ALIASES_REMOVE_SUCCESS, +}); -export const removeFromAliasesFail = (apId, error) => ({ +export const removeFromAliasesFail = error => ({ type: ALIASES_REMOVE_FAIL, - apId, error, }); diff --git a/app/soapbox/actions/security.js b/app/soapbox/actions/security.js index 4cc957992..254acbdfb 100644 --- a/app/soapbox/actions/security.js +++ b/app/soapbox/actions/security.js @@ -35,6 +35,10 @@ export const DELETE_ACCOUNT_REQUEST = 'DELETE_ACCOUNT_REQUEST'; export const DELETE_ACCOUNT_SUCCESS = 'DELETE_ACCOUNT_SUCCESS'; export const DELETE_ACCOUNT_FAIL = 'DELETE_ACCOUNT_FAIL'; +export const MOVE_ACCOUNT_REQUEST = 'MOVE_ACCOUNT_REQUEST'; +export const MOVE_ACCOUNT_SUCCESS = 'MOVE_ACCOUNT_SUCCESS'; +export const MOVE_ACCOUNT_FAIL = 'MOVE_ACCOUNT_FAIL'; + export function fetchOAuthTokens() { return (dispatch, getState) => { dispatch({ type: FETCH_TOKENS_REQUEST }); @@ -124,3 +128,19 @@ export function deleteAccount(intl, password) { }); }; } + +export function moveAccount(targetAccount, password) { + return (dispatch, getState) => { + dispatch({ type: MOVE_ACCOUNT_REQUEST }); + return api(getState).post('/api/pleroma/move_account', { + password, + target_account: targetAccount, + }).then(response => { + if (response.data.error) throw response.data.error; // This endpoint returns HTTP 200 even on failure + dispatch({ type: MOVE_ACCOUNT_SUCCESS, response }); + }).catch(error => { + dispatch({ type: MOVE_ACCOUNT_FAIL, error, skipAlert: true }); + throw error; + }); + }; +} diff --git a/app/soapbox/features/aliases/components/account.js b/app/soapbox/features/aliases/components/account.js index 3e4c7f936..fd0abd21b 100644 --- a/app/soapbox/features/aliases/components/account.js +++ b/app/soapbox/features/aliases/components/account.js @@ -5,11 +5,12 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import { defineMessages, injectIntl } from 'react-intl'; import { connect } from 'react-redux'; -import { addToAliases } from '../../../actions/aliases'; -import Avatar from '../../../components/avatar'; -import DisplayName from '../../../components/display_name'; -import IconButton from '../../../components/icon_button'; -import { makeGetAccount } from '../../../selectors'; +import { addToAliases } from 'soapbox/actions/aliases'; +import Avatar from 'soapbox/components/avatar'; +import DisplayName from 'soapbox/components/display_name'; +import IconButton from 'soapbox/components/icon_button'; +import { makeGetAccount } from 'soapbox/selectors'; +import { getFeatures } from 'soapbox/utils/features'; const messages = defineMessages({ add: { id: 'aliases.account.add', defaultMessage: 'Create alias' }, @@ -18,17 +19,20 @@ const messages = defineMessages({ const makeMapStateToProps = () => { const getAccount = makeGetAccount(); - const mapStateToProps = (state, { accountId, added }) => { + const mapStateToProps = (state, { accountId, added, aliases }) => { const me = state.get('me'); - const ownAccount = getAccount(state, me); + + const instance = state.get('instance'); + const features = getFeatures(instance); const account = getAccount(state, accountId); const apId = account.getIn(['pleroma', 'ap_id']); + const name = features.accountMoving ? account.get('acct') : apId; return { account, apId, - added: typeof added === 'undefined' ? ownAccount.getIn(['pleroma', 'also_known_as']).includes(apId) : added, + added: typeof added === 'undefined' ? aliases.includes(name) : added, me, }; }; @@ -56,7 +60,7 @@ class Account extends ImmutablePureComponent { added: false, }; - handleOnAdd = () => this.props.onAdd(this.props.intl, this.props.apId); + handleOnAdd = () => this.props.onAdd(this.props.intl, this.props.account); render() { const { account, accountId, intl, added, me } = this.props; diff --git a/app/soapbox/features/aliases/index.js b/app/soapbox/features/aliases/index.js index a2f844d98..c9bd35749 100644 --- a/app/soapbox/features/aliases/index.js +++ b/app/soapbox/features/aliases/index.js @@ -1,13 +1,15 @@ +import { List as ImmutableList } from 'immutable'; import React from 'react'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; +import { fetchAliases, removeFromAliases } from 'soapbox/actions/aliases'; import Icon from 'soapbox/components/icon'; +import ScrollableList from 'soapbox/components/scrollable_list'; import { makeGetAccount } from 'soapbox/selectors'; +import { getFeatures } from 'soapbox/utils/features'; -import { removeFromAliases } from '../../actions/aliases'; -import ScrollableList from '../../components/scrollable_list'; import Column from '../ui/components/column'; import ColumnSubheading from '../ui/components/column_subheading'; @@ -30,8 +32,16 @@ const makeMapStateToProps = () => { const me = state.get('me'); const account = getAccount(state, me); + const instance = state.get('instance'); + const features = getFeatures(instance); + + let aliases; + + if (features.accountMoving) aliases = state.getIn(['aliases', 'aliases', 'items'], ImmutableList()); + else aliases = account.getIn(['pleroma', 'also_known_as']); + return { - aliases: account.getIn(['pleroma', 'also_known_as']), + aliases, searchAccountIds: state.getIn(['aliases', 'suggestions', 'items']), loaded: state.getIn(['aliases', 'suggestions', 'loaded']), }; @@ -44,6 +54,11 @@ export default @connect(makeMapStateToProps) @injectIntl class Aliases extends ImmutablePureComponent { + componentDidMount = e => { + const { dispatch } = this.props; + dispatch(fetchAliases); + } + handleFilterDelete = e => { const { dispatch, intl } = this.props; dispatch(removeFromAliases(intl, e.currentTarget.dataset.value)); @@ -65,7 +80,7 @@ class Aliases extends ImmutablePureComponent { ) : (