diff --git a/app/soapbox/actions/email_list.js b/app/soapbox/actions/email_list.js
new file mode 100644
index 000000000..9f440b001
--- /dev/null
+++ b/app/soapbox/actions/email_list.js
@@ -0,0 +1,19 @@
+import api from '../api';
+
+export function getSubscribersCsv() {
+ return (dispatch, getState) => {
+ return api(getState).get('/api/v1/pleroma/admin/email_list/subscribers.csv');
+ };
+}
+
+export function getUnsubscribersCsv() {
+ return (dispatch, getState) => {
+ return api(getState).get('/api/v1/pleroma/admin/email_list/unsubscribers.csv');
+ };
+}
+
+export function getCombinedCsv() {
+ return (dispatch, getState) => {
+ return api(getState).get('/api/v1/pleroma/admin/email_list/combined.csv');
+ };
+}
diff --git a/app/soapbox/features/admin/index.js b/app/soapbox/features/admin/index.js
index a0c4dbaa1..90ce7ac1b 100644
--- a/app/soapbox/features/admin/index.js
+++ b/app/soapbox/features/admin/index.js
@@ -8,6 +8,19 @@ import Column from '../ui/components/column';
import RegistrationModePicker from './components/registration_mode_picker';
import { parseVersion } from 'soapbox/utils/features';
import sourceCode from 'soapbox/utils/code';
+import { getSubscribersCsv, getUnsubscribersCsv, getCombinedCsv } from 'soapbox/actions/email_list';
+import { getFeatures } from 'soapbox/utils/features';
+
+// https://stackoverflow.com/a/53230807
+const download = (response, filename) => {
+ const url = URL.createObjectURL(new Blob([response.data]));
+ const link = document.createElement('a');
+ link.href = url;
+ link.setAttribute('download', filename);
+ document.body.appendChild(link);
+ link.click();
+ link.remove();
+};
const messages = defineMessages({
heading: { id: 'column.admin.dashboard', defaultMessage: 'Dashboard' },
@@ -15,6 +28,7 @@ const messages = defineMessages({
const mapStateToProps = (state, props) => ({
instance: state.get('instance'),
+ supportsEmailList: getFeatures(state.get('instance')).emailList,
});
export default @connect(mapStateToProps)
@@ -24,10 +38,32 @@ class Dashboard extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
instance: ImmutablePropTypes.map.isRequired,
+ supportsEmailList: PropTypes.bool,
};
+ handleSubscribersClick = e => {
+ this.props.dispatch(getSubscribersCsv()).then((response) => {
+ download(response, 'subscribers.csv');
+ }).catch(() => {});
+ e.preventDefault();
+ }
+
+ handleUnsubscribersClick = e => {
+ this.props.dispatch(getUnsubscribersCsv()).then((response) => {
+ download(response, 'unsubscribers.csv');
+ }).catch(() => {});
+ e.preventDefault();
+ }
+
+ handleCombinedClick = e => {
+ this.props.dispatch(getCombinedCsv()).then((response) => {
+ download(response, 'combined.csv');
+ }).catch(() => {});
+ e.preventDefault();
+ }
+
render() {
- const { intl, instance } = this.props;
+ const { intl, instance, supportsEmailList } = this.props;
const v = parseVersion(instance.get('version'));
const userCount = instance.getIn(['stats', 'user_count']);
const mau = instance.getIn(['pleroma', 'stats', 'mau']);
@@ -96,6 +132,14 @@ class Dashboard extends ImmutablePureComponent {
{v.software} {v.version}
+ {supportsEmailList && }
);
diff --git a/app/soapbox/features/edit_profile/index.js b/app/soapbox/features/edit_profile/index.js
index e73ef1256..9b5d179b4 100644
--- a/app/soapbox/features/edit_profile/index.js
+++ b/app/soapbox/features/edit_profile/index.js
@@ -24,6 +24,7 @@ import { updateNotificationSettings } from 'soapbox/actions/accounts';
import { unescape } from 'lodash';
import { isVerified } from 'soapbox/utils/accounts';
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
+import { getFeatures } from 'soapbox/utils/features';
const messages = defineMessages({
heading: { id: 'column.edit_profile', defaultMessage: 'Edit profile' },
@@ -41,6 +42,7 @@ const mapStateToProps = state => {
account,
maxFields: state.getIn(['instance', 'pleroma', 'metadata', 'fields_limits', 'max_fields'], 4),
verifiedCanEditName: soapbox.get('verifiedCanEditName'),
+ supportsEmailList: getFeatures(state.get('instance')).emailList,
};
};
@@ -78,11 +80,13 @@ class EditProfile extends ImmutablePureComponent {
super(props);
const { account } = this.props;
const strangerNotifications = account.getIn(['pleroma', 'notification_settings', 'block_from_strangers']);
+ const acceptsEmailList = account.getIn(['pleroma', 'accepts_email_list']);
const initialState = account.withMutations(map => {
map.merge(map.get('source'));
map.delete('source');
map.set('fields', normalizeFields(map.get('fields'), props.maxFields));
map.set('stranger_notifications', strangerNotifications);
+ map.set('accepts_email_list', acceptsEmailList);
unescapeParams(map, ['display_name', 'bio']);
});
this.state = initialState.toObject();
@@ -117,6 +121,7 @@ class EditProfile extends ImmutablePureComponent {
avatar: state.avatar_file,
header: state.header_file,
locked: state.locked,
+ accepts_email_list: state.accepts_email_list,
}, this.getFieldParams().toJS());
}
@@ -179,7 +184,7 @@ class EditProfile extends ImmutablePureComponent {
}
render() {
- const { intl, maxFields, account, verifiedCanEditName } = this.props;
+ const { intl, maxFields, account, verifiedCanEditName, supportsEmailList } = this.props;
const verified = isVerified(account);
const canEditName = verifiedCanEditName || !verified;
@@ -246,6 +251,13 @@ class EditProfile extends ImmutablePureComponent {
checked={this.state.stranger_notifications}
onChange={this.handleCheckboxChange}
/>
+ {supportsEmailList && }
+ hint={}
+ name='accepts_email_list'
+ checked={this.state.accepts_email_list}
+ onChange={this.handleCheckboxChange}
+ />}
diff --git a/app/soapbox/features/landing_page/components/registration_form.js b/app/soapbox/features/landing_page/components/registration_form.js
index 808e9d56f..b0518c704 100644
--- a/app/soapbox/features/landing_page/components/registration_form.js
+++ b/app/soapbox/features/landing_page/components/registration_form.js
@@ -18,6 +18,7 @@ import { Map as ImmutableMap } from 'immutable';
import { v4 as uuidv4 } from 'uuid';
import { getSettings } from 'soapbox/actions/settings';
import { openModal } from 'soapbox/actions/modal';
+import { getFeatures } from 'soapbox/utils/features';
const messages = defineMessages({
username: { id: 'registration.fields.username_placeholder', defaultMessage: 'Username' },
@@ -28,6 +29,7 @@ const messages = defineMessages({
agreement: { id: 'registration.agreement', defaultMessage: 'I agree to the {tos}.' },
tos: { id: 'registration.tos', defaultMessage: 'Terms of Service' },
close: { id: 'registration.confirmation_modal.close', defaultMessage: 'Close' },
+ newsletter: { id: 'registration.newsletter', defaultMessage: 'Subscribe to newsletter.' },
});
const mapStateToProps = (state, props) => ({
@@ -35,6 +37,7 @@ const mapStateToProps = (state, props) => ({
locale: getSettings(state).get('locale'),
needsConfirmation: state.getIn(['instance', 'pleroma', 'metadata', 'account_activation_required']),
needsApproval: state.getIn(['instance', 'approval_required']),
+ supportsEmailList: getFeatures(state.get('instance')).emailList,
});
export default @connect(mapStateToProps)
@@ -135,7 +138,7 @@ class RegistrationForm extends ImmutablePureComponent {
}
render() {
- const { instance, intl } = this.props;
+ const { instance, intl, supportsEmailList } = this.props;
const { params } = this.state;
const isOpen = instance.get('registrations');
const isLoading = this.state.captchaLoading || this.state.submissionLoading;
@@ -232,6 +235,11 @@ class RegistrationForm extends ImmutablePureComponent {
onChange={this.onCheckboxChange}
required
/>
+ {supportsEmailList && }