From 6f0e398a78ac80c580358ee8360c766060367c73 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 11 Jan 2023 19:01:45 -0600 Subject: [PATCH] Remove singleUserMode, upgrade to redirectRootNoLogin --- app/soapbox/containers/soapbox.tsx | 6 +-- app/soapbox/features/soapbox-config/index.tsx | 25 ----------- .../components/__tests__/cta-banner.test.tsx | 7 +-- .../features/ui/components/cta-banner.tsx | 4 +- .../components/modals/unauthorized-modal.tsx | 7 ++- app/soapbox/features/ui/components/navbar.tsx | 7 ++- .../ui/components/panels/sign-up-panel.tsx | 5 +-- .../soapbox/__tests__/soapbox-config.test.ts | 11 +++++ .../normalizers/soapbox/soapbox-config.ts | 45 +++++++++++++++++-- 9 files changed, 70 insertions(+), 47 deletions(-) diff --git a/app/soapbox/containers/soapbox.tsx b/app/soapbox/containers/soapbox.tsx index b9603b3b1..d6c17db5f 100644 --- a/app/soapbox/containers/soapbox.tsx +++ b/app/soapbox/containers/soapbox.tsx @@ -96,7 +96,7 @@ const SoapboxMount = () => { const waitlisted = account && !account.source.get('approved', true); const needsOnboarding = useAppSelector(state => state.onboarding.needsOnboarding); const showOnboarding = account && !waitlisted && needsOnboarding; - const singleUserMode = soapboxConfig.singleUserMode && soapboxConfig.singleUserModeProfile; + const { redirectRootNoLogin } = soapboxConfig; const pepeEnabled = soapboxConfig.getIn(['extensions', 'pepe', 'enabled']) === true; @@ -134,8 +134,8 @@ const SoapboxMount = () => { /> )} - {!me && (singleUserMode - ? + {!me && (redirectRootNoLogin + ? : )} {!me && ( diff --git a/app/soapbox/features/soapbox-config/index.tsx b/app/soapbox/features/soapbox-config/index.tsx index 8484863b6..761c34d59 100644 --- a/app/soapbox/features/soapbox-config/index.tsx +++ b/app/soapbox/features/soapbox-config/index.tsx @@ -47,10 +47,6 @@ const messages = defineMessages({ 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.' }, displayCtaLabel: { id: 'soapbox_config.cta_label', defaultMessage: 'Display call to action panels if not authenticated' }, - 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' }, mediaPreviewLabel: { id: 'soapbox_config.media_preview_label', defaultMessage: 'Prefer preview media for thumbnails' }, mediaPreviewHint: { id: 'soapbox_config.media_preview_hint', defaultMessage: 'Some backends provide an optimized version of media for display in timelines. However, these preview images may be too small without additional configuration.' }, feedInjectionLabel: { id: 'soapbox_config.feed_injection_label', defaultMessage: 'Feed injection' }, @@ -283,27 +279,6 @@ const SoapboxConfig: React.FC = () => { /> - - e.target.checked)} - /> - - - {soapbox.get('singleUserMode') && ( - - e.target.value)} - /> - - )} - ', () => { }); }); - describe('with singleUserMode enabled', () => { + describe('with registrations closed', () => { it('renders empty', () => { - const store = { soapbox: ImmutableMap({ singleUserMode: true }) }; + const store = { instance: normalizeInstance({ registrations: false }) }; render(, undefined, store); expect(screen.queryAllByTestId('cta-banner')).toHaveLength(0); diff --git a/app/soapbox/features/ui/components/cta-banner.tsx b/app/soapbox/features/ui/components/cta-banner.tsx index 87860b318..5e06b207c 100644 --- a/app/soapbox/features/ui/components/cta-banner.tsx +++ b/app/soapbox/features/ui/components/cta-banner.tsx @@ -6,10 +6,10 @@ import { useAppSelector, useInstance, useSoapboxConfig } from 'soapbox/hooks'; const CtaBanner = () => { const instance = useInstance(); - const { displayCta, singleUserMode } = useSoapboxConfig(); + const { displayCta } = useSoapboxConfig(); const me = useAppSelector((state) => state.me); - if (me || !displayCta || singleUserMode) return null; + if (me || !displayCta || !instance.registrations) return null; return (
diff --git a/app/soapbox/features/ui/components/modals/unauthorized-modal.tsx b/app/soapbox/features/ui/components/modals/unauthorized-modal.tsx index 9a7906e87..809abcbda 100644 --- a/app/soapbox/features/ui/components/modals/unauthorized-modal.tsx +++ b/app/soapbox/features/ui/components/modals/unauthorized-modal.tsx @@ -4,7 +4,7 @@ import { useHistory } from 'react-router-dom'; import { remoteInteraction } from 'soapbox/actions/interactions'; import { Button, Modal, Stack, Text } from 'soapbox/components/ui'; -import { useAppSelector, useAppDispatch, useFeatures, useSoapboxConfig, useInstance } from 'soapbox/hooks'; +import { useAppSelector, useAppDispatch, useFeatures, useInstance } from 'soapbox/hooks'; import toast from 'soapbox/toast'; const messages = defineMessages({ @@ -31,7 +31,6 @@ const UnauthorizedModal: React.FC = ({ action, onClose, acco const dispatch = useAppDispatch(); const instance = useInstance(); - const { singleUserMode } = useSoapboxConfig(); const username = useAppSelector(state => state.accounts.get(accountId)?.display_name); const features = useFeatures(); @@ -98,7 +97,7 @@ const UnauthorizedModal: React.FC = ({ action, onClose, acco } secondaryAction={onRegister} secondaryText={} @@ -122,7 +121,7 @@ const UnauthorizedModal: React.FC = ({ action, onClose, acco
- {!singleUserMode && ( + {instance.registrations && ( diff --git a/app/soapbox/features/ui/components/navbar.tsx b/app/soapbox/features/ui/components/navbar.tsx index d9829afca..bbb357bfa 100644 --- a/app/soapbox/features/ui/components/navbar.tsx +++ b/app/soapbox/features/ui/components/navbar.tsx @@ -9,7 +9,7 @@ import { openSidebar } from 'soapbox/actions/sidebar'; import SiteLogo from 'soapbox/components/site-logo'; import { Avatar, Button, Form, HStack, IconButton, Input, Tooltip } from 'soapbox/components/ui'; import Search from 'soapbox/features/compose/components/search'; -import { useAppDispatch, useOwnAccount, useSoapboxConfig } from 'soapbox/hooks'; +import { useAppDispatch, useInstance, useOwnAccount } from 'soapbox/hooks'; import ProfileDropdown from './profile-dropdown'; @@ -29,8 +29,7 @@ const Navbar = () => { const node = useRef(null); const account = useOwnAccount(); - const soapboxConfig = useSoapboxConfig(); - const singleUserMode = soapboxConfig.get('singleUserMode'); + const instance = useInstance(); const [isLoading, setLoading] = useState(false); const [username, setUsername] = useState(''); @@ -151,7 +150,7 @@ const Navbar = () => { - {!singleUserMode && ( + {!instance.registrations && ( diff --git a/app/soapbox/features/ui/components/panels/sign-up-panel.tsx b/app/soapbox/features/ui/components/panels/sign-up-panel.tsx index 318080770..117a8a8b9 100644 --- a/app/soapbox/features/ui/components/panels/sign-up-panel.tsx +++ b/app/soapbox/features/ui/components/panels/sign-up-panel.tsx @@ -2,14 +2,13 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; import { Button, Stack, Text } from 'soapbox/components/ui'; -import { useAppSelector, useInstance, useSoapboxConfig } from 'soapbox/hooks'; +import { useAppSelector, useInstance } from 'soapbox/hooks'; const SignUpPanel = () => { const instance = useInstance(); - const { singleUserMode } = useSoapboxConfig(); const me = useAppSelector((state) => state.me); - if (me || singleUserMode) return null; + if (me || !instance.registrations) return null; return ( diff --git a/app/soapbox/normalizers/soapbox/__tests__/soapbox-config.test.ts b/app/soapbox/normalizers/soapbox/__tests__/soapbox-config.test.ts index 9b70eba36..5eccab870 100644 --- a/app/soapbox/normalizers/soapbox/__tests__/soapbox-config.test.ts +++ b/app/soapbox/normalizers/soapbox/__tests__/soapbox-config.test.ts @@ -34,4 +34,15 @@ describe('normalizeSoapboxConfig()', () => { expect(ImmutableRecord.isRecord(result.promoPanel.items.get(0))).toBe(true); expect(result.promoPanel.items.get(2)?.icon).toBe('question-circle'); }); + + it('upgrades singleUserModeProfile to redirectRootNoLogin', () => { + expect(normalizeSoapboxConfig({ singleUserMode: true, singleUserModeProfile: 'alex' }).redirectRootNoLogin).toBe('/@alex'); + expect(normalizeSoapboxConfig({ singleUserMode: false, singleUserModeProfile: 'alex' }).redirectRootNoLogin).toBe(''); + }); + + it('normalizes redirectRootNoLogin', () => { + expect(normalizeSoapboxConfig({ redirectRootNoLogin: 'benis' }).redirectRootNoLogin).toBe('/benis'); + expect(normalizeSoapboxConfig({ redirectRootNoLogin: '/benis' }).redirectRootNoLogin).toBe('/benis'); + expect(normalizeSoapboxConfig({ redirectRootNoLogin: '/' }).redirectRootNoLogin).toBe(''); + }); }); diff --git a/app/soapbox/normalizers/soapbox/soapbox-config.ts b/app/soapbox/normalizers/soapbox/soapbox-config.ts index e899e1e80..f2366bdf1 100644 --- a/app/soapbox/normalizers/soapbox/soapbox-config.ts +++ b/app/soapbox/normalizers/soapbox/soapbox-config.ts @@ -106,8 +106,6 @@ export const SoapboxConfigRecord = ImmutableRecord({ }), aboutPages: ImmutableMap>(), authenticatedProfile: true, - singleUserMode: false, - singleUserModeProfile: '', linkFooterMessage: '', links: ImmutableMap(), displayCta: true, @@ -115,7 +113,7 @@ export const SoapboxConfigRecord = ImmutableRecord({ feedInjection: true, tileServer: '', tileServerAttribution: '', - redirectRootNoLogin: '/', + redirectRootNoLogin: '', /** * Whether to use the preview URL for media thumbnails. * On some platforms this can be too blurry without additional configuration. @@ -198,6 +196,45 @@ const normalizeAdsAlgorithm = (soapboxConfig: SoapboxConfigMap): SoapboxConfigMa } }; +/** Single user mode is now managed by `redirectRootNoLogin`. */ +const upgradeSingleUserMode = (soapboxConfig: SoapboxConfigMap): SoapboxConfigMap => { + const singleUserMode = soapboxConfig.get('singleUserMode'); + const singleUserModeProfile = soapboxConfig.get('singleUserModeProfile'); + const redirectRootNoLogin = soapboxConfig.get('redirectRootNoLogin'); + + if (!redirectRootNoLogin && singleUserMode && singleUserModeProfile) { + return soapboxConfig + .set('redirectRootNoLogin', `/@${singleUserModeProfile}`) + .deleteAll(['singleUserMode', 'singleUserModeProfile']); + } else { + return soapboxConfig + .deleteAll(['singleUserMode', 'singleUserModeProfile']); + } +}; + +/** Ensure a valid path is used. */ +const normalizeRedirectRootNoLogin = (soapboxConfig: SoapboxConfigMap): SoapboxConfigMap => { + const redirectRootNoLogin = soapboxConfig.get('redirectRootNoLogin'); + + if (!redirectRootNoLogin) return soapboxConfig; + + try { + // Basically just get the pathname with a leading slash. + const normalized = new URL(redirectRootNoLogin, 'a://').pathname; + + if (normalized !== '/') { + return soapboxConfig.set('redirectRootNoLogin', normalized); + } else { + // Prevent infinite redirect(?) + return soapboxConfig.delete('redirectRootNoLogin'); + } + } catch (e) { + console.error('You have configured an invalid redirect in Soapbox Config.'); + console.error(e); + return soapboxConfig.delete('redirectRootNoLogin'); + } +}; + export const normalizeSoapboxConfig = (soapboxConfig: Record) => { return SoapboxConfigRecord( ImmutableMap(fromJS(soapboxConfig)).withMutations(soapboxConfig => { @@ -210,6 +247,8 @@ export const normalizeSoapboxConfig = (soapboxConfig: Record) => { normalizeCryptoAddresses(soapboxConfig); normalizeAds(soapboxConfig); normalizeAdsAlgorithm(soapboxConfig); + upgradeSingleUserMode(soapboxConfig); + normalizeRedirectRootNoLogin(soapboxConfig); }), ); };