Allow user to opt out of all unnecessary notifications (#974)
* Allow user to opt out of all unnecessary notifications * Unsubscribe from all response ux * Only send one response
This commit is contained in:
		
							parent
							
								
									1f7b9174b3
								
							
						
					
					
						commit
						051c2905e1
					
				| 
						 | 
				
			
			@ -96,6 +96,7 @@ type notification_descriptions = {
 | 
			
		|||
  [key in notification_preference]: {
 | 
			
		||||
    simple: string
 | 
			
		||||
    detailed: string
 | 
			
		||||
    necessary?: boolean
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
export const NOTIFICATION_DESCRIPTIONS: notification_descriptions = {
 | 
			
		||||
| 
						 | 
				
			
			@ -208,8 +209,9 @@ export const NOTIFICATION_DESCRIPTIONS: notification_descriptions = {
 | 
			
		|||
    detailed: 'Bonuses for unique predictors on your markets',
 | 
			
		||||
  },
 | 
			
		||||
  your_contract_closed: {
 | 
			
		||||
    simple: 'Your market has closed and you need to resolve it',
 | 
			
		||||
    detailed: 'Your market has closed and you need to resolve it',
 | 
			
		||||
    simple: 'Your market has closed and you need to resolve it (necessary)',
 | 
			
		||||
    detailed: 'Your market has closed and you need to resolve it (necessary)',
 | 
			
		||||
    necessary: true,
 | 
			
		||||
  },
 | 
			
		||||
  all_comments_on_watched_markets: {
 | 
			
		||||
    simple: 'All new comments',
 | 
			
		||||
| 
						 | 
				
			
			@ -235,6 +237,11 @@ export const NOTIFICATION_DESCRIPTIONS: notification_descriptions = {
 | 
			
		|||
    simple: `Only on markets you're invested in`,
 | 
			
		||||
    detailed: `Answers on markets that you're watching and that you're invested in`,
 | 
			
		||||
  },
 | 
			
		||||
  opt_out_all: {
 | 
			
		||||
    simple: 'Opt out of all notifications (excludes when your markets close)',
 | 
			
		||||
    detailed:
 | 
			
		||||
      'Opt out of all notifications excluding your own market closure notifications',
 | 
			
		||||
  },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type BettingStreakData = {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -53,6 +53,9 @@ export type notification_preferences = {
 | 
			
		|||
  profit_loss_updates: notification_destination_types[]
 | 
			
		||||
  onboarding_flow: notification_destination_types[]
 | 
			
		||||
  thank_you_for_purchases: notification_destination_types[]
 | 
			
		||||
 | 
			
		||||
  opt_out_all: notification_destination_types[]
 | 
			
		||||
  // When adding a new notification preference, use add-new-notification-preference.ts to existing users
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const getDefaultNotificationPreferences = (
 | 
			
		||||
| 
						 | 
				
			
			@ -65,7 +68,7 @@ export const getDefaultNotificationPreferences = (
 | 
			
		|||
    const email = noEmails ? undefined : emailIf ? 'email' : undefined
 | 
			
		||||
    return filterDefined([browser, email]) as notification_destination_types[]
 | 
			
		||||
  }
 | 
			
		||||
  return {
 | 
			
		||||
  const defaults: notification_preferences = {
 | 
			
		||||
    // Watched Markets
 | 
			
		||||
    all_comments_on_watched_markets: constructPref(true, false),
 | 
			
		||||
    all_answers_on_watched_markets: constructPref(true, false),
 | 
			
		||||
| 
						 | 
				
			
			@ -121,7 +124,10 @@ export const getDefaultNotificationPreferences = (
 | 
			
		|||
    probability_updates_on_watched_markets: constructPref(true, false),
 | 
			
		||||
    thank_you_for_purchases: constructPref(false, false),
 | 
			
		||||
    onboarding_flow: constructPref(false, false),
 | 
			
		||||
  } as notification_preferences
 | 
			
		||||
 | 
			
		||||
    opt_out_all: [],
 | 
			
		||||
  }
 | 
			
		||||
  return defaults
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Adding a new key:value here is optional, you can just use a key of notification_subscription_types
 | 
			
		||||
| 
						 | 
				
			
			@ -184,10 +190,18 @@ export const getNotificationDestinationsForUser = (
 | 
			
		|||
      ? notificationSettings[subscriptionType]
 | 
			
		||||
      : []
 | 
			
		||||
  }
 | 
			
		||||
  const optOutOfAllSettings = notificationSettings['opt_out_all']
 | 
			
		||||
  // Your market closure notifications are high priority, opt-out doesn't affect their delivery
 | 
			
		||||
  const optedOutOfEmail =
 | 
			
		||||
    optOutOfAllSettings.includes('email') &&
 | 
			
		||||
    subscriptionType !== 'your_contract_closed'
 | 
			
		||||
  const optedOutOfBrowser =
 | 
			
		||||
    optOutOfAllSettings.includes('browser') &&
 | 
			
		||||
    subscriptionType !== 'your_contract_closed'
 | 
			
		||||
  const unsubscribeEndpoint = getFunctionUrl('unsubscribe')
 | 
			
		||||
  return {
 | 
			
		||||
    sendToEmail: destinations.includes('email'),
 | 
			
		||||
    sendToBrowser: destinations.includes('browser'),
 | 
			
		||||
    sendToEmail: destinations.includes('email') && !optedOutOfEmail,
 | 
			
		||||
    sendToBrowser: destinations.includes('browser') && !optedOutOfBrowser,
 | 
			
		||||
    unsubscribeUrl: `${unsubscribeEndpoint}?id=${privateUser.id}&type=${subscriptionType}`,
 | 
			
		||||
    urlToManageThisNotification: `${DOMAIN}/notifications?tab=settings§ion=${subscriptionType}`,
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										27
									
								
								functions/src/scripts/add-new-notification-preference.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								functions/src/scripts/add-new-notification-preference.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
import * as admin from 'firebase-admin'
 | 
			
		||||
 | 
			
		||||
import { initAdmin } from './script-init'
 | 
			
		||||
import { getAllPrivateUsers } from 'functions/src/utils'
 | 
			
		||||
initAdmin()
 | 
			
		||||
 | 
			
		||||
const firestore = admin.firestore()
 | 
			
		||||
 | 
			
		||||
async function main() {
 | 
			
		||||
  const privateUsers = await getAllPrivateUsers()
 | 
			
		||||
  await Promise.all(
 | 
			
		||||
    privateUsers.map((privateUser) => {
 | 
			
		||||
      if (!privateUser.id) return Promise.resolve()
 | 
			
		||||
      return firestore
 | 
			
		||||
        .collection('private-users')
 | 
			
		||||
        .doc(privateUser.id)
 | 
			
		||||
        .update({
 | 
			
		||||
          notificationPreferences: {
 | 
			
		||||
            ...privateUser.notificationPreferences,
 | 
			
		||||
            opt_out_all: [],
 | 
			
		||||
          },
 | 
			
		||||
        })
 | 
			
		||||
    })
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if (require.main === module) main().then(() => process.exit())
 | 
			
		||||
| 
						 | 
				
			
			@ -4,6 +4,7 @@ import { getPrivateUser } from './utils'
 | 
			
		|||
import { PrivateUser } from '../../common/user'
 | 
			
		||||
import { NOTIFICATION_DESCRIPTIONS } from '../../common/notification'
 | 
			
		||||
import { notification_preference } from '../../common/user-notification-preferences'
 | 
			
		||||
import { getFunctionUrl } from '../../common/api'
 | 
			
		||||
 | 
			
		||||
export const unsubscribe: EndpointDefinition = {
 | 
			
		||||
  opts: { method: 'GET', minInstances: 1 },
 | 
			
		||||
| 
						 | 
				
			
			@ -20,6 +21,8 @@ export const unsubscribe: EndpointDefinition = {
 | 
			
		|||
      res.status(400).send('Invalid subscription type parameter.')
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
    const optOutAllType: notification_preference = 'opt_out_all'
 | 
			
		||||
    const wantsToOptOutAll = notificationSubscriptionType === optOutAllType
 | 
			
		||||
 | 
			
		||||
    const user = await getPrivateUser(id)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -37,17 +40,22 @@ export const unsubscribe: EndpointDefinition = {
 | 
			
		|||
    const update: Partial<PrivateUser> = {
 | 
			
		||||
      notificationPreferences: {
 | 
			
		||||
        ...user.notificationPreferences,
 | 
			
		||||
        [notificationSubscriptionType]: previousDestinations.filter(
 | 
			
		||||
          (destination) => destination !== 'email'
 | 
			
		||||
        ),
 | 
			
		||||
        [notificationSubscriptionType]: wantsToOptOutAll
 | 
			
		||||
          ? previousDestinations.push('email')
 | 
			
		||||
          : previousDestinations.filter(
 | 
			
		||||
              (destination) => destination !== 'email'
 | 
			
		||||
            ),
 | 
			
		||||
      },
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await firestore.collection('private-users').doc(id).update(update)
 | 
			
		||||
    const unsubscribeEndpoint = getFunctionUrl('unsubscribe')
 | 
			
		||||
 | 
			
		||||
    res.send(
 | 
			
		||||
      `
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
    const optOutAllUrl = `${unsubscribeEndpoint}?id=${id}&type=${optOutAllType}`
 | 
			
		||||
    if (wantsToOptOutAll) {
 | 
			
		||||
      res.send(
 | 
			
		||||
        `
 | 
			
		||||
     <!DOCTYPE html>
 | 
			
		||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml"
 | 
			
		||||
      xmlns:o="urn:schemas-microsoft-com:office:office">
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -163,19 +171,6 @@ export const unsubscribe: EndpointDefinition = {
 | 
			
		|||
                  </a>
 | 
			
		||||
                </td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td align="left"
 | 
			
		||||
                    style="font-size:0px;padding:10px 25px;padding-top:0px;padding-bottom:0px;word-break:break-word;">
 | 
			
		||||
                  <div
 | 
			
		||||
                    style="font-family:Arial, sans-serif;font-size:18px;letter-spacing:normal;line-height:1;text-align:left;color:#000000;">
 | 
			
		||||
                    <p class="text-build-content"
 | 
			
		||||
                       style="line-height: 24px; margin: 10px 0; margin-top: 10px; margin-bottom: 10px;"
 | 
			
		||||
                       data-testid="4XoHRGw1Y"><span
 | 
			
		||||
                      style="color:#000000;font-family:Arial, Helvetica, sans-serif;font-size:18px;">
 | 
			
		||||
                              Hello!</span></p>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td align="left"
 | 
			
		||||
                    style="font-size:0px;padding:10px 25px;padding-top:0px;padding-bottom:0px;word-break:break-word;">
 | 
			
		||||
| 
						 | 
				
			
			@ -186,20 +181,9 @@ export const unsubscribe: EndpointDefinition = {
 | 
			
		|||
                       data-testid="4XoHRGw1Y">
 | 
			
		||||
                       <span
 | 
			
		||||
                         style="color:#000000;font-family:Arial, Helvetica, sans-serif;font-size:18px;">
 | 
			
		||||
                      ${email} has been unsubscribed from email notifications related to:
 | 
			
		||||
                      ${email} has opted out of receiving unnecessary email notifications
 | 
			
		||||
                    </span>
 | 
			
		||||
                      <br/>
 | 
			
		||||
                      <br/>
 | 
			
		||||
 | 
			
		||||
                      <span style="font-weight: bold; color:#000000;font-family:Arial, Helvetica, sans-serif;font-size:18px;">${NOTIFICATION_DESCRIPTIONS[notificationSubscriptionType].detailed}.</span>
 | 
			
		||||
                    </p>
 | 
			
		||||
                    <br/>
 | 
			
		||||
                    <br/>
 | 
			
		||||
                    <br/>
 | 
			
		||||
                    <span>Click
 | 
			
		||||
                    <a href='https://manifold.markets/notifications?tab=settings'>here</a>
 | 
			
		||||
                       to manage the rest of your notification settings.
 | 
			
		||||
                      </span>
 | 
			
		||||
                  </div>
 | 
			
		||||
 | 
			
		||||
                </td>
 | 
			
		||||
| 
						 | 
				
			
			@ -219,9 +203,193 @@ export const unsubscribe: EndpointDefinition = {
 | 
			
		|||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
</body>
 | 
			
		||||
</html>`
 | 
			
		||||
      )
 | 
			
		||||
    } else {
 | 
			
		||||
      res.send(
 | 
			
		||||
        `
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml"
 | 
			
		||||
      xmlns:o="urn:schemas-microsoft-com:office:office">
 | 
			
		||||
 | 
			
		||||
<head>
 | 
			
		||||
    <title>Manifold Markets 7th Day Anniversary Gift!</title>
 | 
			
		||||
    <!--[if !mso]><!-->
 | 
			
		||||
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 | 
			
		||||
    <!--<![endif]-->
 | 
			
		||||
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 | 
			
		||||
    <meta name="viewport" content="width=device-width,initial-scale=1">
 | 
			
		||||
    <style type="text/css">
 | 
			
		||||
        #outlook a {
 | 
			
		||||
            padding: 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        body {
 | 
			
		||||
            margin: 0;
 | 
			
		||||
            padding: 0;
 | 
			
		||||
            -webkit-text-size-adjust: 100%;
 | 
			
		||||
            -ms-text-size-adjust: 100%;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        table,
 | 
			
		||||
        td {
 | 
			
		||||
            border-collapse: collapse;
 | 
			
		||||
            mso-table-lspace: 0pt;
 | 
			
		||||
            mso-table-rspace: 0pt;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        img {
 | 
			
		||||
            border: 0;
 | 
			
		||||
            height: auto;
 | 
			
		||||
            line-height: 100%;
 | 
			
		||||
            outline: none;
 | 
			
		||||
            text-decoration: none;
 | 
			
		||||
            -ms-interpolation-mode: bicubic;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        p {
 | 
			
		||||
            display: block;
 | 
			
		||||
            margin: 13px 0;
 | 
			
		||||
        }
 | 
			
		||||
    </style>
 | 
			
		||||
    <!--[if mso]>
 | 
			
		||||
    <noscript>
 | 
			
		||||
        <xml>
 | 
			
		||||
            <o:OfficeDocumentSettings>
 | 
			
		||||
                <o:AllowPNG/>
 | 
			
		||||
                <o:PixelsPerInch>96</o:PixelsPerInch>
 | 
			
		||||
            </o:OfficeDocumentSettings>
 | 
			
		||||
        </xml>
 | 
			
		||||
    </noscript>
 | 
			
		||||
    <![endif]-->
 | 
			
		||||
    <!--[if lte mso 11]>
 | 
			
		||||
    <style type="text/css">
 | 
			
		||||
        .mj-outlook-group-fix { width:100% !important; }
 | 
			
		||||
    </style>
 | 
			
		||||
    <![endif]-->
 | 
			
		||||
    <style type="text/css">
 | 
			
		||||
        @media only screen and (min-width:480px) {
 | 
			
		||||
            .mj-column-per-100 {
 | 
			
		||||
                width: 100% !important;
 | 
			
		||||
                max-width: 100%;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    </style>
 | 
			
		||||
    <style media="screen and (min-width:480px)">
 | 
			
		||||
        .moz-text-html .mj-column-per-100 {
 | 
			
		||||
            width: 100% !important;
 | 
			
		||||
            max-width: 100%;
 | 
			
		||||
        }
 | 
			
		||||
    </style>
 | 
			
		||||
    <style type="text/css">
 | 
			
		||||
        [owa] .mj-column-per-100 {
 | 
			
		||||
            width: 100% !important;
 | 
			
		||||
            max-width: 100%;
 | 
			
		||||
        }
 | 
			
		||||
    </style>
 | 
			
		||||
    <style type="text/css">
 | 
			
		||||
        @media only screen and (max-width:480px) {
 | 
			
		||||
            table.mj-full-width-mobile {
 | 
			
		||||
                width: 100% !important;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            td.mj-full-width-mobile {
 | 
			
		||||
                width: auto !important;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    </style>
 | 
			
		||||
</head>
 | 
			
		||||
<body style="word-spacing:normal;background-color:#F4F4F4;">
 | 
			
		||||
<div style="background-color:#F4F4F4;">
 | 
			
		||||
    <!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
 | 
			
		||||
    <div style="background:#ffffff;background-color:#ffffff;margin:0px auto;max-width:600px;">
 | 
			
		||||
        <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation"
 | 
			
		||||
               style="background:#ffffff;background-color:#ffffff;width:100%;">
 | 
			
		||||
            <tbody>
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td
 | 
			
		||||
                  style="direction:ltr;font-size:0px;padding:0px 0px 0px 0px;padding-bottom:0px;padding-left:0px;padding-right:0px;padding-top:0px;text-align:center;">
 | 
			
		||||
                    <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
 | 
			
		||||
                    <div class="mj-column-per-100 mj-outlook-group-fix"
 | 
			
		||||
                         style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
 | 
			
		||||
                        <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;"
 | 
			
		||||
                               width="100%">
 | 
			
		||||
                            <tbody>
 | 
			
		||||
                            <tr>
 | 
			
		||||
                                <td style="width:550px;">
 | 
			
		||||
                                    <a href="https://manifold.markets" target="_blank">
 | 
			
		||||
                                        <img alt="banner logo" height="auto" src="https://manifold.markets/logo-banner.png"
 | 
			
		||||
                                             style="border:none;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;"
 | 
			
		||||
                                             title="" width="550">
 | 
			
		||||
                                    </a>
 | 
			
		||||
                                </td>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                            <tr>
 | 
			
		||||
                                <td align="left"
 | 
			
		||||
                                    style="font-size:0px;padding:10px 25px;padding-top:0px;padding-bottom:0px;word-break:break-word;">
 | 
			
		||||
                                    <div
 | 
			
		||||
                                      style="font-family:Arial, sans-serif;font-size:18px;letter-spacing:normal;line-height:1;text-align:left;color:#000000;">
 | 
			
		||||
                                        <p class="text-build-content"
 | 
			
		||||
                                           style="line-height: 24px; margin: 10px 0; margin-top: 10px; margin-bottom: 10px;"
 | 
			
		||||
                                           data-testid="4XoHRGw1Y"><span
 | 
			
		||||
                                          style="color:#000000;font-family:Arial, Helvetica, sans-serif;font-size:18px;">
 | 
			
		||||
                              Hello!</span></p>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </td>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                            <tr>
 | 
			
		||||
                                <td align="left"
 | 
			
		||||
                                    style="font-size:0px;padding:10px 25px;padding-top:0px;padding-bottom:0px;word-break:break-word;">
 | 
			
		||||
                                    <div
 | 
			
		||||
                                      style="font-family:Arial, sans-serif;font-size:18px;letter-spacing:normal;line-height:1;text-align:left;color:#000000;">
 | 
			
		||||
                                        <p class="text-build-content"
 | 
			
		||||
                                           style="line-height: 24px; margin: 10px 0; margin-top: 10px; margin-bottom: 10px;"
 | 
			
		||||
                                           data-testid="4XoHRGw1Y">
 | 
			
		||||
                       <span
 | 
			
		||||
                         style="color:#000000;font-family:Arial, Helvetica, sans-serif;font-size:18px;">
 | 
			
		||||
                      ${email} has been unsubscribed from email notifications related to:
 | 
			
		||||
                    </span>
 | 
			
		||||
                                            <br/>
 | 
			
		||||
                                            <br/>
 | 
			
		||||
 | 
			
		||||
                                            <span style="font-weight: bold; color:#000000;font-family:Arial, Helvetica, sans-serif;font-size:18px;">${NOTIFICATION_DESCRIPTIONS[notificationSubscriptionType].detailed}.</span>
 | 
			
		||||
                                        </p>
 | 
			
		||||
                                        <br/>
 | 
			
		||||
                                        <br/>
 | 
			
		||||
                                        <br/>
 | 
			
		||||
                    <span>Click
 | 
			
		||||
                    <a href=${optOutAllUrl}>here</a>
 | 
			
		||||
                       to unsubscribe from all unnecessary emails.
 | 
			
		||||
                      </span>
 | 
			
		||||
                                        <br/>
 | 
			
		||||
                                        <br/>
 | 
			
		||||
                    <span>Click
 | 
			
		||||
                    <a href='https://manifold.markets/notifications?tab=settings'>here</a>
 | 
			
		||||
                       to manage the rest of your notification settings.
 | 
			
		||||
                      </span>
 | 
			
		||||
                                    </div>
 | 
			
		||||
 | 
			
		||||
                                </td>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                            <tr>
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <p></p>
 | 
			
		||||
                                </td>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                            </tbody>
 | 
			
		||||
                        </table>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            </tbody>
 | 
			
		||||
        </table>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
`
 | 
			
		||||
    )
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,6 +10,7 @@ import {
 | 
			
		|||
  ChevronDownIcon,
 | 
			
		||||
  ChevronUpIcon,
 | 
			
		||||
  CurrencyDollarIcon,
 | 
			
		||||
  ExclamationIcon,
 | 
			
		||||
  InboxInIcon,
 | 
			
		||||
  InformationCircleIcon,
 | 
			
		||||
  LightBulbIcon,
 | 
			
		||||
| 
						 | 
				
			
			@ -63,6 +64,7 @@ export function NotificationSettings(props: {
 | 
			
		|||
    '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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -157,20 +159,56 @@ export function NotificationSettings(props: {
 | 
			
		|||
    ],
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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 } = props
 | 
			
		||||
    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<string>('')
 | 
			
		||||
    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(
 | 
			
		||||
| 
						 | 
				
			
			@ -212,18 +250,21 @@ export function NotificationSettings(props: {
 | 
			
		|||
            {!browserDisabled.includes(subscriptionTypeKey) && (
 | 
			
		||||
              <SwitchSetting
 | 
			
		||||
                checked={inAppEnabled}
 | 
			
		||||
                onChange={(newVal) => changeSetting('browser', newVal)}
 | 
			
		||||
                onChange={(newVal) => attemptToChangeSetting('browser', newVal)}
 | 
			
		||||
                label={'Web'}
 | 
			
		||||
                disabled={optOutAll.includes('browser')}
 | 
			
		||||
              />
 | 
			
		||||
            )}
 | 
			
		||||
            {emailsEnabled.includes(subscriptionTypeKey) && (
 | 
			
		||||
              <SwitchSetting
 | 
			
		||||
                checked={emailEnabled}
 | 
			
		||||
                onChange={(newVal) => changeSetting('email', newVal)}
 | 
			
		||||
                onChange={(newVal) => attemptToChangeSetting('email', newVal)}
 | 
			
		||||
                label={'Email'}
 | 
			
		||||
                disabled={optOutAll.includes('email')}
 | 
			
		||||
              />
 | 
			
		||||
            )}
 | 
			
		||||
          </Row>
 | 
			
		||||
          {error && <span className={'text-error'}>{error}</span>}
 | 
			
		||||
        </Col>
 | 
			
		||||
      </Row>
 | 
			
		||||
    )
 | 
			
		||||
| 
						 | 
				
			
			@ -283,6 +324,11 @@ export function NotificationSettings(props: {
 | 
			
		|||
                subType as notification_preference
 | 
			
		||||
              )}
 | 
			
		||||
              description={NOTIFICATION_DESCRIPTIONS[subType].simple}
 | 
			
		||||
              optOutAll={
 | 
			
		||||
                subType === 'opt_out_all' || subType === 'your_contract_closed'
 | 
			
		||||
                  ? []
 | 
			
		||||
                  : getUsersSavedPreference('opt_out_all')
 | 
			
		||||
              }
 | 
			
		||||
            />
 | 
			
		||||
          ))}
 | 
			
		||||
        </Col>
 | 
			
		||||
| 
						 | 
				
			
			@ -332,6 +378,10 @@ export function NotificationSettings(props: {
 | 
			
		|||
          icon={<InboxInIcon className={'h-6 w-6'} />}
 | 
			
		||||
          data={generalOther}
 | 
			
		||||
        />
 | 
			
		||||
        <Section
 | 
			
		||||
          icon={<ExclamationIcon className={'h-6 w-6'} />}
 | 
			
		||||
          data={optOut}
 | 
			
		||||
        />
 | 
			
		||||
        <WatchMarketModal open={showWatchModal} setOpen={setShowWatchModal} />
 | 
			
		||||
      </Col>
 | 
			
		||||
    </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,33 +1,52 @@
 | 
			
		|||
import { Switch } from '@headlessui/react'
 | 
			
		||||
import clsx from 'clsx'
 | 
			
		||||
import React from 'react'
 | 
			
		||||
import { Tooltip } from 'web/components/tooltip'
 | 
			
		||||
 | 
			
		||||
export const SwitchSetting = (props: {
 | 
			
		||||
  checked: boolean
 | 
			
		||||
  onChange: (checked: boolean) => void
 | 
			
		||||
  label: string
 | 
			
		||||
  disabled: boolean
 | 
			
		||||
}) => {
 | 
			
		||||
  const { checked, onChange, label } = props
 | 
			
		||||
  const { checked, onChange, label, disabled } = props
 | 
			
		||||
  return (
 | 
			
		||||
    <Switch.Group as="div" className="flex items-center">
 | 
			
		||||
      <Switch
 | 
			
		||||
        checked={checked}
 | 
			
		||||
        onChange={onChange}
 | 
			
		||||
        className={clsx(
 | 
			
		||||
          checked ? 'bg-indigo-600' : 'bg-gray-200',
 | 
			
		||||
          'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2'
 | 
			
		||||
        )}
 | 
			
		||||
      <Tooltip
 | 
			
		||||
        text={
 | 
			
		||||
          disabled
 | 
			
		||||
            ? `You are opted out of all ${label} notifications. Go to the Opt Out section to undo this setting.`
 | 
			
		||||
            : ''
 | 
			
		||||
        }
 | 
			
		||||
      >
 | 
			
		||||
        <span
 | 
			
		||||
          aria-hidden="true"
 | 
			
		||||
        <Switch
 | 
			
		||||
          checked={checked}
 | 
			
		||||
          onChange={onChange}
 | 
			
		||||
          className={clsx(
 | 
			
		||||
            checked ? 'translate-x-5' : 'translate-x-0',
 | 
			
		||||
            'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out'
 | 
			
		||||
            checked ? 'bg-indigo-600' : 'bg-gray-200',
 | 
			
		||||
            'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2',
 | 
			
		||||
            disabled ? 'cursor-not-allowed opacity-50' : ''
 | 
			
		||||
          )}
 | 
			
		||||
        />
 | 
			
		||||
      </Switch>
 | 
			
		||||
          disabled={disabled}
 | 
			
		||||
        >
 | 
			
		||||
          <span
 | 
			
		||||
            aria-hidden="true"
 | 
			
		||||
            className={clsx(
 | 
			
		||||
              checked ? 'translate-x-5' : 'translate-x-0',
 | 
			
		||||
              'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out'
 | 
			
		||||
            )}
 | 
			
		||||
          />
 | 
			
		||||
        </Switch>
 | 
			
		||||
      </Tooltip>
 | 
			
		||||
      <Switch.Label as="span" className="ml-3">
 | 
			
		||||
        <span className="text-sm font-medium text-gray-900">{label}</span>
 | 
			
		||||
        <span
 | 
			
		||||
          className={clsx(
 | 
			
		||||
            'text-sm font-medium text-gray-900',
 | 
			
		||||
            disabled ? 'cursor-not-allowed opacity-50' : ''
 | 
			
		||||
          )}
 | 
			
		||||
        >
 | 
			
		||||
          {label}
 | 
			
		||||
        </span>
 | 
			
		||||
      </Switch.Label>
 | 
			
		||||
    </Switch.Group>
 | 
			
		||||
  )
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user