From 25cdcc8213bb3baa14398702b5d20fbf0595f47c Mon Sep 17 00:00:00 2001 From: Dwayne Harris Date: Sun, 1 Dec 2019 00:12:46 -0500 Subject: [PATCH] WIP --- src/actions/authentication.ts | 6 +- src/actions/composer.ts | 19 +++- src/components/app.tsx | 104 +++++++++++---------- src/components/composer.tsx | 36 ++++++- src/components/controls/checkbox-field.tsx | 2 +- src/components/pages/about.tsx | 2 +- src/components/pages/self.tsx | 18 +++- src/components/pages/view-group.tsx | 7 +- src/components/pages/view-user.tsx | 27 +++--- src/hooks/index.ts | 13 ++- src/styles/app.css | 20 +++- src/types/index.ts | 4 + src/types/store.ts | 1 + 13 files changed, 173 insertions(+), 86 deletions(-) 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 && -