diff --git a/app/soapbox/actions/export_data.js b/app/soapbox/actions/export_data.js deleted file mode 100644 index 12e0bd58b..000000000 --- a/app/soapbox/actions/export_data.js +++ /dev/null @@ -1,104 +0,0 @@ -import { defineMessages } from 'react-intl'; - -import snackbar from 'soapbox/actions/snackbar'; - -import api, { getLinks } from '../api'; - -export const EXPORT_FOLLOWS_REQUEST = 'EXPORT_FOLLOWS_REQUEST'; -export const EXPORT_FOLLOWS_SUCCESS = 'EXPORT_FOLLOWS_SUCCESS'; -export const EXPORT_FOLLOWS_FAIL = 'EXPORT_FOLLOWS_FAIL'; - -export const EXPORT_BLOCKS_REQUEST = 'EXPORT_BLOCKS_REQUEST'; -export const EXPORT_BLOCKS_SUCCESS = 'EXPORT_BLOCKS_SUCCESS'; -export const EXPORT_BLOCKS_FAIL = 'EXPORT_BLOCKS_FAIL'; - -export const EXPORT_MUTES_REQUEST = 'EXPORT_MUTES_REQUEST'; -export const EXPORT_MUTES_SUCCESS = 'EXPORT_MUTES_SUCCESS'; -export const EXPORT_MUTES_FAIL = 'EXPORT_MUTES_FAIL'; - -const messages = defineMessages({ - blocksSuccess: { id: 'export_data.success.blocks', defaultMessage: 'Blocks exported successfully' }, - followersSuccess: { id: 'export_data.success.followers', defaultMessage: 'Followers exported successfully' }, - mutesSuccess: { id: 'export_data.success.mutes', defaultMessage: 'Mutes exported successfully' }, -}); - -function fileExport(content, fileName) { - const fileToDownload = document.createElement('a'); - - fileToDownload.setAttribute('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(content)); - fileToDownload.setAttribute('download', fileName); - fileToDownload.style.display = 'none'; - document.body.appendChild(fileToDownload); - fileToDownload.click(); - document.body.removeChild(fileToDownload); -} - -function listAccounts(state) { - return async apiResponse => { - const followings = apiResponse.data; - let accounts = []; - let next = getLinks(apiResponse).refs.find(link => link.rel === 'next'); - while (next) { - apiResponse = await api(state).get(next.uri); - next = getLinks(apiResponse).refs.find(link => link.rel === 'next'); - Array.prototype.push.apply(followings, apiResponse.data); - } - - accounts = followings.map(account => account.fqn); - return [... new Set(accounts)]; - }; -} - -export function exportFollows(intl) { - return (dispatch, getState) => { - dispatch({ type: EXPORT_FOLLOWS_REQUEST }); - const me = getState().get('me'); - return api(getState) - .get(`/api/v1/accounts/${me}/following?limit=40`) - .then(listAccounts(getState)) - .then((followings) => { - followings = followings.map(fqn => fqn + ',true'); - followings.unshift('Account address,Show boosts'); - fileExport(followings.join('\n'), 'export_followings.csv'); - - dispatch(snackbar.success(intl.formatMessage(messages.followersSuccess))); - dispatch({ type: EXPORT_FOLLOWS_SUCCESS }); - }).catch(error => { - dispatch({ type: EXPORT_FOLLOWS_FAIL, error }); - }); - }; -} - -export function exportBlocks(intl) { - return (dispatch, getState) => { - dispatch({ type: EXPORT_BLOCKS_REQUEST }); - return api(getState) - .get('/api/v1/blocks?limit=40') - .then(listAccounts(getState)) - .then((blocks) => { - fileExport(blocks.join('\n'), 'export_block.csv'); - - dispatch(snackbar.success(intl.formatMessage(messages.blocksSuccess))); - dispatch({ type: EXPORT_BLOCKS_SUCCESS }); - }).catch(error => { - dispatch({ type: EXPORT_BLOCKS_FAIL, error }); - }); - }; -} - -export function exportMutes(intl) { - return (dispatch, getState) => { - dispatch({ type: EXPORT_MUTES_REQUEST }); - return api(getState) - .get('/api/v1/mutes?limit=40') - .then(listAccounts(getState)) - .then((mutes) => { - fileExport(mutes.join('\n'), 'export_mutes.csv'); - - dispatch(snackbar.success(intl.formatMessage(messages.mutesSuccess))); - dispatch({ type: EXPORT_MUTES_SUCCESS }); - }).catch(error => { - dispatch({ type: EXPORT_MUTES_FAIL, error }); - }); - }; -} diff --git a/app/soapbox/actions/export_data.ts b/app/soapbox/actions/export_data.ts new file mode 100644 index 000000000..de81215dd --- /dev/null +++ b/app/soapbox/actions/export_data.ts @@ -0,0 +1,113 @@ +import { defineMessages } from 'react-intl'; + +import api, { getLinks } from '../api'; + +import snackbar from './snackbar'; + +import type { SnackbarAction } from './snackbar'; +import type { AxiosResponse } from 'axios'; +import type { RootState } from 'soapbox/store'; + +export const EXPORT_FOLLOWS_REQUEST = 'EXPORT_FOLLOWS_REQUEST'; +export const EXPORT_FOLLOWS_SUCCESS = 'EXPORT_FOLLOWS_SUCCESS'; +export const EXPORT_FOLLOWS_FAIL = 'EXPORT_FOLLOWS_FAIL'; + +export const EXPORT_BLOCKS_REQUEST = 'EXPORT_BLOCKS_REQUEST'; +export const EXPORT_BLOCKS_SUCCESS = 'EXPORT_BLOCKS_SUCCESS'; +export const EXPORT_BLOCKS_FAIL = 'EXPORT_BLOCKS_FAIL'; + +export const EXPORT_MUTES_REQUEST = 'EXPORT_MUTES_REQUEST'; +export const EXPORT_MUTES_SUCCESS = 'EXPORT_MUTES_SUCCESS'; +export const EXPORT_MUTES_FAIL = 'EXPORT_MUTES_FAIL'; + +const messages = defineMessages({ + blocksSuccess: { id: 'export_data.success.blocks', defaultMessage: 'Blocks exported successfully' }, + followersSuccess: { id: 'export_data.success.followers', defaultMessage: 'Followers exported successfully' }, + mutesSuccess: { id: 'export_data.success.mutes', defaultMessage: 'Mutes exported successfully' }, +}); + +type ExportDataActions = { + type: typeof EXPORT_FOLLOWS_REQUEST + | typeof EXPORT_FOLLOWS_SUCCESS + | typeof EXPORT_FOLLOWS_FAIL + | typeof EXPORT_BLOCKS_REQUEST + | typeof EXPORT_BLOCKS_SUCCESS + | typeof EXPORT_BLOCKS_FAIL + | typeof EXPORT_MUTES_REQUEST + | typeof EXPORT_MUTES_SUCCESS + | typeof EXPORT_MUTES_FAIL, + error?: any, +} | SnackbarAction + +function fileExport(content: string, fileName: string) { + const fileToDownload = document.createElement('a'); + + fileToDownload.setAttribute('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(content)); + fileToDownload.setAttribute('download', fileName); + fileToDownload.style.display = 'none'; + document.body.appendChild(fileToDownload); + fileToDownload.click(); + document.body.removeChild(fileToDownload); +} + +const listAccounts = (getState: () => RootState) => async(apiResponse: AxiosResponse) => { + const followings = apiResponse.data; + let accounts = []; + let next = getLinks(apiResponse).refs.find(link => link.rel === 'next'); + while (next) { + apiResponse = await api(getState).get(next.uri); + next = getLinks(apiResponse).refs.find(link => link.rel === 'next'); + Array.prototype.push.apply(followings, apiResponse.data); + } + + accounts = followings.map((account: { fqn: string }) => account.fqn); + return Array.from(new Set(accounts)); +}; + +export const exportFollows = () => (dispatch: React.Dispatch, getState: () => RootState) => { + dispatch({ type: EXPORT_FOLLOWS_REQUEST }); + const me = getState().me; + return api(getState) + .get(`/api/v1/accounts/${me}/following?limit=40`) + .then(listAccounts(getState)) + .then((followings) => { + followings = followings.map(fqn => fqn + ',true'); + followings.unshift('Account address,Show boosts'); + fileExport(followings.join('\n'), 'export_followings.csv'); + + dispatch(snackbar.success(messages.followersSuccess)); + dispatch({ type: EXPORT_FOLLOWS_SUCCESS }); + }).catch(error => { + dispatch({ type: EXPORT_FOLLOWS_FAIL, error }); + }); +}; + +export const exportBlocks = () => (dispatch: React.Dispatch, getState: () => RootState) => { + dispatch({ type: EXPORT_BLOCKS_REQUEST }); + return api(getState) + .get('/api/v1/blocks?limit=40') + .then(listAccounts(getState)) + .then((blocks) => { + fileExport(blocks.join('\n'), 'export_block.csv'); + + dispatch(snackbar.success(messages.blocksSuccess)); + dispatch({ type: EXPORT_BLOCKS_SUCCESS }); + }).catch(error => { + dispatch({ type: EXPORT_BLOCKS_FAIL, error }); + }); +}; + +export const exportMutes = () => (dispatch: React.Dispatch, getState: () => RootState) => { + dispatch({ type: EXPORT_MUTES_REQUEST }); + return api(getState) + .get('/api/v1/mutes?limit=40') + .then(listAccounts(getState)) + .then((mutes) => { + fileExport(mutes.join('\n'), 'export_mutes.csv'); + + dispatch(snackbar.success(messages.mutesSuccess)); + dispatch({ type: EXPORT_MUTES_SUCCESS }); + }).catch(error => { + dispatch({ type: EXPORT_MUTES_FAIL, error }); + }); +}; diff --git a/app/soapbox/actions/import_data.js b/app/soapbox/actions/import_data.ts similarity index 65% rename from app/soapbox/actions/import_data.js rename to app/soapbox/actions/import_data.ts index 7bde21a4a..43de9f85c 100644 --- a/app/soapbox/actions/import_data.js +++ b/app/soapbox/actions/import_data.ts @@ -4,6 +4,9 @@ import snackbar from 'soapbox/actions/snackbar'; import api from '../api'; +import type { SnackbarAction } from './snackbar'; +import type { RootState } from 'soapbox/store'; + export const IMPORT_FOLLOWS_REQUEST = 'IMPORT_FOLLOWS_REQUEST'; export const IMPORT_FOLLOWS_SUCCESS = 'IMPORT_FOLLOWS_SUCCESS'; export const IMPORT_FOLLOWS_FAIL = 'IMPORT_FOLLOWS_FAIL'; @@ -16,50 +19,61 @@ export const IMPORT_MUTES_REQUEST = 'IMPORT_MUTES_REQUEST'; export const IMPORT_MUTES_SUCCESS = 'IMPORT_MUTES_SUCCESS'; export const IMPORT_MUTES_FAIL = 'IMPORT_MUTES_FAIL'; +type ImportDataActions = { + type: typeof IMPORT_FOLLOWS_REQUEST + | typeof IMPORT_FOLLOWS_SUCCESS + | typeof IMPORT_FOLLOWS_FAIL + | typeof IMPORT_BLOCKS_REQUEST + | typeof IMPORT_BLOCKS_SUCCESS + | typeof IMPORT_BLOCKS_FAIL + | typeof IMPORT_MUTES_REQUEST + | typeof IMPORT_MUTES_SUCCESS + | typeof IMPORT_MUTES_FAIL, + error?: any, + config?: string +} | SnackbarAction + const messages = defineMessages({ blocksSuccess: { id: 'import_data.success.blocks', defaultMessage: 'Blocks imported successfully' }, followersSuccess: { id: 'import_data.success.followers', defaultMessage: 'Followers imported successfully' }, mutesSuccess: { id: 'import_data.success.mutes', defaultMessage: 'Mutes imported successfully' }, }); -export function importFollows(intl, params) { - return (dispatch, getState) => { +export const importFollows = (params: FormData) => + (dispatch: React.Dispatch, getState: () => RootState) => { dispatch({ type: IMPORT_FOLLOWS_REQUEST }); return api(getState) .post('/api/pleroma/follow_import', params) .then(response => { - dispatch(snackbar.success(intl.formatMessage(messages.followersSuccess))); + dispatch(snackbar.success(messages.followersSuccess)); dispatch({ type: IMPORT_FOLLOWS_SUCCESS, config: response.data }); }).catch(error => { dispatch({ type: IMPORT_FOLLOWS_FAIL, error }); }); }; -} -export function importBlocks(intl, params) { - return (dispatch, getState) => { +export const importBlocks = (params: FormData) => + (dispatch: React.Dispatch, getState: () => RootState) => { dispatch({ type: IMPORT_BLOCKS_REQUEST }); return api(getState) .post('/api/pleroma/blocks_import', params) .then(response => { - dispatch(snackbar.success(intl.formatMessage(messages.blocksSuccess))); + dispatch(snackbar.success(messages.blocksSuccess)); dispatch({ type: IMPORT_BLOCKS_SUCCESS, config: response.data }); }).catch(error => { dispatch({ type: IMPORT_BLOCKS_FAIL, error }); }); }; -} -export function importMutes(intl, params) { - return (dispatch, getState) => { +export const importMutes = (params: FormData) => + (dispatch: React.Dispatch, getState: () => RootState) => { dispatch({ type: IMPORT_MUTES_REQUEST }); return api(getState) .post('/api/pleroma/mutes_import', params) .then(response => { - dispatch(snackbar.success(intl.formatMessage(messages.mutesSuccess))); + dispatch(snackbar.success(messages.mutesSuccess)); dispatch({ type: IMPORT_MUTES_SUCCESS, config: response.data }); }).catch(error => { dispatch({ type: IMPORT_MUTES_FAIL, error }); }); }; -} diff --git a/app/soapbox/actions/snackbar.js b/app/soapbox/actions/snackbar.js deleted file mode 100644 index 47fd11137..000000000 --- a/app/soapbox/actions/snackbar.js +++ /dev/null @@ -1,28 +0,0 @@ -import { ALERT_SHOW } from './alerts'; - -export const show = (severity, message, actionLabel, actionLink) => ({ - type: ALERT_SHOW, - message, - actionLabel, - actionLink, - severity, -}); - -export function info(message, actionLabel, actionLink) { - return show('info', message, actionLabel, actionLink); -} - -export function success(message, actionLabel, actionLink) { - return show('success', message, actionLabel, actionLink); -} - -export function error(message, actionLabel, actionLink) { - return show('error', message, actionLabel, actionLink); -} - -export default { - info, - success, - error, - show, -}; diff --git a/app/soapbox/actions/snackbar.ts b/app/soapbox/actions/snackbar.ts new file mode 100644 index 000000000..d1cda0d94 --- /dev/null +++ b/app/soapbox/actions/snackbar.ts @@ -0,0 +1,39 @@ +import { ALERT_SHOW } from './alerts'; + +import type { MessageDescriptor } from 'react-intl'; + +type SnackbarActionSeverity = 'info' | 'success' | 'error' + +type SnackbarMessage = string | MessageDescriptor + +export type SnackbarAction = { + type: typeof ALERT_SHOW + message: SnackbarMessage + actionLabel?: string + actionLink?: string + severity: SnackbarActionSeverity +} + +export const show = (severity: SnackbarActionSeverity, message: SnackbarMessage, actionLabel?: string, actionLink?: string): SnackbarAction => ({ + type: ALERT_SHOW, + message, + actionLabel, + actionLink, + severity, +}); + +export const info = (message: SnackbarMessage, actionLabel?: string, actionLink?: string) => + show('info', message, actionLabel, actionLink); + +export const success = (message: SnackbarMessage, actionLabel?: string, actionLink?: string) => + show('success', message, actionLabel, actionLink); + +export const error = (message: SnackbarMessage, actionLabel?: string, actionLink?: string) => + show('error', message, actionLabel, actionLink); + +export default { + info, + success, + error, + show, +}; diff --git a/app/soapbox/features/export_data/components/csv_exporter.js b/app/soapbox/features/export_data/components/csv_exporter.js deleted file mode 100644 index bd47b83fb..000000000 --- a/app/soapbox/features/export_data/components/csv_exporter.js +++ /dev/null @@ -1,54 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { injectIntl } from 'react-intl'; -import { connect } from 'react-redux'; - -import { Button, Form, FormActions, Text } from 'soapbox/components/ui'; - -export default @connect() -@injectIntl -class CSVExporter extends ImmutablePureComponent { - - static propTypes = { - action: PropTypes.func.isRequired, - messages: PropTypes.object.isRequired, - dispatch: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - }; - - state = { - isLoading: false, - } - - handleClick = (event) => { - const { dispatch, action, intl } = this.props; - - this.setState({ isLoading: true }); - dispatch(action(intl)).then(() => { - this.setState({ isLoading: false }); - }).catch((error) => { - this.setState({ isLoading: false }); - }); - } - - render() { - const { intl, messages } = this.props; - - return ( - <> -
- {intl.formatMessage(messages.input_label)} - {intl.formatMessage(messages.input_hint)} - - - - -
- - ); - } - -} diff --git a/app/soapbox/features/export_data/components/csv_exporter.tsx b/app/soapbox/features/export_data/components/csv_exporter.tsx new file mode 100644 index 000000000..f44aaa424 --- /dev/null +++ b/app/soapbox/features/export_data/components/csv_exporter.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { useState } from 'react'; +import { MessageDescriptor, useIntl } from 'react-intl'; + +import { Button, Form, FormActions, Text } from 'soapbox/components/ui'; +import { useAppDispatch } from 'soapbox/hooks'; +import { AppDispatch } from 'soapbox/store'; + +interface ICSVExporter { + messages: { + input_label: MessageDescriptor, + input_hint: MessageDescriptor, + submit: MessageDescriptor, + }, + action: () => (dispatch: AppDispatch, getState: any) => Promise, +} + +const CSVExporter: React.FC = ({ messages, action }) => { + const dispatch = useAppDispatch(); + const intl = useIntl(); + + const [isLoading, setIsLoading] = useState(false); + + const handleClick: React.MouseEventHandler = (event) => { + setIsLoading(true); + dispatch(action()).then(() => { + setIsLoading(false); + }).catch(() => { + setIsLoading(false); + }); + }; + + return ( +
+ {intl.formatMessage(messages.input_label)} + {intl.formatMessage(messages.input_hint)} + + + + +
+ ); +}; + +export default CSVExporter; diff --git a/app/soapbox/features/export_data/index.js b/app/soapbox/features/export_data/index.tsx similarity index 60% rename from app/soapbox/features/export_data/index.js rename to app/soapbox/features/export_data/index.tsx index 4a73f700b..3a00b6051 100644 --- a/app/soapbox/features/export_data/index.js +++ b/app/soapbox/features/export_data/index.tsx @@ -1,15 +1,11 @@ -import PropTypes from 'prop-types'; import React from 'react'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { defineMessages, injectIntl } from 'react-intl'; -import { connect } from 'react-redux'; +import { defineMessages, useIntl } from 'react-intl'; import { exportFollows, exportBlocks, exportMutes, } from 'soapbox/actions/export_data'; -import { getFeatures } from 'soapbox/utils/features'; import Column from '../ui/components/column'; @@ -38,29 +34,16 @@ const muteMessages = defineMessages({ submit: { id: 'export_data.actions.export_mutes', defaultMessage: 'Export mutes' }, }); -const mapStateToProps = state => ({ - features: getFeatures(state.get('instance')), -}); +const ExportData = () => { + const intl = useIntl(); -export default @connect(mapStateToProps) -@injectIntl -class ExportData extends ImmutablePureComponent { + return ( + + + + + + ); +}; - static propTypes = { - intl: PropTypes.object.isRequired, - features: PropTypes.object, - }; - - render() { - const { intl } = this.props; - - return ( - - - - - - ); - } - -} +export default ExportData; diff --git a/app/soapbox/features/import_data/components/csv_importer.js b/app/soapbox/features/import_data/components/csv_importer.js deleted file mode 100644 index ff17cade4..000000000 --- a/app/soapbox/features/import_data/components/csv_importer.js +++ /dev/null @@ -1,71 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { injectIntl } from 'react-intl'; -import { connect } from 'react-redux'; - -import { Button, Form, FormActions, Text } from 'soapbox/components/ui'; - -export default @connect() -@injectIntl -class CSVImporter extends ImmutablePureComponent { - - static propTypes = { - action: PropTypes.func.isRequired, - messages: PropTypes.object.isRequired, - dispatch: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - }; - - state = { - file: null, - isLoading: false, - } - - handleSubmit = (event) => { - const { dispatch, action, intl } = this.props; - - const params = new FormData(); - params.append('list', this.state.file); - - this.setState({ isLoading: true }); - dispatch(action(intl, params)).then(() => { - this.setState({ isLoading: false }); - }).catch((error) => { - this.setState({ isLoading: false }); - }); - - event.preventDefault(); - } - - handleFileChange = e => { - const [file] = e.target.files || []; - this.setState({ file }); - } - - render() { - const { intl, messages } = this.props; - - return ( -
- {intl.formatMessage(messages.input_label)} -
- - {intl.formatMessage(messages.input_hint)} -
- - - -
- ); - } - -} diff --git a/app/soapbox/features/import_data/components/csv_importer.tsx b/app/soapbox/features/import_data/components/csv_importer.tsx new file mode 100644 index 000000000..aa5937af9 --- /dev/null +++ b/app/soapbox/features/import_data/components/csv_importer.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { useState } from 'react'; +import { MessageDescriptor, useIntl } from 'react-intl'; + +import { Button, FileInput, Form, FormActions, FormGroup, Text } from 'soapbox/components/ui'; +import { useAppDispatch } from 'soapbox/hooks'; +import { AppDispatch } from 'soapbox/store'; + +interface ICSVImporter { + messages: { + input_label: MessageDescriptor, + input_hint: MessageDescriptor, + submit: MessageDescriptor, + }, + action: (params: FormData) => (dispatch: AppDispatch, getState: any) => Promise, +} + +const CSVImporter: React.FC = ({ messages, action }) => { + const dispatch = useAppDispatch(); + const intl = useIntl(); + + const [isLoading, setIsLoading] = useState(false); + const [file, setFile] = useState(null); + + + const handleSubmit: React.FormEventHandler = (event) => { + const params = new FormData(); + params.append('list', file!); + + setIsLoading(true); + dispatch(action(params)).then(() => { + setIsLoading(false); + }).catch(() => { + setIsLoading(false); + }); + + event.preventDefault(); + }; + + const handleFileChange: React.ChangeEventHandler = e => { + const file = e.target.files?.item(0); + setFile(file); + }; + + return ( +
+ {intl.formatMessage(messages.input_label)} + {intl.formatMessage(messages.input_hint)}} + > + + + + + +
+ ); +}; + +export default CSVImporter; diff --git a/app/soapbox/features/import_data/index.js b/app/soapbox/features/import_data/index.tsx similarity index 61% rename from app/soapbox/features/import_data/index.js rename to app/soapbox/features/import_data/index.tsx index 6a3a267e2..b03b20f29 100644 --- a/app/soapbox/features/import_data/index.js +++ b/app/soapbox/features/import_data/index.tsx @@ -1,14 +1,12 @@ -import PropTypes from 'prop-types'; import React from 'react'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { defineMessages, injectIntl } from 'react-intl'; -import { connect } from 'react-redux'; +import { defineMessages, useIntl } from 'react-intl'; import { importFollows, importBlocks, importMutes, } from 'soapbox/actions/import_data'; +import { useAppSelector } from 'soapbox/hooks'; import { getFeatures } from 'soapbox/utils/features'; import Column from '../ui/components/column'; @@ -38,29 +36,17 @@ const muteMessages = defineMessages({ submit: { id: 'import_data.actions.import_mutes', defaultMessage: 'Import mutes' }, }); -const mapStateToProps = state => ({ - features: getFeatures(state.get('instance')), -}); +const ImportData = () => { + const intl = useIntl(); + const features = getFeatures(useAppSelector((state) => state.instance)); -export default @connect(mapStateToProps) -@injectIntl -class ImportData extends ImmutablePureComponent { + return ( + + + + {features.importMutes && } + + ); +}; - static propTypes = { - intl: PropTypes.object.isRequired, - features: PropTypes.object, - }; - - render() { - const { intl, features } = this.props; - - return ( - - - - {features.importMutes && } - - ); - } - -} +export default ImportData;