v2 Suggestions: add AdminAPI, rearrange sidebars, check instance feature flag

merge-requests/892/head
Alex Gleason 3 years ago
parent e3566db690
commit 92439137c1
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7

@ -62,6 +62,14 @@ export const ADMIN_REMOVE_PERMISSION_GROUP_REQUEST = 'ADMIN_REMOVE_PERMISSION_GR
export const ADMIN_REMOVE_PERMISSION_GROUP_SUCCESS = 'ADMIN_REMOVE_PERMISSION_GROUP_SUCCESS'; export const ADMIN_REMOVE_PERMISSION_GROUP_SUCCESS = 'ADMIN_REMOVE_PERMISSION_GROUP_SUCCESS';
export const ADMIN_REMOVE_PERMISSION_GROUP_FAIL = 'ADMIN_REMOVE_PERMISSION_GROUP_FAIL'; export const ADMIN_REMOVE_PERMISSION_GROUP_FAIL = 'ADMIN_REMOVE_PERMISSION_GROUP_FAIL';
export const ADMIN_USERS_SUGGEST_REQUEST = 'ADMIN_USERS_SUGGEST_REQUEST';
export const ADMIN_USERS_SUGGEST_SUCCESS = 'ADMIN_USERS_SUGGEST_SUCCESS';
export const ADMIN_USERS_SUGGEST_FAIL = 'ADMIN_USERS_SUGGEST_FAIL';
export const ADMIN_USERS_UNSUGGEST_REQUEST = 'ADMIN_USERS_UNSUGGEST_REQUEST';
export const ADMIN_USERS_UNSUGGEST_SUCCESS = 'ADMIN_USERS_UNSUGGEST_SUCCESS';
export const ADMIN_USERS_UNSUGGEST_FAIL = 'ADMIN_USERS_UNSUGGEST_FAIL';
const nicknamesFromIds = (getState, ids) => ids.map(id => getState().getIn(['accounts', id, 'acct'])); const nicknamesFromIds = (getState, ids) => ids.map(id => getState().getIn(['accounts', id, 'acct']));
export function fetchConfig() { export function fetchConfig() {
@ -319,3 +327,31 @@ export function demoteToUser(accountId) {
]); ]);
}; };
} }
export function suggestUsers(accountIds) {
return (dispatch, getState) => {
const nicknames = nicknamesFromIds(getState, accountIds);
dispatch({ type: ADMIN_USERS_SUGGEST_REQUEST, accountIds });
return api(getState)
.patch('/api/pleroma/admin/users/suggest', { nicknames })
.then(({ data: { users } }) => {
dispatch({ type: ADMIN_USERS_SUGGEST_SUCCESS, users, accountIds });
}).catch(error => {
dispatch({ type: ADMIN_USERS_SUGGEST_FAIL, error, accountIds });
});
};
}
export function unsuggestUsers(accountIds) {
return (dispatch, getState) => {
const nicknames = nicknamesFromIds(getState, accountIds);
dispatch({ type: ADMIN_USERS_UNSUGGEST_REQUEST, accountIds });
return api(getState)
.patch('/api/pleroma/admin/users/unsuggest', { nicknames })
.then(({ data: { users } }) => {
dispatch({ type: ADMIN_USERS_UNSUGGEST_SUCCESS, users, accountIds });
}).catch(error => {
dispatch({ type: ADMIN_USERS_UNSUGGEST_FAIL, error, accountIds });
});
};
}

