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]: {
|
[key in notification_preference]: {
|
||||||
simple: string
|
simple: string
|
||||||
detailed: string
|
detailed: string
|
||||||
|
necessary?: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const NOTIFICATION_DESCRIPTIONS: notification_descriptions = {
|
export const NOTIFICATION_DESCRIPTIONS: notification_descriptions = {
|
||||||
|
@ -208,8 +209,9 @@ export const NOTIFICATION_DESCRIPTIONS: notification_descriptions = {
|
||||||
detailed: 'Bonuses for unique predictors on your markets',
|
detailed: 'Bonuses for unique predictors on your markets',
|
||||||
},
|
},
|
||||||
your_contract_closed: {
|
your_contract_closed: {
|
||||||
simple: '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',
|
detailed: 'Your market has closed and you need to resolve it (necessary)',
|
||||||
|
necessary: true,
|
||||||
},
|
},
|
||||||
all_comments_on_watched_markets: {
|
all_comments_on_watched_markets: {
|
||||||
simple: 'All new comments',
|
simple: 'All new comments',
|
||||||
|
@ -235,6 +237,11 @@ export const NOTIFICATION_DESCRIPTIONS: notification_descriptions = {
|
||||||
simple: `Only on markets you're invested in`,
|
simple: `Only on markets you're invested in`,
|
||||||
detailed: `Answers on markets that you're watching and that 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 = {
|
export type BettingStreakData = {
|
||||||
|
|
|
@ -53,6 +53,9 @@ export type notification_preferences = {
|
||||||
profit_loss_updates: notification_destination_types[]
|
profit_loss_updates: notification_destination_types[]
|
||||||
onboarding_flow: notification_destination_types[]
|
onboarding_flow: notification_destination_types[]
|
||||||
thank_you_for_purchases: 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 = (
|
export const getDefaultNotificationPreferences = (
|
||||||
|
@ -65,7 +68,7 @@ export const getDefaultNotificationPreferences = (
|
||||||
const email = noEmails ? undefined : emailIf ? 'email' : undefined
|
const email = noEmails ? undefined : emailIf ? 'email' : undefined
|
||||||
return filterDefined([browser, email]) as notification_destination_types[]
|
return filterDefined([browser, email]) as notification_destination_types[]
|
||||||
}
|
}
|
||||||
return {
|
const defaults: notification_preferences = {
|
||||||
// Watched Markets
|
// Watched Markets
|
||||||
all_comments_on_watched_markets: constructPref(true, false),
|
all_comments_on_watched_markets: constructPref(true, false),
|
||||||
all_answers_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),
|
probability_updates_on_watched_markets: constructPref(true, false),
|
||||||
thank_you_for_purchases: constructPref(false, false),
|
thank_you_for_purchases: constructPref(false, false),
|
||||||
onboarding_flow: 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
|
// 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]
|
? 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')
|
const unsubscribeEndpoint = getFunctionUrl('unsubscribe')
|
||||||
return {
|
return {
|
||||||
sendToEmail: destinations.includes('email'),
|
sendToEmail: destinations.includes('email') && !optedOutOfEmail,
|
||||||
sendToBrowser: destinations.includes('browser'),
|
sendToBrowser: destinations.includes('browser') && !optedOutOfBrowser,
|
||||||
unsubscribeUrl: `${unsubscribeEndpoint}?id=${privateUser.id}&type=${subscriptionType}`,
|
unsubscribeUrl: `${unsubscribeEndpoint}?id=${privateUser.id}&type=${subscriptionType}`,
|
||||||
urlToManageThisNotification: `${DOMAIN}/notifications?tab=settings§ion=${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 { PrivateUser } from '../../common/user'
|
||||||
import { NOTIFICATION_DESCRIPTIONS } from '../../common/notification'
|
import { NOTIFICATION_DESCRIPTIONS } from '../../common/notification'
|
||||||
import { notification_preference } from '../../common/user-notification-preferences'
|
import { notification_preference } from '../../common/user-notification-preferences'
|
||||||
|
import { getFunctionUrl } from '../../common/api'
|
||||||
|
|
||||||
export const unsubscribe: EndpointDefinition = {
|
export const unsubscribe: EndpointDefinition = {
|
||||||
opts: { method: 'GET', minInstances: 1 },
|
opts: { method: 'GET', minInstances: 1 },
|
||||||
|
@ -20,6 +21,8 @@ export const unsubscribe: EndpointDefinition = {
|
||||||
res.status(400).send('Invalid subscription type parameter.')
|
res.status(400).send('Invalid subscription type parameter.')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
const optOutAllType: notification_preference = 'opt_out_all'
|
||||||
|
const wantsToOptOutAll = notificationSubscriptionType === optOutAllType
|
||||||
|
|
||||||
const user = await getPrivateUser(id)
|
const user = await getPrivateUser(id)
|
||||||
|
|
||||||
|
@ -37,17 +40,22 @@ export const unsubscribe: EndpointDefinition = {
|
||||||
const update: Partial<PrivateUser> = {
|
const update: Partial<PrivateUser> = {
|
||||||
notificationPreferences: {
|
notificationPreferences: {
|
||||||
...user.notificationPreferences,
|
...user.notificationPreferences,
|
||||||
[notificationSubscriptionType]: previousDestinations.filter(
|
[notificationSubscriptionType]: wantsToOptOutAll
|
||||||
(destination) => destination !== 'email'
|
? previousDestinations.push('email')
|
||||||
),
|
: previousDestinations.filter(
|
||||||
|
(destination) => destination !== 'email'
|
||||||
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
await firestore.collection('private-users').doc(id).update(update)
|
await firestore.collection('private-users').doc(id).update(update)
|
||||||
|
const unsubscribeEndpoint = getFunctionUrl('unsubscribe')
|
||||||
|
|
||||||
res.send(
|
const optOutAllUrl = `${unsubscribeEndpoint}?id=${id}&type=${optOutAllType}`
|
||||||
`
|
if (wantsToOptOutAll) {
|
||||||
<!DOCTYPE html>
|
res.send(
|
||||||
|
`
|
||||||
|
<!DOCTYPE html>
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml"
|
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml"
|
||||||
xmlns:o="urn:schemas-microsoft-com:office:office">
|
xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||||
|
|
||||||
|
@ -163,19 +171,6 @@ export const unsubscribe: EndpointDefinition = {
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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>
|
<tr>
|
||||||
<td align="left"
|
<td align="left"
|
||||||
style="font-size:0px;padding:10px 25px;padding-top:0px;padding-bottom:0px;word-break:break-word;">
|
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">
|
data-testid="4XoHRGw1Y">
|
||||||
<span
|
<span
|
||||||
style="color:#000000;font-family:Arial, Helvetica, sans-serif;font-size:18px;">
|
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>
|
</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>
|
</div>
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
|
@ -219,9 +203,193 @@ export const unsubscribe: EndpointDefinition = {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</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>
|
</html>
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
ChevronUpIcon,
|
ChevronUpIcon,
|
||||||
CurrencyDollarIcon,
|
CurrencyDollarIcon,
|
||||||
|
ExclamationIcon,
|
||||||
InboxInIcon,
|
InboxInIcon,
|
||||||
InformationCircleIcon,
|
InformationCircleIcon,
|
||||||
LightBulbIcon,
|
LightBulbIcon,
|
||||||
|
@ -63,6 +64,7 @@ export function NotificationSettings(props: {
|
||||||
'contract_from_followed_user',
|
'contract_from_followed_user',
|
||||||
'unique_bettors_on_your_contract',
|
'unique_bettors_on_your_contract',
|
||||||
'profit_loss_updates',
|
'profit_loss_updates',
|
||||||
|
'opt_out_all',
|
||||||
// TODO: add these
|
// TODO: add these
|
||||||
// biggest winner, here are the rest of your markets
|
// 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: {
|
function NotificationSettingLine(props: {
|
||||||
description: string
|
description: string
|
||||||
subscriptionTypeKey: notification_preference
|
subscriptionTypeKey: notification_preference
|
||||||
destinations: notification_destination_types[]
|
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 previousInAppValue = destinations.includes('browser')
|
||||||
const previousEmailValue = destinations.includes('email')
|
const previousEmailValue = destinations.includes('email')
|
||||||
const [inAppEnabled, setInAppEnabled] = useState(previousInAppValue)
|
const [inAppEnabled, setInAppEnabled] = useState(previousInAppValue)
|
||||||
const [emailEnabled, setEmailEnabled] = useState(previousEmailValue)
|
const [emailEnabled, setEmailEnabled] = useState(previousEmailValue)
|
||||||
|
const [error, setError] = useState<string>('')
|
||||||
const loading = 'Changing Notifications Settings'
|
const loading = 'Changing Notifications Settings'
|
||||||
const success = 'Changed Notification Settings!'
|
const success = 'Changed Notification Settings!'
|
||||||
const highlight = navigateToSection === subscriptionTypeKey
|
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) => {
|
const changeSetting = (setting: 'browser' | 'email', newValue: boolean) => {
|
||||||
toast
|
toast
|
||||||
.promise(
|
.promise(
|
||||||
|
@ -212,18 +250,21 @@ export function NotificationSettings(props: {
|
||||||
{!browserDisabled.includes(subscriptionTypeKey) && (
|
{!browserDisabled.includes(subscriptionTypeKey) && (
|
||||||
<SwitchSetting
|
<SwitchSetting
|
||||||
checked={inAppEnabled}
|
checked={inAppEnabled}
|
||||||
onChange={(newVal) => changeSetting('browser', newVal)}
|
onChange={(newVal) => attemptToChangeSetting('browser', newVal)}
|
||||||
label={'Web'}
|
label={'Web'}
|
||||||
|
disabled={optOutAll.includes('browser')}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{emailsEnabled.includes(subscriptionTypeKey) && (
|
{emailsEnabled.includes(subscriptionTypeKey) && (
|
||||||
<SwitchSetting
|
<SwitchSetting
|
||||||
checked={emailEnabled}
|
checked={emailEnabled}
|
||||||
onChange={(newVal) => changeSetting('email', newVal)}
|
onChange={(newVal) => attemptToChangeSetting('email', newVal)}
|
||||||
label={'Email'}
|
label={'Email'}
|
||||||
|
disabled={optOutAll.includes('email')}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Row>
|
</Row>
|
||||||
|
{error && <span className={'text-error'}>{error}</span>}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
)
|
)
|
||||||
|
@ -283,6 +324,11 @@ export function NotificationSettings(props: {
|
||||||
subType as notification_preference
|
subType as notification_preference
|
||||||
)}
|
)}
|
||||||
description={NOTIFICATION_DESCRIPTIONS[subType].simple}
|
description={NOTIFICATION_DESCRIPTIONS[subType].simple}
|
||||||
|
optOutAll={
|
||||||
|
subType === 'opt_out_all' || subType === 'your_contract_closed'
|
||||||
|
? []
|
||||||
|
: getUsersSavedPreference('opt_out_all')
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -332,6 +378,10 @@ export function NotificationSettings(props: {
|
||||||
icon={<InboxInIcon className={'h-6 w-6'} />}
|
icon={<InboxInIcon className={'h-6 w-6'} />}
|
||||||
data={generalOther}
|
data={generalOther}
|
||||||
/>
|
/>
|
||||||
|
<Section
|
||||||
|
icon={<ExclamationIcon className={'h-6 w-6'} />}
|
||||||
|
data={optOut}
|
||||||
|
/>
|
||||||
<WatchMarketModal open={showWatchModal} setOpen={setShowWatchModal} />
|
<WatchMarketModal open={showWatchModal} setOpen={setShowWatchModal} />
|
||||||
</Col>
|
</Col>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,33 +1,52 @@
|
||||||
import { Switch } from '@headlessui/react'
|
import { Switch } from '@headlessui/react'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { Tooltip } from 'web/components/tooltip'
|
||||||
|
|
||||||
export const SwitchSetting = (props: {
|
export const SwitchSetting = (props: {
|
||||||
checked: boolean
|
checked: boolean
|
||||||
onChange: (checked: boolean) => void
|
onChange: (checked: boolean) => void
|
||||||
label: string
|
label: string
|
||||||
|
disabled: boolean
|
||||||
}) => {
|
}) => {
|
||||||
const { checked, onChange, label } = props
|
const { checked, onChange, label, disabled } = props
|
||||||
return (
|
return (
|
||||||
<Switch.Group as="div" className="flex items-center">
|
<Switch.Group as="div" className="flex items-center">
|
||||||
<Switch
|
<Tooltip
|
||||||
checked={checked}
|
text={
|
||||||
onChange={onChange}
|
disabled
|
||||||
className={clsx(
|
? `You are opted out of all ${label} notifications. Go to the Opt Out section to undo this setting.`
|
||||||
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'
|
}
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<span
|
<Switch
|
||||||
aria-hidden="true"
|
checked={checked}
|
||||||
|
onChange={onChange}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
checked ? 'translate-x-5' : 'translate-x-0',
|
checked ? 'bg-indigo-600' : 'bg-gray-200',
|
||||||
'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out'
|
'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' : ''
|
||||||
)}
|
)}
|
||||||
/>
|
disabled={disabled}
|
||||||
</Switch>
|
>
|
||||||
|
<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">
|
<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.Label>
|
||||||
</Switch.Group>
|
</Switch.Group>
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user