Single user mode

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
merge-requests/1051/head
marcin mikołajczak 3 years ago
parent ec5e498068
commit 06d33de47f

@ -60,6 +60,8 @@ export const makeDefaultConfig = features => {
}), }),
aboutPages: ImmutableMap(), aboutPages: ImmutableMap(),
authenticatedProfile: true, authenticatedProfile: true,
singleUserMode: false,
singleUserModeProfile: '',
}); });
}; };

@ -6,7 +6,7 @@ import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { IntlProvider } from 'react-intl'; import { IntlProvider } from 'react-intl';
import { Provider, connect } from 'react-redux'; 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 { ScrollContext } from 'react-router-scroll-4';
// import Introduction from '../features/introduction'; // import Introduction from '../features/introduction';
@ -66,6 +66,8 @@ const mapStateToProps = (state) => {
const brandColor = settings.get('demo') ? '#0482d8' : soapboxConfig.get('brandColor'); const brandColor = settings.get('demo') ? '#0482d8' : soapboxConfig.get('brandColor');
const accentColor = settings.get('demo') ? null : soapboxConfig.get('accentColor'); const accentColor = settings.get('demo') ? null : soapboxConfig.get('accentColor');
const singleUserMode = soapboxConfig.get('singleUserMode') && soapboxConfig.get('singleUserModeProfile');
return { return {
showIntroduction, showIntroduction,
me, me,
@ -81,6 +83,7 @@ const mapStateToProps = (state) => {
themeMode: settings.get('themeMode'), themeMode: settings.get('themeMode'),
halloween: settings.get('halloween'), halloween: settings.get('halloween'),
customCss: settings.get('demo') ? null : soapboxConfig.get('customCss'), customCss: settings.get('demo') ? null : soapboxConfig.get('customCss'),
singleUserMode,
}; };
}; };
@ -103,6 +106,7 @@ class SoapboxMount extends React.PureComponent {
customCss: ImmutablePropTypes.list, customCss: ImmutablePropTypes.list,
halloween: PropTypes.bool, halloween: PropTypes.bool,
dispatch: PropTypes.func, dispatch: PropTypes.func,
singleUserMode: PropTypes.string,
}; };
state = { state = {
@ -135,7 +139,7 @@ class SoapboxMount extends React.PureComponent {
} }
render() { render() {
const { me, instanceLoaded, themeCss, locale, customCss } = this.props; const { me, instanceLoaded, themeCss, locale, customCss, singleUserMode } = this.props;
if (me === null) return null; if (me === null) return null;
if (!instanceLoaded) return null; if (!instanceLoaded) return null;
if (this.state.localeLoading) return null; if (this.state.localeLoading) return null;
@ -171,7 +175,9 @@ class SoapboxMount extends React.PureComponent {
</Helmet> </Helmet>
<ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}> <ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
<Switch> <Switch>
{!me && <Route exact path='/' component={PublicLayout} />} {!me && (singleUserMode
? <Redirect exact from='/' to={`/${singleUserMode}`} />
: <Route exact path='/' component={PublicLayout} />)}
<Route exact path='/about/:slug?' component={PublicLayout} /> <Route exact path='/about/:slug?' component={PublicLayout} />
<Route path='/' component={UI} /> <Route path='/' component={UI} />
</Switch> </Switch>

@ -56,6 +56,10 @@ const messages = defineMessages({
promoPanelIconsLink: { id: 'soapbox_config.hints.promo_panel_icons.link', defaultMessage: 'Soapbox Icons List' }, promoPanelIconsLink: { id: 'soapbox_config.hints.promo_panel_icons.link', defaultMessage: 'Soapbox Icons List' },
authenticatedProfileLabel: { id: 'soapbox_config.authenticated_profile_label', defaultMessage: 'Profiles require authentication' }, 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.' }, 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; const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
@ -297,6 +301,22 @@ class SoapboxConfig extends ImmutablePureComponent {
checked={soapbox.get('authenticatedProfile') === true} checked={soapbox.get('authenticatedProfile') === true}
onChange={this.handleChange(['authenticatedProfile'], (e) => e.target.checked)} onChange={this.handleChange(['authenticatedProfile'], (e) => e.target.checked)}
/> />
<Checkbox
name='singleUserMode'
label={intl.formatMessage(messages.singleUserModeLabel)}
hint={intl.formatMessage(messages.singleUserModeHint)}
checked={soapbox.get('singleUserMode') === true}
onChange={this.handleChange(['singleUserMode'], (e) => e.target.checked)}
/>
{soapbox.get('singleUserMode') && (
<TextInput
name='singleUserModeProfile'
label={intl.formatMessage(messages.singleUserModeProfileLabel)}
placeholder={intl.formatMessage(messages.singleUserModeProfileHint)}
value={soapbox.get('singleUserModeProfile')}
onChange={this.handleChange(['singleUserModeProfile'], (e) => e.target.value)}
/>
)}
</FieldsGroup> </FieldsGroup>
<FieldsGroup> <FieldsGroup>
<div className='input with_block_label popup'> <div className='input with_block_label popup'>

@ -3,17 +3,21 @@ import React from 'react';
import { FormattedMessage, injectIntl } from 'react-intl'; import { FormattedMessage, injectIntl } from 'react-intl';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
import SoapboxPropTypes from 'soapbox/utils/soapbox_prop_types'; import SoapboxPropTypes from 'soapbox/utils/soapbox_prop_types';
const mapStateToProps = state => { const mapStateToProps = state => {
const soapboxConfig = getSoapboxConfig(state);
return { return {
siteTitle: state.getIn(['instance', 'title']), siteTitle: state.getIn(['instance', 'title']),
me: state.get('me'), me: state.get('me'),
singleUserMode: soapboxConfig.get('singleUserMode'),
}; };
}; };
const SignUpPanel = ({ siteTitle, me }) => { const SignUpPanel = ({ siteTitle, me, singleUserMode }) => {
if (me) return null; if (me || singleUserMode) return null;
return ( return (
<div className='wtf-panel'> <div className='wtf-panel'>
@ -39,6 +43,7 @@ const SignUpPanel = ({ siteTitle, me }) => {
SignUpPanel.propTypes = { SignUpPanel.propTypes = {
siteTitle: PropTypes.string, siteTitle: PropTypes.string,
me: SoapboxPropTypes.me, me: SoapboxPropTypes.me,
singleUserMode: PropTypes.bool,
}; };
export default injectIntl(connect(mapStateToProps)(SignUpPanel)); export default injectIntl(connect(mapStateToProps)(SignUpPanel));

@ -38,6 +38,7 @@ class TabsBar extends React.PureComponent {
dashboardCount: PropTypes.number, dashboardCount: PropTypes.number,
notificationCount: PropTypes.number, notificationCount: PropTypes.number,
chatsCount: PropTypes.number, chatsCount: PropTypes.number,
singleUserMode: PropTypes.bool,
} }
state = { state = {
@ -67,7 +68,7 @@ class TabsBar extends React.PureComponent {
} }
render() { 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 { collapsed } = this.state;
const showLinks = this.shouldShowLinks(); const showLinks = this.shouldShowLinks();
@ -151,9 +152,11 @@ class TabsBar extends React.PureComponent {
<Link className='tabs-bar__button button' to='/auth/sign_in'> <Link className='tabs-bar__button button' to='/auth/sign_in'>
<FormattedMessage id='account.login' defaultMessage='Log In' /> <FormattedMessage id='account.login' defaultMessage='Log In' />
</Link> </Link>
<Link className='tabs-bar__button button button-alternative-2' to='/'> {!singleUserMode && (
<FormattedMessage id='account.register' defaultMessage='Sign up' /> <Link className='tabs-bar__button button button-alternative-2' to='/'>
</Link> <FormattedMessage id='account.register' defaultMessage='Sign up' />
</Link>
)}
</div> </div>
)} )}
</div> </div>
@ -170,6 +173,7 @@ const mapStateToProps = state => {
const approvalCount = state.getIn(['admin', 'awaitingApproval']).count(); const approvalCount = state.getIn(['admin', 'awaitingApproval']).count();
const instance = state.get('instance'); const instance = state.get('instance');
const settings = getSettings(state); const settings = getSettings(state);
const soapboxConfig = getSoapboxConfig(state);
// In demo mode, use the Soapbox logo // In demo mode, use the Soapbox logo
const logo = settings.get('demo') ? require('images/soapbox-logo.svg') : getSoapboxConfig(state).get('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']), notificationCount: state.getIn(['notifications', 'unread']),
chatsCount: state.getIn(['chats', 'items']).reduce((acc, curr) => acc + Math.min(curr.get('unread', 0), 1), 0), chatsCount: state.getIn(['chats', 'items']).reduce((acc, curr) => acc + Math.min(curr.get('unread', 0), 1), 0),
dashboardCount: reportsCount + approvalCount, dashboardCount: reportsCount + approvalCount,
singleUserMode: soapboxConfig.get('singleUserMode'),
}; };
}; };

@ -7,6 +7,7 @@ import { Link } from 'react-router-dom';
import { remoteInteraction } from 'soapbox/actions/interactions'; import { remoteInteraction } from 'soapbox/actions/interactions';
import snackbar from 'soapbox/actions/snackbar'; import snackbar from 'soapbox/actions/snackbar';
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
import IconButton from 'soapbox/components/icon_button'; import IconButton from 'soapbox/components/icon_button';
import { getFeatures } from 'soapbox/utils/features'; import { getFeatures } from 'soapbox/utils/features';
@ -19,12 +20,14 @@ const messages = defineMessages({
const mapStateToProps = (state, props) => { const mapStateToProps = (state, props) => {
const instance = state.get('instance'); const instance = state.get('instance');
const features = getFeatures(instance); const features = getFeatures(instance);
const soapboxConfig = getSoapboxConfig(state);
if (props.action !== 'FOLLOW') { if (props.action !== 'FOLLOW') {
return { return {
features, features,
siteTitle: state.getIn(['instance', 'title']), siteTitle: state.getIn(['instance', 'title']),
remoteInteractionsAPI: features.remoteInteractionsAPI, remoteInteractionsAPI: features.remoteInteractionsAPI,
singleUserMode: soapboxConfig.get('singleUserMode'),
}; };
} }
@ -35,6 +38,7 @@ const mapStateToProps = (state, props) => {
siteTitle: state.getIn(['instance', 'title']), siteTitle: state.getIn(['instance', 'title']),
userName, userName,
remoteInteractionsAPI: features.remoteInteractionsAPI, remoteInteractionsAPI: features.remoteInteractionsAPI,
singleUserMode: soapboxConfig.get('singleUserMode'),
}; };
}; };
@ -53,6 +57,7 @@ class UnauthorizedModal extends ImmutablePureComponent {
onClose: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired,
onRemoteInteraction: PropTypes.func.isRequired, onRemoteInteraction: PropTypes.func.isRequired,
userName: PropTypes.string, userName: PropTypes.string,
singleUserMode: PropTypes.bool,
}; };
state = { state = {
@ -86,7 +91,7 @@ class UnauthorizedModal extends ImmutablePureComponent {
} }
renderRemoteInteractions() { renderRemoteInteractions() {
const { intl, siteTitle, userName, action } = this.props; const { intl, siteTitle, userName, action, singleUserMode } = this.props;
const { account } = this.state; const { account } = this.state;
let header; let header;
@ -134,10 +139,14 @@ class UnauthorizedModal extends ImmutablePureComponent {
<FormattedMessage id='remote_interaction.divider' defaultMessage='or' /> <FormattedMessage id='remote_interaction.divider' defaultMessage='or' />
</span> </span>
</div> </div>
<h3 className='compose-modal__header__title'><FormattedMessage id='unauthorized_modal.title' defaultMessage='Sign up for {site_title}' values={{ site_title: siteTitle }} /></h3> {!singleUserMode && (
<Link to='/' className='unauthorized-modal-content__button button' onClick={this.onClickClose}> <>
<FormattedMessage id='account.register' defaultMessage='Sign up' /> <h3 className='compose-modal__header__title'><FormattedMessage id='unauthorized_modal.title' defaultMessage='Sign up for {site_title}' values={{ site_title: siteTitle }} /></h3>
</Link> <Link to='/' className='unauthorized-modal-content__button button' onClick={this.onClickClose}>
<FormattedMessage id='account.register' defaultMessage='Sign up' />
</Link>
</>
)}
<Link to='/auth/sign_in' className='unauthorized-modal-content__button button button-secondary' onClick={this.onClickClose}> <Link to='/auth/sign_in' className='unauthorized-modal-content__button button button-secondary' onClick={this.onClickClose}>
<FormattedMessage id='account.login' defaultMessage='Log in' /> <FormattedMessage id='account.login' defaultMessage='Log in' />
</Link> </Link>

Loading…
Cancel
Save