diff --git a/lib/hooks/promote-sidebar.react.js b/lib/hooks/promote-sidebar.react.js index e59a23338..efafd3a52 100644 --- a/lib/hooks/promote-sidebar.react.js +++ b/lib/hooks/promote-sidebar.react.js @@ -1,84 +1,93 @@ // @flow import * as React from 'react'; import { changeThreadSettingsActionTypes, changeThreadSettings, } from '../actions/thread-actions'; import { createLoadingStatusSelector } from '../selectors/loading-selectors'; import { threadInfoSelector } from '../selectors/thread-selectors'; -import { threadHasPermission } from '../shared/thread-utils'; +import { threadHasPermission, threadIsSidebar } from '../shared/thread-utils'; import type { LoadingStatus } from '../types/loading-types'; import { threadTypes, type ThreadInfo, threadPermissions, } from '../types/thread-types'; import { useServerCall, useDispatchActionPromise } from '../utils/action-utils'; import { useSelector } from '../utils/redux-utils'; +function canPromoteSidebar( + sidebarThreadInfo: ThreadInfo, + parentThreadInfo: ?ThreadInfo, +): boolean { + if (!threadIsSidebar(sidebarThreadInfo)) { + return false; + } + + const canChangeThreadType = threadHasPermission( + sidebarThreadInfo, + threadPermissions.EDIT_PERMISSIONS, + ); + const canCreateSubchannelsInParent = threadHasPermission( + parentThreadInfo, + threadPermissions.CREATE_SUBCHANNELS, + ); + return canChangeThreadType && canCreateSubchannelsInParent; +} + type PromoteSidebarType = { +onPromoteSidebar: () => void, +loading: LoadingStatus, +canPromoteSidebar: boolean, }; function usePromoteSidebar( threadInfo: ThreadInfo, onError?: () => mixed, ): PromoteSidebarType { const dispatchActionPromise = useDispatchActionPromise(); const callChangeThreadSettings = useServerCall(changeThreadSettings); const loadingStatusSelector = createLoadingStatusSelector( changeThreadSettingsActionTypes, ); const loadingStatus = useSelector(loadingStatusSelector); const { parentThreadID } = threadInfo; const parentThreadInfo: ?ThreadInfo = useSelector(state => parentThreadID ? threadInfoSelector(state)[parentThreadID] : null, ); - const canChangeThreadType = threadHasPermission( - threadInfo, - threadPermissions.EDIT_PERMISSIONS, - ); - const canCreateSubchannelsInParent = threadHasPermission( - parentThreadInfo, - threadPermissions.CREATE_SUBCHANNELS, - ); - const canPromoteSidebar = - threadInfo.type === threadTypes.SIDEBAR && - canChangeThreadType && - canCreateSubchannelsInParent; + + const canPromote = canPromoteSidebar(threadInfo, parentThreadInfo); const onClick = React.useCallback(() => { try { dispatchActionPromise( changeThreadSettingsActionTypes, (async () => { return await callChangeThreadSettings({ threadID: threadInfo.id, changes: { type: threadTypes.COMMUNITY_OPEN_SUBTHREAD }, }); })(), ); } catch (e) { onError?.(); throw e; } }, [threadInfo.id, callChangeThreadSettings, dispatchActionPromise, onError]); const returnValues = React.useMemo( () => ({ onPromoteSidebar: onClick, loading: loadingStatus, - canPromoteSidebar, + canPromoteSidebar: canPromote, }), - [onClick, loadingStatus, canPromoteSidebar], + [onClick, loadingStatus, canPromote], ); return returnValues; } -export { usePromoteSidebar }; +export { usePromoteSidebar, canPromoteSidebar }; diff --git a/web/modals/threads/notifications/notifications-modal.css b/web/modals/threads/notifications/notifications-modal.css index e0522e224..cf1f87173 100644 --- a/web/modals/threads/notifications/notifications-modal.css +++ b/web/modals/threads/notifications/notifications-modal.css @@ -1,14 +1,21 @@ div.container { display: flex; flex-direction: column; color: var(--fg); margin: 24px 32px; - row-gap: 40px; + row-gap: 20px; min-width: 343px; + width: min-content; +} + +p.notice { + text-align: center; + font-size: var(--xs-font-12); + color: var(--notification-settings-option-color); } div.optionsContainer { display: flex; flex-direction: column; row-gap: 12px; } diff --git a/web/modals/threads/notifications/notifications-modal.react.js b/web/modals/threads/notifications/notifications-modal.react.js index 7737f2cd8..034a2a918 100644 --- a/web/modals/threads/notifications/notifications-modal.react.js +++ b/web/modals/threads/notifications/notifications-modal.react.js @@ -1,215 +1,271 @@ // @flow import * as React from 'react'; import { updateSubscription, updateSubscriptionActionTypes, } from 'lib/actions/user-actions'; +import { canPromoteSidebar } from 'lib/hooks/promote-sidebar.react'; import { threadInfoSelector } from 'lib/selectors/thread-selectors'; import { threadIsSidebar } from 'lib/shared/thread-utils'; import { useServerCall, useDispatchActionPromise, } from 'lib/utils/action-utils'; import AllNotifsIllustration from '../../../assets/all-notifs.react.js'; import BadgeNotifsIllustration from '../../../assets/badge-notifs.react.js'; import MutedNotifsIllustration from '../../../assets/muted-notifs.react.js'; import Button from '../../../components/button.react'; import EnumSettingsOption from '../../../components/enum-settings-option.react'; import { useSelector } from '../../../redux/redux-utils'; import Modal from '../../modal.react'; import css from './notifications-modal.css'; type NotificationSettings = 'focused' | 'badge-only' | 'background'; const BANNER_NOTIFS = 'Banner notifs'; const BADGE_COUNT = 'Badge count'; const IN_FOCUSED_TAB = 'Lives in Focused tab'; const IN_BACKGROUND_TAB = 'Lives in Background tab'; const focusedStatements = [ { statement: BANNER_NOTIFS, isStatementValid: true, styleStatementBasedOnValidity: true, }, { statement: BADGE_COUNT, isStatementValid: true, styleStatementBasedOnValidity: true, }, { statement: IN_FOCUSED_TAB, isStatementValid: true, styleStatementBasedOnValidity: true, }, ]; const badgeOnlyStatements = [ { statement: BANNER_NOTIFS, isStatementValid: false, styleStatementBasedOnValidity: true, }, { statement: BADGE_COUNT, isStatementValid: true, styleStatementBasedOnValidity: true, }, { statement: IN_FOCUSED_TAB, isStatementValid: true, styleStatementBasedOnValidity: true, }, ]; const backgroundStatements = [ { statement: BANNER_NOTIFS, isStatementValid: false, styleStatementBasedOnValidity: true, }, { statement: BADGE_COUNT, isStatementValid: false, styleStatementBasedOnValidity: true, }, { statement: IN_BACKGROUND_TAB, isStatementValid: true, styleStatementBasedOnValidity: true, }, ]; type Props = { +threadID: string, +onClose: () => void, }; function NotificationsModal(props: Props): React.Node { const { onClose, threadID } = props; const threadInfo = useSelector(state => threadInfoSelector(state)[threadID]); const { subscription } = threadInfo.currentUser; + const { parentThreadID } = threadInfo; + const parentThreadInfo = useSelector(state => + parentThreadID ? threadInfoSelector(state)[parentThreadID] : null, + ); const isSidebar = threadIsSidebar(threadInfo); const initialThreadSetting = React.useMemo(() => { if (!subscription.home) { return 'background'; } if (!subscription.pushNotifs) { return 'badge-only'; } return 'focused'; }, [subscription.home, subscription.pushNotifs]); const [ notificationSettings, setNotificationSettings, ] = React.useState(initialThreadSetting); const onFocusedSelected = React.useCallback( () => setNotificationSettings('focused'), [], ); const onBadgeOnlySelected = React.useCallback( () => setNotificationSettings('badge-only'), [], ); const onBackgroundSelected = React.useCallback( () => setNotificationSettings('background'), [], ); const isFocusedSelected = notificationSettings === 'focused'; const focusedItem = React.useMemo(() => { const icon = ; return ( ); }, [isFocusedSelected, onFocusedSelected]); const isFocusedBadgeOnlySelected = notificationSettings === 'badge-only'; const focusedBadgeOnlyItem = React.useMemo(() => { const icon = ; return ( ); }, [isFocusedBadgeOnlySelected, onBadgeOnlySelected]); const isBackgroundSelected = notificationSettings === 'background'; const backgroundItem = React.useMemo(() => { const icon = ; return ( ); }, [isBackgroundSelected, onBackgroundSelected, isSidebar]); const dispatchActionPromise = useDispatchActionPromise(); const callUpdateSubscription = useServerCall(updateSubscription); const onClickSave = React.useCallback(() => { dispatchActionPromise( updateSubscriptionActionTypes, callUpdateSubscription({ threadID: threadID, updatedFields: { home: notificationSettings !== 'background', pushNotifs: notificationSettings === 'focused', }, }), ); onClose(); }, [ callUpdateSubscription, dispatchActionPromise, notificationSettings, onClose, threadID, ]); const modalName = isSidebar ? 'Thread notifications' : 'Channel notifications'; - return ( - -
+ let modalContent; + if (isSidebar && !parentThreadInfo?.currentUser.subscription.home) { + modalContent = ( + <> +

+ {'It’s not possible to change the notif settings for a thread ' + + 'whose parent is in Background. That’s because Comm’s design ' + + 'always shows threads underneath their parent in the Inbox, ' + + 'which means that if a thread’s parent is in Background, the ' + + 'thread must also be there.'} +

+

+ {canPromoteSidebar(threadInfo, parentThreadInfo) + ? 'If you want to change the notif settings for this thread, ' + + 'you can either change the notif settings for the parent, ' + + 'or you can promote the thread to a channel.' + : 'If you want to change the notif settings for this thread, ' + + 'you’ll have to change the notif settings for the parent.'} +

+ + ); + } else { + let noticeText = null; + if (isSidebar) { + noticeText = ( + <> +

+ {'It’s not possible to move this thread to Background. ' + + 'That’s because Comm’s design always shows threads ' + + 'underneath their parent in the Inbox, which means ' + + 'that if a thread’s parent is in Focused, the thread ' + + 'must also be there.'} +

+

+ {canPromoteSidebar(threadInfo, parentThreadInfo) + ? 'If you want to move this thread to Background, ' + + 'you can either move the parent to Background, ' + + 'or you can promote the thread to a channel.' + : 'If you want to move this thread to Background, ' + + 'you’ll have to move the parent to Background.'} +

+ + ); + } + + modalContent = ( + <>
{focusedItem} {focusedBadgeOnlyItem} {backgroundItem}
-
+ {noticeText} + + ); + } + + return ( + +
{modalContent}
); } export default NotificationsModal;