diff --git a/src/actions/authentication.ts b/src/actions/authentication.ts
index c427c37..f186e95 100644
--- a/src/actions/authentication.ts
+++ b/src/actions/authentication.ts
@@ -11,7 +11,7 @@ import {
LOCAL_STORAGE_ACCESS_TOKEN_AT_KEY,
} from 'src/constants'
-import { AppThunkAction, Entity, RequestKey, EntityType } from 'src/types'
+import { AppThunkAction, Entity, RequestKey, EntityType, Settings } from 'src/types'
export interface SetCheckedAction extends Action {
type: 'AUTHENTICATION_SET_CHECKED'
@@ -115,10 +115,11 @@ interface UpdateSelfOptions {
imageUrl: string
coverImageUrl: string
theme: string
+ settings: Settings
}
export const updateSelf = (options: UpdateSelfOptions): AppThunkAction => async dispatch => {
- const { name, about, requiresApproval, privacy, imageUrl, coverImageUrl, theme } = options
+ const { name, about, requiresApproval, privacy, imageUrl, coverImageUrl, theme, settings } = options
dispatch(startRequest(RequestKey.UpdateSelf))
try {
@@ -133,6 +134,7 @@ export const updateSelf = (options: UpdateSelfOptions): AppThunkAction => async
imageUrl,
coverImageUrl,
theme,
+ settings,
},
})
diff --git a/src/actions/composer.ts b/src/actions/composer.ts
index 2017f0f..77c82b4 100644
--- a/src/actions/composer.ts
+++ b/src/actions/composer.ts
@@ -4,7 +4,7 @@ import { setEntities } from 'src/actions/entities'
import { startRequest, finishRequest } from 'src/actions/requests'
import { apiFetch } from 'src/api'
import { normalize } from 'src/utils/normalization'
-import { AppThunkAction, Installation, RequestKey, EntityType } from 'src/types'
+import { AppThunkAction, Installation, RequestKey, EntityType, Settings } from 'src/types'
export interface SetInstallationsAction extends Action {
type: 'COMPOSER_SET_INSTALLATIONS'
@@ -70,3 +70,20 @@ export const fetchInstallations = (): AppThunkAction => async dispatch => {
throw err
}
}
+
+export const saveInstallationSettings = (id: string, settings: Settings): AppThunkAction => async dispatch => {
+ dispatch(startRequest(RequestKey.UpdateInstallationSettings))
+
+ try {
+ await apiFetch({
+ path: `/api/installation/${id}/settings`,
+ method: 'put',
+ body: { settings },
+ })
+
+ dispatch(finishRequest(RequestKey.UpdateInstallationSettings, true))
+ } catch (err) {
+ dispatch(finishRequest(RequestKey.UpdateInstallationSettings, false))
+ throw err
+ }
+}
diff --git a/src/components/app.tsx b/src/components/app.tsx
index e73c87d..4d32032 100644
--- a/src/components/app.tsx
+++ b/src/components/app.tsx
@@ -81,57 +81,59 @@ const App: FC = () => {
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/composer.tsx b/src/components/composer.tsx
index 9896495..830ff26 100644
--- a/src/components/composer.tsx
+++ b/src/components/composer.tsx
@@ -3,7 +3,13 @@ import { useSelector, useDispatch } from 'react-redux'
import { getOrigin } from 'src/utils'
import { useConfig, useDeepCompareEffect, useTheme } from 'src/hooks'
-import { fetchInstallations, setSelectedInstallation, setHeight as setComposerHeight, setError as setComposerError } from 'src/actions/composer'
+import {
+ fetchInstallations,
+ setSelectedInstallation,
+ setHeight as setComposerHeight,
+ setError as setComposerError,
+ saveInstallationSettings,
+} from 'src/actions/composer'
import { showNotification } from 'src/actions/notifications'
import { createPost } from 'src/actions/posts'
import { getInstallations, getSelectedInstallation, getError, getHeight as getComposerHeight } from 'src/selectors/composer'
@@ -120,6 +126,28 @@ const Composer: FC
= ({ parent, onPost }) => {
content: {},
})
+ break
+ case 'saveSettings':
+ withRateLimit(async ({ name, content }) => {
+ try {
+ await dispatch(saveInstallationSettings(installation.id, content.settings))
+
+ postMessage({
+ name,
+ content: {
+ settings: content.settings,
+ },
+ })
+ } catch (error) {
+ postMessage({
+ name,
+ error,
+ })
+
+ dispatch(showNotification(NotificationType.Error, `Error saving settings: ${error.message}`))
+ }
+ }, data, 2000)
+
break
case 'post':
withRateLimit(async ({ name, content }) => {
@@ -143,13 +171,13 @@ const Composer: FC = ({ parent, onPost }) => {
dispatch(showNotification(NotificationType.Success, `Posted!`))
if (onPost) onPost()
- } catch (err) {
+ } catch (error) {
postMessage({
name,
- error: err,
+ error,
})
- dispatch(showNotification(NotificationType.Error, `Error posting: ${err.message}`))
+ dispatch(showNotification(NotificationType.Error, `Error posting: ${error.message}`))
}
}, data, 2000)
diff --git a/src/components/controls/checkbox-field.tsx b/src/components/controls/checkbox-field.tsx
index 6026fe8..af33dd4 100644
--- a/src/components/controls/checkbox-field.tsx
+++ b/src/components/controls/checkbox-field.tsx
@@ -16,7 +16,7 @@ const CheckboxField: FC = ({ name, children }) => {
return (
diff --git a/src/components/pages/about.tsx b/src/components/pages/about.tsx
index 2723c80..bf522d5 100644
--- a/src/components/pages/about.tsx
+++ b/src/components/pages/about.tsx
@@ -25,7 +25,7 @@ const About: FC = () => {
Flexor is made up of Communities. Each account is created through one. Communities enforce their own standards of behavior.
- Check out the list of Communities.
+ Check out the list of Communities.
Apps
diff --git a/src/components/pages/self.tsx b/src/components/pages/self.tsx
index 771ae35..15bbcdb 100644
--- a/src/components/pages/self.tsx
+++ b/src/components/pages/self.tsx
@@ -1,6 +1,6 @@
import React, { FC } from 'react'
import { useSelector, useDispatch } from 'react-redux'
-import { Link, useHistory } from 'react-router-dom'
+import { useHistory } from 'react-router-dom'
import { faDoorOpen, faCheckCircle, faIdCard, faEnvelope, faUserShield, faUserCircle } from '@fortawesome/free-solid-svg-icons'
import { unauthenticate, updateSelf } from 'src/actions/authentication'
@@ -10,7 +10,7 @@ import { getForm } from 'src/selectors/forms'
import { handleApiError } from 'src/api/errors'
import { PRIVACY_OPTIONS } from 'src/constants'
-import { useAuthenticationCheck, useDeepCompareEffect, useTheme } from 'src/hooks'
+import { useAuthenticationCheck, useDeepCompareEffect } from 'src/hooks'
import { setTitle, valueFromForm } from 'src/utils'
import { AppState, User, Form } from 'src/types'
@@ -31,7 +31,6 @@ import ThemeField from 'src/components/controls/theme-field'
import StaticField from 'src/components/controls/static-field'
const Self: FC = () => {
- const theme = useTheme()
const dispatch = useDispatch()
const history = useHistory()
@@ -47,6 +46,8 @@ const Self: FC = () => {
}
const handleUpdate = () => {
+ if (!user) return
+ const settings = user.settings ?? {}
const name = valueFromForm(form, 'name', '')
const about = valueFromForm(form, 'about', '')
const requiresApproval = valueFromForm(form, 'requiresApproval', true)
@@ -54,6 +55,7 @@ const Self: FC = () => {
const imageUrl = valueFromForm(form, 'image', '')
const coverImageUrl = valueFromForm(form, 'coverImage', '')
const theme = valueFromForm(form, 'theme', '')
+ const allowThemeChange = valueFromForm(form, 'allowThemeChange', true)
try {
dispatch(updateSelf({
@@ -64,6 +66,10 @@ const Self: FC = () => {
imageUrl,
coverImageUrl,
theme,
+ settings: {
+ ...settings,
+ allowThemeChange,
+ }
}))
} catch (err) {
handleApiError(err, dispatch, history)
@@ -82,6 +88,7 @@ const Self: FC = () => {
dispatch(initField('image', user.imageUrl || ''))
dispatch(initField('coverImage', user.coverImageUrl || ''))
dispatch(initField('theme', user.theme))
+ dispatch(initField('allowThemeChange', user.settings.allowThemeChange))
}
}, [user])
@@ -108,6 +115,11 @@ const Self: FC = () => {
Approve each Subscription request from other users.
+
+
+
+ Allow theme changes on User and Community pages.
+
diff --git a/src/components/pages/view-group.tsx b/src/components/pages/view-group.tsx
index 9710acd..b63cb42 100644
--- a/src/components/pages/view-group.tsx
+++ b/src/components/pages/view-group.tsx
@@ -10,7 +10,7 @@ import { getAuthenticated } from 'src/selectors/authentication'
import { getEntity } from 'src/selectors/entities'
import { getThemeName } from 'src/selectors/theme'
-import { useDeepCompareEffect, useConfig, useTheme } from 'src/hooks'
+import { useDeepCompareEffect, useConfig, useTheme, useSetting } from 'src/hooks'
import { setTitle, urlForBlob } from 'src/utils'
import { AppState, EntityType, Group, GroupMembershipType, AppThunkDispatch, LevelItem } from 'src/types'
@@ -37,6 +37,7 @@ const ViewGroup: FC = () => {
const dispatch = useDispatch()
const config = useConfig()
const history = useHistory()
+ const allowThemeChange = useSetting('allowThemeChange', true)
useEffect(() => {
try {
@@ -49,11 +50,11 @@ const ViewGroup: FC = () => {
useDeepCompareEffect(() => {
if (group) {
setTitle(group.name)
- dispatch(setTheme(group.theme))
+ if (allowThemeChange) dispatch(setTheme(group.theme))
}
return () => {
- dispatch(setTheme(selectedThemeName))
+ if (allowThemeChange) dispatch(setTheme(selectedThemeName))
}
}, [group])
diff --git a/src/components/pages/view-user.tsx b/src/components/pages/view-user.tsx
index 9730493..e751acf 100644
--- a/src/components/pages/view-user.tsx
+++ b/src/components/pages/view-user.tsx
@@ -14,7 +14,7 @@ import { getAuthenticatedUser, getChecked } from 'src/selectors/authentication'
import { getUserPosts } from 'src/selectors/posts'
import { getThemeName } from 'src/selectors/theme'
-import { useDeepCompareEffect, useConfig, useTheme } from 'src/hooks'
+import { useDeepCompareEffect, useConfig, useTheme, useSetting } from 'src/hooks'
import { setTitle, urlForBlob } from 'src/utils'
import { AppState, Theme, EntityType, User, Post, AppThunkDispatch, LevelItem } from 'src/types'
@@ -42,6 +42,7 @@ const ViewUser: FC = () => {
const dispatch = useDispatch()
const config = useConfig()
const history = useHistory()
+ const allowThemeChange = useSetting('allowThemeChange', true)
useEffect(() => {
const init = async () => {
@@ -59,11 +60,11 @@ const ViewUser: FC = () => {
useDeepCompareEffect(() => {
if (user) {
setTitle(user.name)
- dispatch(setTheme(user.theme))
+ if (allowThemeChange && user.theme) dispatch(setTheme(user.theme))
}
return () => {
- if (selectedThemeName) dispatch(setTheme(selectedThemeName))
+ if (allowThemeChange && selectedThemeName) dispatch(setTheme(selectedThemeName))
}
}, [user])
@@ -121,8 +122,8 @@ const ViewUser: FC = () => {
{subscribed &&
-