Componentize notification line setting, don't use useEffect
This commit is contained in:
		
							parent
							
								
									c9d323c83f
								
							
						
					
					
						commit
						df3d7b591d
					
				|  | @ -1,11 +1,10 @@ | |||
| import { usePrivateUser } from 'web/hooks/use-user' | ||||
| import React, { ReactNode, useEffect, useState } from 'react' | ||||
| import { LoadingIndicator } from 'web/components/loading-indicator' | ||||
| import React, { memo, ReactNode, useEffect, useState } from 'react' | ||||
| import { Row } from 'web/components/layout/row' | ||||
| import clsx from 'clsx' | ||||
| import { | ||||
|   notification_subscription_types, | ||||
|   notification_destination_types, | ||||
|   PrivateUser, | ||||
| } from 'common/user' | ||||
| import { updatePrivateUser } from 'web/lib/firebase/users' | ||||
| import { Col } from 'web/components/layout/col' | ||||
|  | @ -23,21 +22,22 @@ import { | |||
|   UsersIcon, | ||||
| } from '@heroicons/react/outline' | ||||
| import { WatchMarketModal } from 'web/components/contract/watch-market-modal' | ||||
| import { filterDefined } from 'common/util/array' | ||||
| import toast from 'react-hot-toast' | ||||
| import { SwitchSetting } from 'web/components/switch-setting' | ||||
| import { uniq } from 'lodash' | ||||
| import { | ||||
|   storageStore, | ||||
|   usePersistentState, | ||||
| } from 'web/hooks/use-persistent-state' | ||||
| import { safeLocalStorage } from 'web/lib/util/local' | ||||
| 
 | ||||