@ -68,6 +68,8 @@ const messages = defineMessages({
demoteToUser: { id: 'admin.users.actions.demote_to_user', defaultMessage: 'Demote @{name} to a regular user' }, demoteToUser: { id: 'admin.users.actions.demote_to_user', defaultMessage: 'Demote @{name} to a regular user' },
subscribe: { id: 'account.subscribe', defaultMessage: 'Subscribe to notifications from @{name}' }, subscribe: { id: 'account.subscribe', defaultMessage: 'Subscribe to notifications from @{name}' },
unsubscribe: { id: 'account.unsubscribe', defaultMessage: 'Unsubscribe to notifications from @{name}' }, unsubscribe: { id: 'account.unsubscribe', defaultMessage: 'Unsubscribe to notifications from @{name}' },
suggestUser: { id: 'admin.users.actions.suggest_user', defaultMessage: 'Suggest @{name}' },
unsuggestUser: { id: 'admin.users.actions.unsuggest_user', defaultMessage: 'Unsuggest @{name}' },
}); });
const mapStateToProps = state => { const mapStateToProps = state => {
@ -405,6 +407,20 @@ class Header extends ImmutablePureComponent {
}); });
} }
if (account.getIn(['pleroma', 'is_suggested'])) {
menu.push({
text: intl.formatMessage(messages.unsuggestUser, { name: account.get('username') }),
action: this.props.onUnsuggestUser,
icon: require('@tabler/icons/icons/user-x.svg'),
});
} else {
menu.push({
text: intl.formatMessage(messages.suggestUser, { name: account.get('username') }),
action: this.props.onSuggestUser,
icon: require('@tabler/icons/icons/user-check.svg'),
});
}
if (account.get('id') !== me) { if (account.get('id') !== me) {
menu.push({ menu.push({
text: intl.formatMessage(messages.deactivateUser, { name: account.get('username') }), text: intl.formatMessage(messages.deactivateUser, { name: account.get('username') }),

@ -117,6 +117,14 @@ export default class Header extends ImmutablePureComponent {
this.props.onDemoteToUser(this.props.account); this.props.onDemoteToUser(this.props.account);
} }
handleSuggestUser = () => {
this.props.onSuggestUser(this.props.account);
}
handleUnsuggestUser = () => {
this.props.onUnsuggestUser(this.props.account);
}
render() { render() {
const { account, identity_proofs } = this.props; const { account, identity_proofs } = this.props;
const moved = (account) ? account.get('moved') : false; const moved = (account) ? account.get('moved') : false;
@ -148,6 +156,8 @@ export default class Header extends ImmutablePureComponent {
onPromoteToAdmin={this.handlePromoteToAdmin} onPromoteToAdmin={this.handlePromoteToAdmin}
onPromoteToModerator={this.handlePromoteToModerator} onPromoteToModerator={this.handlePromoteToModerator}
onDemoteToUser={this.handleDemoteToUser} onDemoteToUser={this.handleDemoteToUser}
onSuggestUser={this.handleSuggestUser}
onUnsuggestUser={this.handleUnsuggestUser}
username={this.props.username} username={this.props.username}
/> />
</div> </div>

@ -32,6 +32,8 @@ import {
promoteToAdmin, promoteToAdmin,
promoteToModerator, promoteToModerator,
demoteToUser, demoteToUser,
suggestUsers,
unsuggestUsers,
} from 'soapbox/actions/admin'; } from 'soapbox/actions/admin';
import { isAdmin } from 'soapbox/utils/accounts'; import { isAdmin } from 'soapbox/utils/accounts';
import snackbar from 'soapbox/actions/snackbar'; import snackbar from 'soapbox/actions/snackbar';
@ -47,7 +49,8 @@ const messages = defineMessages({
promotedToModerator: { id: 'admin.users.actions.promote_to_moderator_message', defaultMessage: '@{acct} was promoted to a moderator' }, promotedToModerator: { id: 'admin.users.actions.promote_to_moderator_message', defaultMessage: '@{acct} was promoted to a moderator' },
demotedToModerator: { id: 'admin.users.actions.demote_to_moderator_message', defaultMessage: '@{acct} was demoted to a moderator' }, demotedToModerator: { id: 'admin.users.actions.demote_to_moderator_message', defaultMessage: '@{acct} was demoted to a moderator' },
demotedToUser: { id: 'admin.users.actions.demote_to_user_message', defaultMessage: '@{acct} was demoted to a regular user' }, demotedToUser: { id: 'admin.users.actions.demote_to_user_message', defaultMessage: '@{acct} was demoted to a regular user' },
userSuggested: { id: 'admin.users.user_suggested_message', defaultMessage: '@{acct} was suggested' },
userUnsuggested: { id: 'admin.users.user_unsuggested_message', defaultMessage: '@{acct} was unsuggested' },
}); });
const makeMapStateToProps = () => { const makeMapStateToProps = () => {
@ -213,6 +216,22 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
.then(() => dispatch(snackbar.success(message))) .then(() => dispatch(snackbar.success(message)))
.catch(() => {}); .catch(() => {});
}, },
onSuggestUser(account) {
const message = intl.formatMessage(messages.userSuggested, { acct: account.get('acct') });
dispatch(suggestUsers([account.get('id')]))
.then(() => dispatch(snackbar.success(message)))
.catch(() => {});
},
onUnsuggestUser(account) {
const message = intl.formatMessage(messages.userUnsuggested, { acct: account.get('acct') });
dispatch(unsuggestUsers([account.get('id')]))
.then(() => dispatch(snackbar.success(message)))
.catch(() => {});
},
}); });
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Header)); export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Header));

