From f75ffeadd8d3d467ef2a683fc7823b9d0bf188d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 14 Feb 2022 21:35:35 +0100 Subject: [PATCH] Account migrations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/actions/security.js | 20 +++ app/soapbox/features/migration/index.js | 115 ++++++++++++++++++ app/soapbox/features/ui/index.js | 2 + .../features/ui/util/async-components.js | 4 + app/styles/components/buttons.scss | 2 +- 5 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 app/soapbox/features/migration/index.js 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/migration/index.js b/app/soapbox/features/migration/index.js new file mode 100644 index 000000000..a999ca030 --- /dev/null +++ b/app/soapbox/features/migration/index.js @@ -0,0 +1,115 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; +import { connect } from 'react-redux'; +import { Link } from 'react-router-dom'; + +import { moveAccount } from 'soapbox/actions/security'; +import snackbar from 'soapbox/actions/snackbar'; +import ShowablePassword from 'soapbox/components/showable_password'; +import { FieldsGroup, SimpleForm, TextInput } from 'soapbox/features/forms'; +import Column from 'soapbox/features/ui/components/column'; + +const messages = defineMessages({ + heading: { id: 'column.migration', defaultMessage: 'Account migration' }, + submit: { id: 'migration.submit', defaultMessage: 'Move followers' }, + moveAccountSuccess: { id: 'migration.move_account.success', defaultMessage: 'Account successfully moved.' }, + moveAccountFail: { id: 'migration.move_account.fail', defaultMessage: 'Account migration failed.' }, + acctFieldLabel: { id: 'migration.fields.acct.label', defaultMessage: 'Handle of the new account' }, + acctFieldPlaceholder: { id: 'migration.fields.acct.placeholder', defaultMessage: 'username@domain' }, + currentPasswordFieldLabel: { id: 'migration.fields.confirm_password.label', defaultMessage: 'Current password' }, +}); + +export default @connect() +@injectIntl +class Migration extends ImmutablePureComponent { + + static propTypes = { + dispatch: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + state = { + targetAccount: '', + password: '', + isLoading: false, + } + + handleInputChange = e => { + this.setState({ [e.target.name]: e.target.value }); + } + + clearForm = () => { + this.setState({ targetAccount: '', password: '' }); + } + + handleSubmit = e => { + const { targetAccount, password } = this.state; + const { dispatch, intl } = this.props; + this.setState({ isLoading: true }); + return dispatch(moveAccount(targetAccount, password)).then(() => { + this.clearForm(); + dispatch(snackbar.success(intl.formatMessage(messages.moveAccountSuccess))); + }).catch(error => { + dispatch(snackbar.error(intl.formatMessage(messages.moveAccountFail))); + }).then(() => { + this.setState({ isLoading: false }); + }); + } + + render() { + const { intl } = this.props; + + return ( + + +
+ +

+ + + + ), + }} + /> +

+ + +
+ +
+
+
+
+
+ ); + } + +} diff --git a/app/soapbox/features/ui/index.js b/app/soapbox/features/ui/index.js index 1e0ed2d51..34534c506 100644 --- a/app/soapbox/features/ui/index.js +++ b/app/soapbox/features/ui/index.js @@ -104,6 +104,7 @@ import { UserIndex, FederationRestrictions, Aliases, + Migration, FollowRecommendations, Directory, SidebarMenu, @@ -314,6 +315,7 @@ class SwitchingColumnsArea extends React.PureComponent { + diff --git a/app/soapbox/features/ui/util/async-components.js b/app/soapbox/features/ui/util/async-components.js index 341c59047..5f44a2801 100644 --- a/app/soapbox/features/ui/util/async-components.js +++ b/app/soapbox/features/ui/util/async-components.js @@ -414,6 +414,10 @@ export function Aliases() { return import(/* webpackChunkName: "features/aliases" */'../../aliases'); } +export function Migration() { + return import(/* webpackChunkName: "features/migration" */'../../migration'); +} + export function ScheduleForm() { return import(/* webpackChunkName: "features/compose" */'../../compose/components/schedule_form'); } diff --git a/app/styles/components/buttons.scss b/app/styles/components/buttons.scss index 51157bf31..677ae8e28 100644 --- a/app/styles/components/buttons.scss +++ b/app/styles/components/buttons.scss @@ -67,7 +67,7 @@ button { &:disabled, &.disabled { - background-color: var(--brand-color--med); + opacity: 0.2; cursor: default; }