| export function NotificationSettings(props: { | ||||
|   navigateToSection: string | undefined | ||||
|   privateUser: PrivateUser | ||||
| }) { | ||||
|   const { navigateToSection } = props | ||||
|   const privateUser = usePrivateUser() | ||||
|   const { navigateToSection, privateUser } = props | ||||
|   const [showWatchModal, setShowWatchModal] = useState(false) | ||||
| 
 | ||||
|   if (!privateUser || !privateUser.notificationSubscriptionTypes) { | ||||
|     return <LoadingIndicator spinnerClassName={'border-gray-500 h-4 w-4'} /> | ||||
|   } | ||||
| 
 | ||||
|   const emailsEnabled: Array<keyof notification_subscription_types> = [ | ||||
|     'all_comments_on_watched_markets', | ||||
|     'all_replies_to_my_comments_on_watched_markets', | ||||
|  | @ -165,32 +165,29 @@ export function NotificationSettings(props: { | |||
|     }, | ||||
|   } | ||||
| 
 | ||||
|   const NotificationSettingLine = ( | ||||
|     description: string, | ||||
|     key: keyof notification_subscription_types, | ||||
|     value: notification_destination_types[] | ||||
|   ) => { | ||||
|     const previousInAppValue = value.includes('browser') | ||||
|     const previousEmailValue = value.includes('email') | ||||
|   function NotificationSettingLine(props: { | ||||
|     description: string | ||||
|     subscriptionTypeKey: keyof notification_subscription_types | ||||
|     destinations: notification_destination_types[] | ||||
|   }) { | ||||
|     const { description, subscriptionTypeKey, destinations } = props | ||||
|     const previousInAppValue = destinations.includes('browser') | ||||
|     const previousEmailValue = destinations.includes('email') | ||||
|     const [inAppEnabled, setInAppEnabled] = useState(previousInAppValue) | ||||
|     const [emailEnabled, setEmailEnabled] = useState(previousEmailValue) | ||||
|     const loading = 'Changing Notifications Settings' | ||||
|     const success = 'Changed Notification Settings!' | ||||
|     const highlight = navigateToSection === key | ||||
|     const highlight = navigateToSection === subscriptionTypeKey | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|       if ( | ||||
|         inAppEnabled !== previousInAppValue || | ||||
|         emailEnabled !== previousEmailValue | ||||
|       ) { | ||||
|         toast.promise( | ||||
|     const changeSetting = (setting: 'browser' | 'email', newValue: boolean) => { | ||||
|       toast | ||||
|         .promise( | ||||
|           updatePrivateUser(privateUser.id, { | ||||
|             notificationSubscriptionTypes: { | ||||
|               ...privateUser.notificationSubscriptionTypes, | ||||
|               [key]: filterDefined([ | ||||
|                 inAppEnabled ? 'browser' : undefined, | ||||
|                 emailEnabled ? 'email' : undefined, | ||||
|               ]), | ||||
|               [subscriptionTypeKey]: destinations.includes(setting) | ||||
|                 ? destinations.filter((d) => d !== setting) | ||||
|                 : uniq([...destinations, setting]), | ||||
|             }, | ||||
|           }), | ||||
|           { | ||||
|  | @ -199,14 +196,14 @@ export function NotificationSettings(props: { | |||
|             error: 'Error changing notification settings. Try again?', | ||||
|           } | ||||
|         ) | ||||
|       } | ||||
|     }, [ | ||||
|       inAppEnabled, | ||||
|       emailEnabled, | ||||
|       previousInAppValue, | ||||
|       previousEmailValue, | ||||
|       key, | ||||
|     ]) | ||||
|         .then(() => { | ||||
|           if (setting === 'browser') { | ||||
|             setInAppEnabled(newValue) | ||||
|           } else { | ||||
|             setEmailEnabled(newValue) | ||||
|           } | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <Row | ||||
|  | @ -220,17 +217,17 @@ export function NotificationSettings(props: { | |||
|             <span>{description}</span> | ||||
|           </Row> | ||||
|           <Row className={'gap-4'}> | ||||
|             {!browserDisabled.includes(key) && ( | ||||
|             {!browserDisabled.includes(subscriptionTypeKey) && ( | ||||
|               <SwitchSetting | ||||
|                 checked={inAppEnabled} | ||||
|                 onChange={setInAppEnabled} | ||||
|                 onChange={(newVal) => changeSetting('browser', newVal)} | ||||
|                 label={'Web'} | ||||
|               /> | ||||
|             )} | ||||
|             {emailsEnabled.includes(key) && ( | ||||
|             {emailsEnabled.includes(subscriptionTypeKey) && ( | ||||
|               <SwitchSetting | ||||
|                 checked={emailEnabled} | ||||
|                 onChange={setEmailEnabled} | ||||
|                 onChange={(newVal) => changeSetting('email', newVal)} | ||||
|                 label={'Email'} | ||||
|               /> | ||||
|             )} | ||||
|  | @ -246,12 +243,22 @@ export function NotificationSettings(props: { | |||
|     return privateUser.notificationSubscriptionTypes[key] ?? [] | ||||
|   } | ||||
| 
 | ||||
|   const Section = (icon: ReactNode, data: sectionData) => { | ||||
|   const Section = memo(function Section(props: { | ||||
|     icon: ReactNode | ||||
|     data: sectionData | ||||
|   }) { | ||||
|     const { icon, data } = props | ||||
|     const { label, subscriptionTypeToDescription } = data | ||||
|     const expand = | ||||
|       navigateToSection && | ||||
|       Object.keys(subscriptionTypeToDescription).includes(navigateToSection) | ||||
|     const [expanded, setExpanded] = useState(expand) | ||||
| 
 | ||||
|     // Not sure how to prevent re-render (and collapse of an open section)
 | ||||
|     // due to a private user settings change. Just going to persist expanded state here
 | ||||
|     const [expanded, setExpanded] = usePersistentState(expand ?? false, { | ||||
|       key: 'NotificationsSettingsSection-' + label, | ||||
|       store: storageStore(safeLocalStorage()), | ||||
|     }) | ||||
| 
 | ||||
|     // Not working as the default value for expanded, so using a useEffect
 | ||||
|     useEffect(() => { | ||||
|  | @ -278,19 +285,19 @@ export function NotificationSettings(props: { | |||
|           )} | ||||
|         </Row> | ||||
|         <Col className={clsx(expanded ? 'block' : 'hidden', 'gap-2 p-2')}> | ||||
|           {Object.entries(subscriptionTypeToDescription).map(([key, value]) => | ||||
|             NotificationSettingLine( | ||||
|               value, | ||||
|               key as keyof notification_subscription_types, | ||||
|               getUsersSavedPreference( | ||||
|           {Object.entries(subscriptionTypeToDescription).map(([key, value]) => ( | ||||
|             <NotificationSettingLine | ||||
|               subscriptionTypeKey={key as keyof notification_subscription_types} | ||||
|               destinations={getUsersSavedPreference( | ||||
|                 key as keyof notification_subscription_types | ||||
|               ) | ||||
|             ) | ||||
|           )} | ||||
|               )} | ||||
|               description={value} | ||||
|             /> | ||||
|           ))} | ||||
|         </Col> | ||||
|       </Col> | ||||
|     ) | ||||
|   } | ||||
|   }) | ||||
| 
 | ||||
|   return ( | ||||
|     <div className={'p-2'}> | ||||
|  | @ -302,20 +309,38 @@ export function NotificationSettings(props: { | |||
|             onClick={() => setShowWatchModal(true)} | ||||
|           /> | ||||
|         </Row> | ||||
|         {Section(<ChatIcon className={'h-6 w-6'} />, comments)} | ||||
|         {Section(<LightBulbIcon className={'h-6 w-6'} />, answers)} | ||||
|         {Section(<TrendingUpIcon className={'h-6 w-6'} />, updates)} | ||||
|         {Section(<UserIcon className={'h-6 w-6'} />, yourMarkets)} | ||||
|         <Section icon={<ChatIcon className={'h-6 w-6'} />} data={comments} /> | ||||
|         <Section | ||||
|           icon={<TrendingUpIcon className={'h-6 w-6'} />} | ||||
|           data={updates} | ||||
|         /> | ||||
|         <Section | ||||
|           icon={<LightBulbIcon className={'h-6 w-6'} />} | ||||
|           data={answers} | ||||
|         /> | ||||
|         <Section icon={<UserIcon className={'h-6 w-6'} />} data={yourMarkets} /> | ||||
|         <Row className={'gap-2 text-xl text-gray-700'}> | ||||
|           <span>Balance Changes</span> | ||||
|         </Row> | ||||
|         {Section(<CurrencyDollarIcon className={'h-6 w-6'} />, bonuses)} | ||||
|         {Section(<CashIcon className={'h-6 w-6'} />, otherBalances)} | ||||
|         <Section | ||||
|           icon={<CurrencyDollarIcon className={'h-6 w-6'} />} | ||||
|           data={bonuses} | ||||
|         /> | ||||
|         <Section | ||||
|           icon={<CashIcon className={'h-6 w-6'} />} | ||||
|           data={otherBalances} | ||||
|         /> | ||||
|         <Row className={'gap-2 text-xl text-gray-700'}> | ||||
|           <span>General</span> | ||||
|         </Row> | ||||
|         {Section(<UsersIcon className={'h-6 w-6'} />, userInteractions)} | ||||
|         {Section(<InboxInIcon className={'h-6 w-6'} />, generalOther)} | ||||
|         <Section | ||||
|           icon={<UsersIcon className={'h-6 w-6'} />} | ||||
|           data={userInteractions} | ||||
|         /> | ||||
|         <Section | ||||
|           icon={<InboxInIcon className={'h-6 w-6'} />} | ||||
|           data={generalOther} | ||||
|         /> | ||||
|         <WatchMarketModal open={showWatchModal} setOpen={setShowWatchModal} /> | ||||
|       </Col> | ||||
|     </div> | ||||
|  |  | |||
|  | @ -112,6 +112,7 @@ export default function Notifications() { | |||
|                   content: ( | ||||
|                     <NotificationSettings | ||||
|                       navigateToSection={navigateToSection} | ||||
|                       privateUser={privateUser} | ||||
|                     /> | ||||
|                   ), | ||||
|                 }, | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user