@ -62,6 +62,9 @@ class DefaultPage extends ImmutablePureComponent {
{Component => <Component key='sign-up-panel' />} {Component => <Component key='sign-up-panel' />}
</BundleContainer> </BundleContainer>
)} )}
<BundleContainer fetchComponent={PromoPanel}>
{Component => <Component key='promo-panel' />}
</BundleContainer>
{showTrendsPanel && ( {showTrendsPanel && (
<BundleContainer fetchComponent={TrendsPanel}> <BundleContainer fetchComponent={TrendsPanel}>
{Component => <Component limit={3} key='trends-panel' />} {Component => <Component limit={3} key='trends-panel' />}
@ -72,9 +75,6 @@ class DefaultPage extends ImmutablePureComponent {
{Component => <Component limit={5} key='wtf-panel' />} {Component => <Component limit={5} key='wtf-panel' />}
</BundleContainer> </BundleContainer>
)} )}
<BundleContainer fetchComponent={PromoPanel}>
{Component => <Component key='promo-panel' />}
</BundleContainer>
<LinkFooter key='link-footer' /> <LinkFooter key='link-footer' />
</Sticky> </Sticky>
</div> </div>

@ -96,16 +96,6 @@ class HomePage extends ImmutablePureComponent {
{Component => <Component key='sign-up-panel' />} {Component => <Component key='sign-up-panel' />}
</BundleContainer> </BundleContainer>
)} )}
{showTrendsPanel && (
<BundleContainer fetchComponent={TrendsPanel}>
{Component => <Component limit={3} key='trends-panel' />}
</BundleContainer>
)}
{showWhoToFollowPanel && (
<BundleContainer fetchComponent={WhoToFollowPanel}>
{Component => <Component limit={5} key='wtf-panel' />}
</BundleContainer>
)}
<BundleContainer fetchComponent={PromoPanel}> <BundleContainer fetchComponent={PromoPanel}>
{Component => <Component key='promo-panel' />} {Component => <Component key='promo-panel' />}
</BundleContainer> </BundleContainer>
@ -119,6 +109,16 @@ class HomePage extends ImmutablePureComponent {
{Component => <Component limit={cryptoLimit} key='crypto-panel' />} {Component => <Component limit={cryptoLimit} key='crypto-panel' />}
</BundleContainer> </BundleContainer>
)} )}
{showTrendsPanel && (
<BundleContainer fetchComponent={TrendsPanel}>
{Component => <Component limit={3} key='trends-panel' />}
</BundleContainer>
)}
{showWhoToFollowPanel && (
<BundleContainer fetchComponent={WhoToFollowPanel}>
{Component => <Component limit={5} key='wtf-panel' />}
</BundleContainer>
)}
<LinkFooter key='link-footer' /> <LinkFooter key='link-footer' />
</Sticky> </Sticky>
</div> </div>

