diff --git a/app/soapbox/actions/soapbox.js b/app/soapbox/actions/soapbox.js index cc266aeb3..03814cf7c 100644 --- a/app/soapbox/actions/soapbox.js +++ b/app/soapbox/actions/soapbox.js @@ -60,6 +60,8 @@ export const makeDefaultConfig = features => { }), aboutPages: ImmutableMap(), authenticatedProfile: true, + singleUserMode: false, + singleUserModeProfile: '', }); }; diff --git a/app/soapbox/containers/soapbox.js b/app/soapbox/containers/soapbox.js index c08e643a6..19b218877 100644 --- a/app/soapbox/containers/soapbox.js +++ b/app/soapbox/containers/soapbox.js @@ -6,7 +6,7 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { IntlProvider } from 'react-intl'; import { Provider, connect } from 'react-redux'; -import { Switch, BrowserRouter, Route } from 'react-router-dom'; +import { BrowserRouter, Switch, Redirect, Route } from 'react-router-dom'; import { ScrollContext } from 'react-router-scroll-4'; // import Introduction from '../features/introduction'; @@ -66,6 +66,8 @@ const mapStateToProps = (state) => { const brandColor = settings.get('demo') ? '#0482d8' : soapboxConfig.get('brandColor'); const accentColor = settings.get('demo') ? null : soapboxConfig.get('accentColor'); + const singleUserMode = soapboxConfig.get('singleUserMode') && soapboxConfig.get('singleUserModeProfile'); + return { showIntroduction, me, @@ -81,6 +83,7 @@ const mapStateToProps = (state) => { themeMode: settings.get('themeMode'), halloween: settings.get('halloween'), customCss: settings.get('demo') ? null : soapboxConfig.get('customCss'), + singleUserMode, }; }; @@ -103,6 +106,7 @@ class SoapboxMount extends React.PureComponent { customCss: ImmutablePropTypes.list, halloween: PropTypes.bool, dispatch: PropTypes.func, + singleUserMode: PropTypes.string, }; state = { @@ -135,7 +139,7 @@ class SoapboxMount extends React.PureComponent { } render() { - const { me, instanceLoaded, themeCss, locale, customCss } = this.props; + const { me, instanceLoaded, themeCss, locale, customCss, singleUserMode } = this.props; if (me === null) return null; if (!instanceLoaded) return null; if (this.state.localeLoading) return null; @@ -171,7 +175,9 @@ class SoapboxMount extends React.PureComponent { - {!me && } + {!me && (singleUserMode + ? + : )} diff --git a/app/soapbox/features/soapbox_config/index.js b/app/soapbox/features/soapbox_config/index.js index 1b3fb51d3..84d80eea3 100644 --- a/app/soapbox/features/soapbox_config/index.js +++ b/app/soapbox/features/soapbox_config/index.js @@ -56,6 +56,10 @@ const messages = defineMessages({ promoPanelIconsLink: { id: 'soapbox_config.hints.promo_panel_icons.link', defaultMessage: 'Soapbox Icons List' }, authenticatedProfileLabel: { id: 'soapbox_config.authenticated_profile_label', defaultMessage: 'Profiles require authentication' }, authenticatedProfileHint: { id: 'soapbox_config.authenticated_profile_hint', defaultMessage: 'Users must be logged-in to view replies and media on user profiles.' }, + singleUserModeLabel: { id: 'soapbox_config.single_user_mode_label', defaultMessage: 'Single user mode' }, + singleUserModeHint: { id: 'soapbox_config.single_user_mode_hint', defaultMessage: 'Front page will redirect to a given user profile.' }, + singleUserModeProfileLabel: { id: 'soapbox_config.single_user_mode_profile_label', defaultMessage: 'Main user handle' }, + singleUserModeProfileHint: { id: 'soapbox_config.single_user_mode_profile_hint', defaultMessage: '@handle' }, }); const listenerOptions = supportsPassiveEvents ? { passive: true } : false; @@ -297,6 +301,22 @@ class SoapboxConfig extends ImmutablePureComponent { checked={soapbox.get('authenticatedProfile') === true} onChange={this.handleChange(['authenticatedProfile'], (e) => e.target.checked)} /> + e.target.checked)} + /> + {soapbox.get('singleUserMode') && ( + e.target.value)} + /> + )}
diff --git a/app/soapbox/features/ui/components/sign_up_panel.js b/app/soapbox/features/ui/components/sign_up_panel.js index cee57bb0d..49410affa 100644 --- a/app/soapbox/features/ui/components/sign_up_panel.js +++ b/app/soapbox/features/ui/components/sign_up_panel.js @@ -3,17 +3,21 @@ import React from 'react'; import { FormattedMessage, injectIntl } from 'react-intl'; import { connect } from 'react-redux'; +import { getSoapboxConfig } from 'soapbox/actions/soapbox'; import SoapboxPropTypes from 'soapbox/utils/soapbox_prop_types'; const mapStateToProps = state => { + const soapboxConfig = getSoapboxConfig(state); + return { siteTitle: state.getIn(['instance', 'title']), me: state.get('me'), + singleUserMode: soapboxConfig.get('singleUserMode'), }; }; -const SignUpPanel = ({ siteTitle, me }) => { - if (me) return null; +const SignUpPanel = ({ siteTitle, me, singleUserMode }) => { + if (me || singleUserMode) return null; return (
@@ -39,6 +43,7 @@ const SignUpPanel = ({ siteTitle, me }) => { SignUpPanel.propTypes = { siteTitle: PropTypes.string, me: SoapboxPropTypes.me, + singleUserMode: PropTypes.bool, }; export default injectIntl(connect(mapStateToProps)(SignUpPanel)); diff --git a/app/soapbox/features/ui/components/tabs_bar.js b/app/soapbox/features/ui/components/tabs_bar.js index 70f7fbd26..26731c95f 100644 --- a/app/soapbox/features/ui/components/tabs_bar.js +++ b/app/soapbox/features/ui/components/tabs_bar.js @@ -38,6 +38,7 @@ class TabsBar extends React.PureComponent { dashboardCount: PropTypes.number, notificationCount: PropTypes.number, chatsCount: PropTypes.number, + singleUserMode: PropTypes.bool, } state = { @@ -67,7 +68,7 @@ class TabsBar extends React.PureComponent { } render() { - const { intl, account, logo, onOpenCompose, onOpenSidebar, features, dashboardCount, notificationCount, chatsCount } = this.props; + const { intl, account, logo, onOpenCompose, onOpenSidebar, features, dashboardCount, notificationCount, chatsCount, singleUserMode } = this.props; const { collapsed } = this.state; const showLinks = this.shouldShowLinks(); @@ -151,9 +152,11 @@ class TabsBar extends React.PureComponent { - - - + {!singleUserMode && ( + + + + )}
)}
@@ -170,6 +173,7 @@ const mapStateToProps = state => { const approvalCount = state.getIn(['admin', 'awaitingApproval']).count(); const instance = state.get('instance'); const settings = getSettings(state); + const soapboxConfig = getSoapboxConfig(state); // In demo mode, use the Soapbox logo const logo = settings.get('demo') ? require('images/soapbox-logo.svg') : getSoapboxConfig(state).get('logo'); @@ -181,6 +185,7 @@ const mapStateToProps = state => { notificationCount: state.getIn(['notifications', 'unread']), chatsCount: state.getIn(['chats', 'items']).reduce((acc, curr) => acc + Math.min(curr.get('unread', 0), 1), 0), dashboardCount: reportsCount + approvalCount, + singleUserMode: soapboxConfig.get('singleUserMode'), }; }; diff --git a/app/soapbox/features/ui/components/unauthorized_modal.js b/app/soapbox/features/ui/components/unauthorized_modal.js index be54a6bec..590b73d2d 100644 --- a/app/soapbox/features/ui/components/unauthorized_modal.js +++ b/app/soapbox/features/ui/components/unauthorized_modal.js @@ -7,6 +7,7 @@ import { Link } from 'react-router-dom'; import { remoteInteraction } from 'soapbox/actions/interactions'; import snackbar from 'soapbox/actions/snackbar'; +import { getSoapboxConfig } from 'soapbox/actions/soapbox'; import IconButton from 'soapbox/components/icon_button'; import { getFeatures } from 'soapbox/utils/features'; @@ -19,12 +20,14 @@ const messages = defineMessages({ const mapStateToProps = (state, props) => { const instance = state.get('instance'); const features = getFeatures(instance); + const soapboxConfig = getSoapboxConfig(state); if (props.action !== 'FOLLOW') { return { features, siteTitle: state.getIn(['instance', 'title']), remoteInteractionsAPI: features.remoteInteractionsAPI, + singleUserMode: soapboxConfig.get('singleUserMode'), }; } @@ -35,6 +38,7 @@ const mapStateToProps = (state, props) => { siteTitle: state.getIn(['instance', 'title']), userName, remoteInteractionsAPI: features.remoteInteractionsAPI, + singleUserMode: soapboxConfig.get('singleUserMode'), }; }; @@ -53,6 +57,7 @@ class UnauthorizedModal extends ImmutablePureComponent { onClose: PropTypes.func.isRequired, onRemoteInteraction: PropTypes.func.isRequired, userName: PropTypes.string, + singleUserMode: PropTypes.bool, }; state = { @@ -86,7 +91,7 @@ class UnauthorizedModal extends ImmutablePureComponent { } renderRemoteInteractions() { - const { intl, siteTitle, userName, action } = this.props; + const { intl, siteTitle, userName, action, singleUserMode } = this.props; const { account } = this.state; let header; @@ -134,10 +139,14 @@ class UnauthorizedModal extends ImmutablePureComponent { -

- - - + {!singleUserMode && ( + <> +

+ + + + + )}