diff --git a/web/components/enum-settings-option-info.react.js b/web/components/enum-settings-option-info.react.js index 1807e7328..d36a464b3 100644 --- a/web/components/enum-settings-option-info.react.js +++ b/web/components/enum-settings-option-info.react.js @@ -1,40 +1,49 @@ // @flow import classnames from 'classnames'; import * as React from 'react'; import SWMansionIcon from '../SWMansionIcon.react'; import css from './enum-settings-option-info.css'; type Props = { +optionSelected: boolean, +valid: boolean, + +styleStatementBasedOnValidity: boolean, +children: React.Node, }; function EnumSettingsOptionInfo(props: Props): React.Node { - const { valid, children, optionSelected } = props; + const { + optionSelected, + valid, + styleStatementBasedOnValidity, + children, + } = props; const optionInfoClasses = React.useMemo( () => classnames({ [css.optionInfo]: true, - [css.optionInfoInvalid]: !valid, - [css.optionInfoInvalidSelected]: !valid && optionSelected, + [css.optionInfoInvalid]: styleStatementBasedOnValidity && !valid, + [css.optionInfoInvalidSelected]: + styleStatementBasedOnValidity && !valid && optionSelected, }), - [valid, optionSelected], + [styleStatementBasedOnValidity, valid, optionSelected], ); - const icon = React.useMemo( - () => , - [valid], - ); + const icon = React.useMemo(() => { + if (!styleStatementBasedOnValidity) { + return null; + } + return ; + }, [styleStatementBasedOnValidity, valid]); return (
{icon} {children}
); } export default EnumSettingsOptionInfo; diff --git a/web/components/enum-settings-option.react.js b/web/components/enum-settings-option.react.js index e6d639d61..bfa270cf5 100644 --- a/web/components/enum-settings-option.react.js +++ b/web/components/enum-settings-option.react.js @@ -1,57 +1,61 @@ // @flow import classnames from 'classnames'; import * as React from 'react'; import EnumSettingsOptionInfo from './enum-settings-option-info.react.js'; import css from './enum-settings-option.css'; import Radio from './radio.react'; type Props = { +selected: boolean, +onSelect: () => void, +icon: React.Node, +title: string, +statements: $ReadOnlyArray<{ +statement: string, +isStatementValid: boolean, + +styleStatementBasedOnValidity: boolean, }>, }; function EnumSettingsOption(props: Props): React.Node { const { icon, title, statements, selected, onSelect } = props; const descriptionItems = React.useMemo( () => - statements.map(({ statement, isStatementValid }) => ( - - {statement} - - )), + statements.map( + ({ statement, isStatementValid, styleStatementBasedOnValidity }) => ( + + {statement} + + ), + ), [selected, statements], ); const optionContainerClasses = React.useMemo( () => classnames(css.optionContainer, { [css.optionContainerSelected]: selected, }), [selected], ); return (
{icon}
{title}
{descriptionItems}
); } export default EnumSettingsOption; diff --git a/web/modals/threads/notifications/notifications-modal.react.js b/web/modals/threads/notifications/notifications-modal.react.js index b0f488b6c..ebf784ad7 100644 --- a/web/modals/threads/notifications/notifications-modal.react.js +++ b/web/modals/threads/notifications/notifications-modal.react.js @@ -1,193 +1,229 @@ // @flow import * as React from 'react'; import { updateSubscription, updateSubscriptionActionTypes, } from 'lib/actions/user-actions'; import { threadInfoSelector } from 'lib/selectors/thread-selectors'; import { useServerCall, useDispatchActionPromise, } from 'lib/utils/action-utils'; import { assetCacheURLPrefix, focusedNotificationsIllustrationAsset, badgeOnlyNotificationsIllustrationAsset, backgroundNotificationsIllustrationAsset, } from '../../../assets.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 }, - { statement: BADGE_COUNT, isStatementValid: true }, - { statement: IN_FOCUSED_TAB, isStatementValid: true }, + { + 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 }, - { statement: BADGE_COUNT, isStatementValid: true }, - { statement: IN_FOCUSED_TAB, isStatementValid: true }, + { + 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 }, - { statement: BADGE_COUNT, isStatementValid: false }, - { statement: IN_BACKGROUND_TAB, isStatementValid: true }, + { + 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 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]); 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, ]); return (
{focusedItem} {focusedBadgeOnlyItem} {backgroundItem}
); } export default NotificationsModal; diff --git a/web/modals/threads/settings/thread-settings-privacy-tab.react.js b/web/modals/threads/settings/thread-settings-privacy-tab.react.js index 7ce91b82d..33c9b00de 100644 --- a/web/modals/threads/settings/thread-settings-privacy-tab.react.js +++ b/web/modals/threads/settings/thread-settings-privacy-tab.react.js @@ -1,174 +1,176 @@ // @flow import * as React from 'react'; import { changeThreadSettings, changeThreadSettingsActionTypes, } from 'lib/actions/thread-actions'; import { threadTypeDescriptions } from 'lib/shared/thread-utils'; import { type SetState } from 'lib/types/hook-types'; import { type ThreadInfo, type ThreadChanges, threadTypes, } from 'lib/types/thread-types'; import { useDispatchActionPromise, useServerCall, } from 'lib/utils/action-utils'; import Button from '../../../components/button.react'; import EnumSettingsOption from '../../../components/enum-settings-option.react'; import SWMansionIcon from '../../../SWMansionIcon.react'; import { useModalContext } from '../../modal-provider.react'; import css from './thread-settings-privacy-tab.css'; const { COMMUNITY_OPEN_SUBTHREAD, COMMUNITY_SECRET_SUBTHREAD } = threadTypes; const openStatements = [ { statement: threadTypeDescriptions[COMMUNITY_OPEN_SUBTHREAD], isStatementValid: true, + styleStatementBasedOnValidity: false, }, ]; const secretStatements = [ { statement: threadTypeDescriptions[COMMUNITY_SECRET_SUBTHREAD], isStatementValid: true, + styleStatementBasedOnValidity: false, }, ]; type ThreadSettingsPrivacyTabProps = { +inputDisabled: boolean, +threadInfo: ThreadInfo, +queuedChanges: ThreadChanges, +setQueuedChanges: SetState, +setErrorMessage: SetState, }; function ThreadSettingsPrivacyTab( props: ThreadSettingsPrivacyTabProps, ): React.Node { const { inputDisabled, threadInfo, queuedChanges, setQueuedChanges, setErrorMessage, } = props; const modalContext = useModalContext(); const dispatchActionPromise = useDispatchActionPromise(); const callChangeThreadSettings = useServerCall(changeThreadSettings); const changeQueued: boolean = React.useMemo( () => Object.values(queuedChanges).some(v => v !== null && v !== undefined), [queuedChanges], ); const changeThreadSettingsAction = React.useCallback(async () => { try { const response = await callChangeThreadSettings({ threadID: threadInfo.id, changes: queuedChanges, }); modalContext.popModal(); return response; } catch (e) { setErrorMessage('unknown_error'); setQueuedChanges(Object.freeze({})); throw e; } }, [ callChangeThreadSettings, modalContext, queuedChanges, setErrorMessage, setQueuedChanges, threadInfo.id, ]); const onSubmit = React.useCallback( (event: SyntheticEvent) => { event.preventDefault(); dispatchActionPromise( changeThreadSettingsActionTypes, changeThreadSettingsAction(), ); }, [changeThreadSettingsAction, dispatchActionPromise], ); const onOpenSelected = React.useCallback(() => { setQueuedChanges(prevQueuedChanges => Object.freeze({ ...prevQueuedChanges, type: COMMUNITY_OPEN_SUBTHREAD !== threadInfo.type ? COMMUNITY_OPEN_SUBTHREAD : undefined, }), ); }, [setQueuedChanges, threadInfo.type]); const onSecretSelected = React.useCallback(() => { setQueuedChanges(prevQueuedChanges => Object.freeze({ ...prevQueuedChanges, type: COMMUNITY_SECRET_SUBTHREAD !== threadInfo.type ? COMMUNITY_SECRET_SUBTHREAD : undefined, }), ); }, [setQueuedChanges, threadInfo.type]); const globeIcon = React.useMemo( () => , [], ); const lockIcon = React.useMemo( () => , [], ); return (
Thread type
); } export default ThreadSettingsPrivacyTab;