@ -63,6 +63,9 @@ class StatusPage extends ImmutablePureComponent {
{Component => <Component key='sign-up-panel' />} {Component => <Component key='sign-up-panel' />}
</BundleContainer> </BundleContainer>
)} )}
<BundleContainer fetchComponent={PromoPanel}>
{Component => <Component key='promo-panel' />}
</BundleContainer>
{showTrendsPanel && ( {showTrendsPanel && (
<BundleContainer fetchComponent={TrendsPanel}> <BundleContainer fetchComponent={TrendsPanel}>
{Component => <Component limit={3} key='trends-panel' />} {Component => <Component limit={3} key='trends-panel' />}
@ -73,9 +76,6 @@ class StatusPage extends ImmutablePureComponent {
{Component => <Component limit={5} key='wtf-panel' />} {Component => <Component limit={5} key='wtf-panel' />}
</BundleContainer> </BundleContainer>
)} )}
<BundleContainer fetchComponent={PromoPanel}>
{Component => <Component key='promo-panel' />}
</BundleContainer>
<LinkFooter key='link-footer' /> <LinkFooter key='link-footer' />
</Sticky> </Sticky>
</div> </div>

@ -30,6 +30,10 @@ import {
ADMIN_USERS_DELETE_FAIL, ADMIN_USERS_DELETE_FAIL,
ADMIN_USERS_DEACTIVATE_REQUEST, ADMIN_USERS_DEACTIVATE_REQUEST,
ADMIN_USERS_DEACTIVATE_FAIL, ADMIN_USERS_DEACTIVATE_FAIL,
ADMIN_USERS_SUGGEST_REQUEST,
ADMIN_USERS_SUGGEST_FAIL,
ADMIN_USERS_UNSUGGEST_REQUEST,
ADMIN_USERS_UNSUGGEST_FAIL,
} from 'soapbox/actions/admin'; } from 'soapbox/actions/admin';
const initialState = ImmutableMap(); const initialState = ImmutableMap();
@ -185,6 +189,14 @@ const importAdminUsers = (state, adminUsers) => {
}); });
}; };
const setSuggested = (state, accountIds, isSuggested) => {
return state.withMutations(state => {
accountIds.forEach(id => {
state.setIn([id, 'pleroma', 'is_suggested'], isSuggested);
});
});
};
export default function accounts(state = initialState, action) { export default function accounts(state = initialState, action) {
switch(action.type) { switch(action.type) {
case ACCOUNT_IMPORT: case ACCOUNT_IMPORT:
@ -224,6 +236,12 @@ export default function accounts(state = initialState, action) {
return setActive(state, action.accountIds, true); return setActive(state, action.accountIds, true);
case ADMIN_USERS_FETCH_SUCCESS: case ADMIN_USERS_FETCH_SUCCESS:
return importAdminUsers(state, action.users); return importAdminUsers(state, action.users);
case ADMIN_USERS_SUGGEST_REQUEST:
case ADMIN_USERS_UNSUGGEST_FAIL:
return setSuggested(state, action.accountIds, true);
case ADMIN_USERS_UNSUGGEST_REQUEST:
case ADMIN_USERS_SUGGEST_FAIL:
return setSuggested(state, action.accountIds, false);
default: default:
return state; return state;
} }

@ -24,8 +24,14 @@ export const getFeatures = createSelector([
v.software === MASTODON && gte(v.compatVersion, '2.1.0'), v.software === MASTODON && gte(v.compatVersion, '2.1.0'),
v.software === PLEROMA && gte(v.version, '0.9.9'), v.software === PLEROMA && gte(v.version, '0.9.9'),
]), ]),
suggestions: v.software === MASTODON && gte(v.compatVersion, '2.4.3'), suggestions: any([
suggestionsV2: v.software === MASTODON && gte(v.compatVersion, '3.4.0'), v.software === MASTODON && gte(v.compatVersion, '2.4.3'),
features.includes('v2_suggestions'),
]),
suggestionsV2: any([
v.software === MASTODON && gte(v.compatVersion, '3.4.0'),
features.includes('v2_suggestions'),
]),
trends: v.software === MASTODON && gte(v.compatVersion, '3.0.0'), trends: v.software === MASTODON && gte(v.compatVersion, '3.0.0'),
mediaV2: any([ mediaV2: any([
v.software === MASTODON && gte(v.compatVersion, '3.1.3'), v.software === MASTODON && gte(v.compatVersion, '3.1.3'),

Loading…
Cancel
Save