import React, { memo, ReactNode, useEffect, useState } from 'react' import { Row } from 'web/components/layout/row' import clsx from 'clsx' import { PrivateUser } from 'common/user' import { updatePrivateUser } from 'web/lib/firebase/users' import { Col } from 'web/components/layout/col' import { CashIcon, ChatIcon, ChevronDownIcon, ChevronUpIcon, CurrencyDollarIcon, ExclamationIcon, InboxInIcon, InformationCircleIcon, LightBulbIcon, TrendingUpIcon, UserIcon, UsersIcon, } from '@heroicons/react/outline' import { WatchMarketModal } from 'web/components/contract/watch-market-modal' 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' import { NOTIFICATION_DESCRIPTIONS } from 'common/notification' import { notification_destination_types, notification_preference, } from 'common/user-notification-preferences' export function NotificationSettings(props: { navigateToSection: string | undefined privateUser: PrivateUser }) { const { navigateToSection, privateUser } = props const [showWatchModal, setShowWatchModal] = useState(false) const emailsEnabled: Array = [ 'all_comments_on_watched_markets', 'all_replies_to_my_comments_on_watched_markets', 'all_comments_on_contracts_with_shares_in_on_watched_markets', 'all_answers_on_watched_markets', 'all_replies_to_my_answers_on_watched_markets', 'all_answers_on_contracts_with_shares_in_on_watched_markets', 'your_contract_closed', 'all_comments_on_my_markets', 'all_answers_on_my_markets', 'resolutions_on_watched_markets_with_shares_in', 'resolutions_on_watched_markets', 'trending_markets', 'onboarding_flow', 'thank_you_for_purchases', 'tagged_user', // missing tagged on contract description email 'contract_from_followed_user', 'unique_bettors_on_your_contract', 'profit_loss_updates', 'opt_out_all', // TODO: add these // biggest winner, here are the rest of your markets // 'referral_bonuses', // 'on_new_follow', // 'tips_on_your_markets', // 'tips_on_your_comments', // maybe the following? // 'probability_updates_on_watched_markets', // 'limit_order_fills', ] const browserDisabled: Array = [ 'trending_markets', 'profit_loss_updates', 'onboarding_flow', 'thank_you_for_purchases', ] type SectionData = { label: string subscriptionTypes: Partial[] } const comments: SectionData = { label: 'New Comments', subscriptionTypes: [ 'all_comments_on_watched_markets', 'all_comments_on_contracts_with_shares_in_on_watched_markets', // TODO: combine these two 'all_replies_to_my_comments_on_watched_markets', 'all_replies_to_my_answers_on_watched_markets', ], } const answers: SectionData = { label: 'New Answers', subscriptionTypes: [ 'all_answers_on_watched_markets', 'all_answers_on_contracts_with_shares_in_on_watched_markets', ], } const updates: SectionData = { label: 'Updates & Resolutions', subscriptionTypes: [ 'market_updates_on_watched_markets', 'market_updates_on_watched_markets_with_shares_in', 'resolutions_on_watched_markets', 'resolutions_on_watched_markets_with_shares_in', ], } const yourMarkets: SectionData = { label: 'Markets You Created', subscriptionTypes: [ // 'your_contract_closed', 'all_comments_on_my_markets', 'all_answers_on_my_markets', 'subsidized_your_market', 'tips_on_your_markets', ], } const bonuses: SectionData = { label: 'Bonuses', subscriptionTypes: [ 'betting_streaks', 'referral_bonuses', 'unique_bettors_on_your_contract', ], } const otherBalances: SectionData = { label: 'Other', subscriptionTypes: [ 'loan_income', 'limit_order_fills', 'tips_on_your_comments', 'badges_awarded', ], } const userInteractions: SectionData = { label: 'Users', subscriptionTypes: [ 'tagged_user', 'on_new_follow', 'contract_from_followed_user', ], } const generalOther: SectionData = { label: 'Other', subscriptionTypes: [ 'trending_markets', 'thank_you_for_purchases', 'onboarding_flow', 'profit_loss_updates', ], } const optOut: SectionData = { label: 'Opt Out', subscriptionTypes: ['opt_out_all'], } function NotificationSettingLine(props: { description: string subscriptionTypeKey: notification_preference destinations: notification_destination_types[] optOutAll: notification_destination_types[] }) { const { description, subscriptionTypeKey, destinations, optOutAll } = props const previousInAppValue = destinations.includes('browser') const previousEmailValue = destinations.includes('email') const [inAppEnabled, setInAppEnabled] = useState(previousInAppValue) const [emailEnabled, setEmailEnabled] = useState(previousEmailValue) const [error, setError] = useState('') const loading = 'Changing Notifications Settings' const success = 'Changed Notification Settings!' const highlight = navigateToSection === subscriptionTypeKey const attemptToChangeSetting = ( setting: 'browser' | 'email', newValue: boolean ) => { const necessaryError = 'This notification type is necessary. At least one destination must be enabled.' const necessarySetting = NOTIFICATION_DESCRIPTIONS[subscriptionTypeKey].necessary if ( necessarySetting && setting === 'browser' && !emailEnabled && !newValue ) { setError(necessaryError) return } else if ( necessarySetting && setting === 'email' && !inAppEnabled && !newValue ) { setError(necessaryError) return } changeSetting(setting, newValue) } const changeSetting = (setting: 'browser' | 'email', newValue: boolean) => { toast .promise( updatePrivateUser(privateUser.id, { notificationPreferences: { ...privateUser.notificationPreferences, [subscriptionTypeKey]: destinations.includes(setting) ? destinations.filter((d) => d !== setting) : uniq([...destinations, setting]), }, }), { success, loading, error: 'Error changing notification settings. Try again?', } ) .then(() => { if (setting === 'browser') { setInAppEnabled(newValue) } else { setEmailEnabled(newValue) } }) } return ( {description} {!browserDisabled.includes(subscriptionTypeKey) && ( attemptToChangeSetting('browser', newVal)} label={'Web'} disabled={optOutAll.includes('browser')} /> )} {emailsEnabled.includes(subscriptionTypeKey) && ( attemptToChangeSetting('email', newVal)} label={'Email'} disabled={optOutAll.includes('email')} /> )} {error && {error}} ) } const getUsersSavedPreference = (key: notification_preference) => { return privateUser.notificationPreferences[key] ?? [] } const Section = memo(function Section(props: { icon: ReactNode data: SectionData }) { const { icon, data } = props const { label, subscriptionTypes } = data const expand = navigateToSection && subscriptionTypes.includes(navigateToSection as notification_preference) // 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-' + subscriptionTypes.join('-'), store: storageStore(safeLocalStorage()), }) // Not working as the default value for expanded, so using a useEffect useEffect(() => { if (expand) setExpanded(true) }, [expand, setExpanded]) return ( setExpanded(!expanded)} > {icon} {label} {expanded ? ( Hide ) : ( Show )} {subscriptionTypes.map((subType) => ( ))} ) }) return (
Notifications for Watched Markets setShowWatchModal(true)} />
} data={comments} />
} data={updates} />
} data={answers} />
} data={yourMarkets} /> Balance Changes
} data={bonuses} />
} data={otherBalances} /> General
} data={userInteractions} />
} data={generalOther} />
} data={optOut} />
) }