From 3efd968058176905018cdd0af8beab9894a187fc Mon Sep 17 00:00:00 2001 From: Ian Philips Date: Wed, 14 Sep 2022 17:17:32 -0600 Subject: [PATCH] Allow one-click unsubscribe, slight refactor --- common/notification.ts | 219 ++++++++---- common/user-notification-preferences.ts | 243 +++++++++++++ common/user.ts | 174 +--------- functions/src/create-notification.ts | 24 +- functions/src/create-user.ts | 9 +- functions/src/email-templates/500-mana.html | 321 ------------------ .../src/email-templates/creating-market.html | 2 +- .../email-templates/interesting-markets.html | 2 +- .../market-answer-comment.html | 2 +- .../src/email-templates/market-answer.html | 2 +- .../src/email-templates/market-close.html | 2 +- .../src/email-templates/market-comment.html | 2 +- .../market-resolved-no-bets.html | 2 +- .../src/email-templates/market-resolved.html | 2 +- .../new-market-from-followed-user.html | 2 +- .../email-templates/new-unique-bettor.html | 2 +- .../email-templates/new-unique-bettors.html | 2 +- functions/src/email-templates/one-week.html | 2 +- functions/src/email-templates/thank-you.html | 2 +- functions/src/email-templates/welcome.html | 2 +- functions/src/emails.ts | 61 ++-- .../create-new-notification-preferences.ts | 4 +- functions/src/scripts/create-private-users.ts | 5 +- functions/src/unsubscribe.ts | 252 +++++++++++--- web/components/notification-settings.tsx | 132 ++++--- 25 files changed, 723 insertions(+), 749 deletions(-) create mode 100644 common/user-notification-preferences.ts delete mode 100644 functions/src/email-templates/500-mana.html diff --git a/common/notification.ts b/common/notification.ts index affa33cb..c34f5b9c 100644 --- a/common/notification.ts +++ b/common/notification.ts @@ -1,5 +1,4 @@ -import { notification_subscription_types, PrivateUser } from './user' -import { DOMAIN } from './envs/constants' +import { notification_preference } from './user-notification-preferences' export type Notification = { id: string @@ -29,6 +28,7 @@ export type Notification = { isSeenOnHref?: string } + export type notification_source_types = | 'contract' | 'comment' @@ -54,7 +54,7 @@ export type notification_source_update_types = | 'deleted' | 'closed' -/* Optional - if possible use a keyof notification_subscription_types */ +/* Optional - if possible use a notification_preference */ export type notification_reason_types = | 'tagged_user' | 'on_new_follow' @@ -92,75 +92,152 @@ export type notification_reason_types = | 'your_contract_closed' | 'subsidized_your_market' -// Adding a new key:value here is optional, you can just use a key of notification_subscription_types -// You might want to add a key:value here if there will be multiple notification reasons that map to the same -// subscription type, i.e. 'comment_on_contract_you_follow' and 'comment_on_contract_with_users_answer' both map to -// 'all_comments_on_watched_markets' subscription type -// TODO: perhaps better would be to map notification_subscription_types to arrays of notification_reason_types -export const notificationReasonToSubscriptionType: Partial< - Record -> = { - you_referred_user: 'referral_bonuses', - user_joined_to_bet_on_your_market: 'referral_bonuses', - tip_received: 'tips_on_your_comments', - bet_fill: 'limit_order_fills', - user_joined_from_your_group_invite: 'referral_bonuses', - challenge_accepted: 'limit_order_fills', - betting_streak_incremented: 'betting_streaks', - liked_and_tipped_your_contract: 'tips_on_your_markets', - comment_on_your_contract: 'all_comments_on_my_markets', - answer_on_your_contract: 'all_answers_on_my_markets', - comment_on_contract_you_follow: 'all_comments_on_watched_markets', - answer_on_contract_you_follow: 'all_answers_on_watched_markets', - update_on_contract_you_follow: 'market_updates_on_watched_markets', - resolution_on_contract_you_follow: 'resolutions_on_watched_markets', - comment_on_contract_with_users_shares_in: - 'all_comments_on_contracts_with_shares_in_on_watched_markets', - answer_on_contract_with_users_shares_in: - 'all_answers_on_contracts_with_shares_in_on_watched_markets', - update_on_contract_with_users_shares_in: - 'market_updates_on_watched_markets_with_shares_in', - resolution_on_contract_with_users_shares_in: - 'resolutions_on_watched_markets_with_shares_in', - comment_on_contract_with_users_answer: 'all_comments_on_watched_markets', - update_on_contract_with_users_answer: 'market_updates_on_watched_markets', - resolution_on_contract_with_users_answer: 'resolutions_on_watched_markets', - answer_on_contract_with_users_answer: 'all_answers_on_watched_markets', - comment_on_contract_with_users_comment: 'all_comments_on_watched_markets', - answer_on_contract_with_users_comment: 'all_answers_on_watched_markets', - update_on_contract_with_users_comment: 'market_updates_on_watched_markets', - resolution_on_contract_with_users_comment: 'resolutions_on_watched_markets', - reply_to_users_answer: 'all_replies_to_my_answers_on_watched_markets', - reply_to_users_comment: 'all_replies_to_my_comments_on_watched_markets', -} - -export const getDestinationsForUser = async ( - privateUser: PrivateUser, - reason: notification_reason_types | keyof notification_subscription_types -) => { - const notificationSettings = privateUser.notificationPreferences - let destinations - let subscriptionType: keyof notification_subscription_types | undefined - if (Object.keys(notificationSettings).includes(reason)) { - subscriptionType = reason as keyof notification_subscription_types - destinations = notificationSettings[subscriptionType] - } else { - const key = reason as notification_reason_types - subscriptionType = notificationReasonToSubscriptionType[key] - destinations = subscriptionType - ? notificationSettings[subscriptionType] - : [] - } - // const unsubscribeEndpoint = getFunctionUrl('unsubscribe') - return { - sendToEmail: destinations.includes('email'), - sendToBrowser: destinations.includes('browser'), - // unsubscribeUrl: `${unsubscribeEndpoint}?id=${privateUser.id}&type=${subscriptionType}`, - urlToManageThisNotification: `${DOMAIN}/notifications?tab=settings§ion=${subscriptionType}`, - } -} - export type BettingStreakData = { streak: number bonusAmount: number } + +type notification_descriptions = { + [key in notification_preference]: { + simple: string + detailed: string + } +} +export const NOTIFICATION_DESCRIPTIONS: notification_descriptions = { + all_answers_on_my_markets: { + simple: 'Answers on your markets', + detailed: 'Answers on your own markets', + }, + all_comments_on_my_markets: { + simple: 'Comments on your markets', + detailed: 'Comments on your own markets', + }, + answers_by_followed_users_on_watched_markets: { + simple: 'Only answers by users you follow', + detailed: "Only answers by users you follow on markets you're watching", + }, + answers_by_market_creator_on_watched_markets: { + simple: 'Only answers by market creator', + detailed: "Only answers by market creator on markets you're watching", + }, + betting_streaks: { + simple: 'For predictions made over consecutive days', + detailed: 'Bonuses for predictions made over consecutive days', + }, + comments_by_followed_users_on_watched_markets: { + simple: 'Only comments by users you follow', + detailed: + 'Only comments by users that you follow on markets that you watch', + }, + contract_from_followed_user: { + simple: 'New markets from users you follow', + detailed: 'New markets from users you follow', + }, + limit_order_fills: { + simple: 'Limit order fills', + detailed: 'When your limit order is filled by another user', + }, + loan_income: { + simple: 'Automatic loans from your predictions in unresolved markets', + detailed: + 'Automatic loans from your predictions that are locked in unresolved markets', + }, + market_updates_on_watched_markets: { + simple: 'All creator updates', + detailed: 'All market updates made by the creator', + }, + market_updates_on_watched_markets_with_shares_in: { + simple: "Only creator updates on markets that you're invested in", + detailed: + "Only updates made by the creator on markets that you're invested in", + }, + on_new_follow: { + simple: 'A user followed you', + detailed: 'A user followed you', + }, + onboarding_flow: { + simple: 'Emails to help you get started using Manifold', + detailed: 'Emails to help you learn how to use Manifold', + }, + probability_updates_on_watched_markets: { + simple: 'Large changes in probability on markets that you watch', + detailed: 'Large changes in probability on markets that you watch', + }, + profit_loss_updates: { + simple: 'Weekly profit and loss updates', + detailed: 'Weekly profit and loss updates', + }, + referral_bonuses: { + simple: 'For referring new users', + detailed: 'Bonuses you receive from referring a new user', + }, + resolutions_on_watched_markets: { + simple: 'All market resolutions', + detailed: "All resolutions on markets that you're watching", + }, + resolutions_on_watched_markets_with_shares_in: { + simple: "Only market resolutions that you're invested in", + detailed: + "Only resolutions of markets you're watching and that you're invested in", + }, + subsidized_your_market: { + simple: 'Your market was subsidized', + detailed: 'When someone subsidizes your market', + }, + tagged_user: { + simple: 'A user tagged you', + detailed: 'When another use tags you', + }, + thank_you_for_purchases: { + simple: 'Thank you notes for your purchases', + detailed: 'Thank you notes for your purchases', + }, + tipped_comments_on_watched_markets: { + simple: 'Only highly tipped comments on markets that you watch', + detailed: 'Only highly tipped comments on markets that you watch', + }, + tips_on_your_comments: { + simple: 'Tips on your comments', + detailed: 'Tips on your comments', + }, + tips_on_your_markets: { + simple: 'Tips/Likes on your markets', + detailed: 'Tips/Likes on your markets', + }, + trending_markets: { + simple: 'Weekly interesting markets', + detailed: 'Weekly interesting markets', + }, + unique_bettors_on_your_contract: { + simple: 'For unique predictors on your markets', + 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', + }, + all_comments_on_watched_markets: { + simple: 'All new comments', + detailed: 'All new comments on markets you follow', + }, + all_comments_on_contracts_with_shares_in_on_watched_markets: { + simple: `Only on markets you're invested in`, + detailed: `Comments on markets that you're watching and you're invested in`, + }, + all_replies_to_my_comments_on_watched_markets: { + simple: 'Only replies to your comments', + detailed: "Only replies to your comments on markets you're watching", + }, + all_replies_to_my_answers_on_watched_markets: { + simple: 'Only replies to your answers', + detailed: "Only replies to your answers on markets you're watching", + }, + all_answers_on_watched_markets: { + simple: 'All new answers', + detailed: "All new answers on markets you're watching", + }, + all_answers_on_contracts_with_shares_in_on_watched_markets: { + simple: `Only on markets you're invested in`, + detailed: `Answers on markets that you're watching and that you're invested in`, + }, +} diff --git a/common/user-notification-preferences.ts b/common/user-notification-preferences.ts new file mode 100644 index 00000000..e2402ea9 --- /dev/null +++ b/common/user-notification-preferences.ts @@ -0,0 +1,243 @@ +import { filterDefined } from './util/array' +import { notification_reason_types } from './notification' +import { getFunctionUrl } from './api' +import { DOMAIN } from './envs/constants' +import { PrivateUser } from './user' + +export type notification_destination_types = 'email' | 'browser' +export type notification_preference = keyof notification_preferences +export type notification_preferences = { + // Watched Markets + all_comments_on_watched_markets: notification_destination_types[] + all_answers_on_watched_markets: notification_destination_types[] + + // Comments + tipped_comments_on_watched_markets: notification_destination_types[] + comments_by_followed_users_on_watched_markets: notification_destination_types[] + all_replies_to_my_comments_on_watched_markets: notification_destination_types[] + all_replies_to_my_answers_on_watched_markets: notification_destination_types[] + all_comments_on_contracts_with_shares_in_on_watched_markets: notification_destination_types[] + + // Answers + answers_by_followed_users_on_watched_markets: notification_destination_types[] + answers_by_market_creator_on_watched_markets: notification_destination_types[] + all_answers_on_contracts_with_shares_in_on_watched_markets: notification_destination_types[] + + // On users' markets + your_contract_closed: notification_destination_types[] + all_comments_on_my_markets: notification_destination_types[] + all_answers_on_my_markets: notification_destination_types[] + subsidized_your_market: notification_destination_types[] + + // Market updates + resolutions_on_watched_markets: notification_destination_types[] + resolutions_on_watched_markets_with_shares_in: notification_destination_types[] + market_updates_on_watched_markets: notification_destination_types[] + market_updates_on_watched_markets_with_shares_in: notification_destination_types[] + probability_updates_on_watched_markets: notification_destination_types[] + + // Balance Changes + loan_income: notification_destination_types[] + betting_streaks: notification_destination_types[] + referral_bonuses: notification_destination_types[] + unique_bettors_on_your_contract: notification_destination_types[] + tips_on_your_comments: notification_destination_types[] + tips_on_your_markets: notification_destination_types[] + limit_order_fills: notification_destination_types[] + + // General + tagged_user: notification_destination_types[] + on_new_follow: notification_destination_types[] + contract_from_followed_user: notification_destination_types[] + trending_markets: notification_destination_types[] + profit_loss_updates: notification_destination_types[] + onboarding_flow: notification_destination_types[] + thank_you_for_purchases: notification_destination_types[] +} + +export const getDefaultNotificationPreferences = ( + userId: string, + privateUser?: PrivateUser, + noEmails?: boolean +) => { + const { + unsubscribedFromCommentEmails, + unsubscribedFromAnswerEmails, + unsubscribedFromResolutionEmails, + unsubscribedFromWeeklyTrendingEmails, + unsubscribedFromGenericEmails, + } = privateUser || {} + + const constructPref = (browserIf: boolean, emailIf: boolean) => { + const browser = browserIf ? 'browser' : undefined + const email = noEmails ? undefined : emailIf ? 'email' : undefined + return filterDefined([browser, email]) as notification_destination_types[] + } + return { + // Watched Markets + all_comments_on_watched_markets: constructPref( + true, + !unsubscribedFromCommentEmails + ), + all_answers_on_watched_markets: constructPref( + true, + !unsubscribedFromAnswerEmails + ), + + // Comments + tips_on_your_comments: constructPref(true, !unsubscribedFromCommentEmails), + comments_by_followed_users_on_watched_markets: constructPref(true, false), + all_replies_to_my_comments_on_watched_markets: constructPref( + true, + !unsubscribedFromCommentEmails + ), + all_replies_to_my_answers_on_watched_markets: constructPref( + true, + !unsubscribedFromCommentEmails + ), + all_comments_on_contracts_with_shares_in_on_watched_markets: constructPref( + true, + !unsubscribedFromCommentEmails + ), + + // Answers + answers_by_followed_users_on_watched_markets: constructPref( + true, + !unsubscribedFromAnswerEmails + ), + answers_by_market_creator_on_watched_markets: constructPref( + true, + !unsubscribedFromAnswerEmails + ), + all_answers_on_contracts_with_shares_in_on_watched_markets: constructPref( + true, + !unsubscribedFromAnswerEmails + ), + + // On users' markets + your_contract_closed: constructPref( + true, + !unsubscribedFromResolutionEmails + ), // High priority + all_comments_on_my_markets: constructPref( + true, + !unsubscribedFromCommentEmails + ), + all_answers_on_my_markets: constructPref( + true, + !unsubscribedFromAnswerEmails + ), + subsidized_your_market: constructPref(true, true), + + // Market updates + resolutions_on_watched_markets: constructPref( + true, + !unsubscribedFromResolutionEmails + ), + market_updates_on_watched_markets: constructPref(true, false), + market_updates_on_watched_markets_with_shares_in: constructPref( + true, + false + ), + resolutions_on_watched_markets_with_shares_in: constructPref( + true, + !unsubscribedFromResolutionEmails + ), + + //Balance Changes + loan_income: constructPref(true, false), + betting_streaks: constructPref(true, false), + referral_bonuses: constructPref(true, true), + unique_bettors_on_your_contract: constructPref(true, false), + tipped_comments_on_watched_markets: constructPref( + true, + !unsubscribedFromCommentEmails + ), + tips_on_your_markets: constructPref(true, true), + limit_order_fills: constructPref(true, false), + + // General + tagged_user: constructPref(true, true), + on_new_follow: constructPref(true, true), + contract_from_followed_user: constructPref(true, true), + trending_markets: constructPref( + false, + !unsubscribedFromWeeklyTrendingEmails + ), + profit_loss_updates: constructPref(false, true), + probability_updates_on_watched_markets: constructPref(true, false), + thank_you_for_purchases: constructPref( + false, + !unsubscribedFromGenericEmails + ), + onboarding_flow: constructPref(false, !unsubscribedFromGenericEmails), + } as notification_preferences +} + +// Adding a new key:value here is optional, you can just use a key of notification_subscription_types +// You might want to add a key:value here if there will be multiple notification reasons that map to the same +// subscription type, i.e. 'comment_on_contract_you_follow' and 'comment_on_contract_with_users_answer' both map to +// 'all_comments_on_watched_markets' subscription type +// TODO: perhaps better would be to map notification_subscription_types to arrays of notification_reason_types +const notificationReasonToSubscriptionType: Partial< + Record +> = { + you_referred_user: 'referral_bonuses', + user_joined_to_bet_on_your_market: 'referral_bonuses', + tip_received: 'tips_on_your_comments', + bet_fill: 'limit_order_fills', + user_joined_from_your_group_invite: 'referral_bonuses', + challenge_accepted: 'limit_order_fills', + betting_streak_incremented: 'betting_streaks', + liked_and_tipped_your_contract: 'tips_on_your_markets', + comment_on_your_contract: 'all_comments_on_my_markets', + answer_on_your_contract: 'all_answers_on_my_markets', + comment_on_contract_you_follow: 'all_comments_on_watched_markets', + answer_on_contract_you_follow: 'all_answers_on_watched_markets', + update_on_contract_you_follow: 'market_updates_on_watched_markets', + resolution_on_contract_you_follow: 'resolutions_on_watched_markets', + comment_on_contract_with_users_shares_in: + 'all_comments_on_contracts_with_shares_in_on_watched_markets', + answer_on_contract_with_users_shares_in: + 'all_answers_on_contracts_with_shares_in_on_watched_markets', + update_on_contract_with_users_shares_in: + 'market_updates_on_watched_markets_with_shares_in', + resolution_on_contract_with_users_shares_in: + 'resolutions_on_watched_markets_with_shares_in', + comment_on_contract_with_users_answer: 'all_comments_on_watched_markets', + update_on_contract_with_users_answer: 'market_updates_on_watched_markets', + resolution_on_contract_with_users_answer: 'resolutions_on_watched_markets', + answer_on_contract_with_users_answer: 'all_answers_on_watched_markets', + comment_on_contract_with_users_comment: 'all_comments_on_watched_markets', + answer_on_contract_with_users_comment: 'all_answers_on_watched_markets', + update_on_contract_with_users_comment: 'market_updates_on_watched_markets', + resolution_on_contract_with_users_comment: 'resolutions_on_watched_markets', + reply_to_users_answer: 'all_replies_to_my_answers_on_watched_markets', + reply_to_users_comment: 'all_replies_to_my_comments_on_watched_markets', +} + +export const getNotificationDestinationsForUser = ( + privateUser: PrivateUser, + reason: notification_reason_types | notification_preference +) => { + const notificationSettings = privateUser.notificationPreferences + let destinations + let subscriptionType: notification_preference | undefined + if (Object.keys(notificationSettings).includes(reason)) { + subscriptionType = reason as notification_preference + destinations = notificationSettings[subscriptionType] + } else { + const key = reason as notification_reason_types + subscriptionType = notificationReasonToSubscriptionType[key] + destinations = subscriptionType + ? notificationSettings[subscriptionType] + : [] + } + const unsubscribeEndpoint = getFunctionUrl('unsubscribe') + return { + sendToEmail: destinations.includes('email'), + sendToBrowser: destinations.includes('browser'), + unsubscribeUrl: `${unsubscribeEndpoint}?id=${privateUser.id}&type=${subscriptionType}`, + urlToManageThisNotification: `${DOMAIN}/notifications?tab=settings§ion=${subscriptionType}`, + } +} diff --git a/common/user.ts b/common/user.ts index 7bd89906..16a2b437 100644 --- a/common/user.ts +++ b/common/user.ts @@ -1,4 +1,4 @@ -import { filterDefined } from './util/array' +import { notification_preferences } from './user-notification-preferences' export type User = { id: string @@ -65,7 +65,7 @@ export type PrivateUser = { initialDeviceToken?: string initialIpAddress?: string apiKey?: string - notificationPreferences: notification_subscription_types + notificationPreferences: notification_preferences twitchInfo?: { twitchName: string controlToken: string @@ -73,57 +73,6 @@ export type PrivateUser = { } } -export type notification_destination_types = 'email' | 'browser' -export type notification_subscription_types = { - // Watched Markets - all_comments_on_watched_markets: notification_destination_types[] - all_answers_on_watched_markets: notification_destination_types[] - - // Comments - tipped_comments_on_watched_markets: notification_destination_types[] - comments_by_followed_users_on_watched_markets: notification_destination_types[] - all_replies_to_my_comments_on_watched_markets: notification_destination_types[] - all_replies_to_my_answers_on_watched_markets: notification_destination_types[] - all_comments_on_contracts_with_shares_in_on_watched_markets: notification_destination_types[] - - // Answers - answers_by_followed_users_on_watched_markets: notification_destination_types[] - answers_by_market_creator_on_watched_markets: notification_destination_types[] - all_answers_on_contracts_with_shares_in_on_watched_markets: notification_destination_types[] - - // On users' markets - your_contract_closed: notification_destination_types[] - all_comments_on_my_markets: notification_destination_types[] - all_answers_on_my_markets: notification_destination_types[] - subsidized_your_market: notification_destination_types[] - - // Market updates - resolutions_on_watched_markets: notification_destination_types[] - resolutions_on_watched_markets_with_shares_in: notification_destination_types[] - market_updates_on_watched_markets: notification_destination_types[] - market_updates_on_watched_markets_with_shares_in: notification_destination_types[] - probability_updates_on_watched_markets: notification_destination_types[] - - // Balance Changes - loan_income: notification_destination_types[] - betting_streaks: notification_destination_types[] - referral_bonuses: notification_destination_types[] - unique_bettors_on_your_contract: notification_destination_types[] - tips_on_your_comments: notification_destination_types[] - tips_on_your_markets: notification_destination_types[] - limit_order_fills: notification_destination_types[] - - // General - tagged_user: notification_destination_types[] - on_new_follow: notification_destination_types[] - contract_from_followed_user: notification_destination_types[] - trending_markets: notification_destination_types[] - profit_loss_updates: notification_destination_types[] - onboarding_flow: notification_destination_types[] - thank_you_for_purchases: notification_destination_types[] -} -export type notification_subscribe_types = 'all' | 'less' | 'none' - export type PortfolioMetrics = { investmentValue: number balance: number @@ -134,122 +83,3 @@ export type PortfolioMetrics = { export const MANIFOLD_USERNAME = 'ManifoldMarkets' export const MANIFOLD_AVATAR_URL = 'https://manifold.markets/logo-bg-white.png' - -export const getDefaultNotificationSettings = ( - userId: string, - privateUser?: PrivateUser, - noEmails?: boolean -) => { - const { - unsubscribedFromCommentEmails, - unsubscribedFromAnswerEmails, - unsubscribedFromResolutionEmails, - unsubscribedFromWeeklyTrendingEmails, - unsubscribedFromGenericEmails, - } = privateUser || {} - - const constructPref = (browserIf: boolean, emailIf: boolean) => { - const browser = browserIf ? 'browser' : undefined - const email = noEmails ? undefined : emailIf ? 'email' : undefined - return filterDefined([browser, email]) as notification_destination_types[] - } - return { - // Watched Markets - all_comments_on_watched_markets: constructPref( - true, - !unsubscribedFromCommentEmails - ), - all_answers_on_watched_markets: constructPref( - true, - !unsubscribedFromAnswerEmails - ), - - // Comments - tips_on_your_comments: constructPref(true, !unsubscribedFromCommentEmails), - comments_by_followed_users_on_watched_markets: constructPref(true, false), - all_replies_to_my_comments_on_watched_markets: constructPref( - true, - !unsubscribedFromCommentEmails - ), - all_replies_to_my_answers_on_watched_markets: constructPref( - true, - !unsubscribedFromCommentEmails - ), - all_comments_on_contracts_with_shares_in_on_watched_markets: constructPref( - true, - !unsubscribedFromCommentEmails - ), - - // Answers - answers_by_followed_users_on_watched_markets: constructPref( - true, - !unsubscribedFromAnswerEmails - ), - answers_by_market_creator_on_watched_markets: constructPref( - true, - !unsubscribedFromAnswerEmails - ), - all_answers_on_contracts_with_shares_in_on_watched_markets: constructPref( - true, - !unsubscribedFromAnswerEmails - ), - - // On users' markets - your_contract_closed: constructPref( - true, - !unsubscribedFromResolutionEmails - ), // High priority - all_comments_on_my_markets: constructPref( - true, - !unsubscribedFromCommentEmails - ), - all_answers_on_my_markets: constructPref( - true, - !unsubscribedFromAnswerEmails - ), - subsidized_your_market: constructPref(true, true), - - // Market updates - resolutions_on_watched_markets: constructPref( - true, - !unsubscribedFromResolutionEmails - ), - market_updates_on_watched_markets: constructPref(true, false), - market_updates_on_watched_markets_with_shares_in: constructPref( - true, - false - ), - resolutions_on_watched_markets_with_shares_in: constructPref( - true, - !unsubscribedFromResolutionEmails - ), - - //Balance Changes - loan_income: constructPref(true, false), - betting_streaks: constructPref(true, false), - referral_bonuses: constructPref(true, true), - unique_bettors_on_your_contract: constructPref(true, false), - tipped_comments_on_watched_markets: constructPref( - true, - !unsubscribedFromCommentEmails - ), - tips_on_your_markets: constructPref(true, true), - limit_order_fills: constructPref(true, false), - - // General - tagged_user: constructPref(true, true), - on_new_follow: constructPref(true, true), - contract_from_followed_user: constructPref(true, true), - trending_markets: constructPref( - false, - !unsubscribedFromWeeklyTrendingEmails - ), - profit_loss_updates: constructPref(false, true), - probability_updates_on_watched_markets: constructPref(true, false), - thank_you_for_purchases: constructPref( - false, - !unsubscribedFromGenericEmails - ), - onboarding_flow: constructPref(false, !unsubscribedFromGenericEmails), - } as notification_subscription_types -} diff --git a/functions/src/create-notification.ts b/functions/src/create-notification.ts index 34a8f218..ba9fa5c4 100644 --- a/functions/src/create-notification.ts +++ b/functions/src/create-notification.ts @@ -1,7 +1,6 @@ import * as admin from 'firebase-admin' import { BettingStreakData, - getDestinationsForUser, Notification, notification_reason_types, } from '../../common/notification' @@ -27,6 +26,7 @@ import { sendNewUniqueBettorsEmail, } from './emails' import { filterDefined } from '../../common/util/array' +import { getNotificationDestinationsForUser } from '../../common/user-notification-preferences' const firestore = admin.firestore() type recipients_to_reason_texts = { @@ -66,7 +66,7 @@ export const createNotification = async ( const { reason } = userToReasonTexts[userId] const privateUser = await getPrivateUser(userId) if (!privateUser) continue - const { sendToBrowser, sendToEmail } = await getDestinationsForUser( + const { sendToBrowser, sendToEmail } = getNotificationDestinationsForUser( privateUser, reason ) @@ -236,7 +236,7 @@ export const createCommentOrAnswerOrUpdatedContractNotification = async ( return const privateUser = await getPrivateUser(userId) if (!privateUser) return - const { sendToBrowser, sendToEmail } = await getDestinationsForUser( + const { sendToBrowser, sendToEmail } = getNotificationDestinationsForUser( privateUser, reason ) @@ -468,7 +468,7 @@ export const createTipNotification = async ( ) => { const privateUser = await getPrivateUser(toUser.id) if (!privateUser) return - const { sendToBrowser } = await getDestinationsForUser( + const { sendToBrowser } = getNotificationDestinationsForUser( privateUser, 'tip_received' ) @@ -513,7 +513,7 @@ export const createBetFillNotification = async ( ) => { const privateUser = await getPrivateUser(toUser.id) if (!privateUser) return - const { sendToBrowser } = await getDestinationsForUser( + const { sendToBrowser } = getNotificationDestinationsForUser( privateUser, 'bet_fill' ) @@ -558,7 +558,7 @@ export const createReferralNotification = async ( ) => { const privateUser = await getPrivateUser(toUser.id) if (!privateUser) return - const { sendToBrowser } = await getDestinationsForUser( + const { sendToBrowser } = getNotificationDestinationsForUser( privateUser, 'you_referred_user' ) @@ -612,7 +612,7 @@ export const createLoanIncomeNotification = async ( ) => { const privateUser = await getPrivateUser(toUser.id) if (!privateUser) return - const { sendToBrowser } = await getDestinationsForUser( + const { sendToBrowser } = getNotificationDestinationsForUser( privateUser, 'loan_income' ) @@ -650,7 +650,7 @@ export const createChallengeAcceptedNotification = async ( ) => { const privateUser = await getPrivateUser(challengeCreator.id) if (!privateUser) return - const { sendToBrowser } = await getDestinationsForUser( + const { sendToBrowser } = getNotificationDestinationsForUser( privateUser, 'challenge_accepted' ) @@ -692,7 +692,7 @@ export const createBettingStreakBonusNotification = async ( ) => { const privateUser = await getPrivateUser(user.id) if (!privateUser) return - const { sendToBrowser } = await getDestinationsForUser( + const { sendToBrowser } = getNotificationDestinationsForUser( privateUser, 'betting_streak_incremented' ) @@ -739,7 +739,7 @@ export const createLikeNotification = async ( ) => { const privateUser = await getPrivateUser(toUser.id) if (!privateUser) return - const { sendToBrowser } = await getDestinationsForUser( + const { sendToBrowser } = getNotificationDestinationsForUser( privateUser, 'liked_and_tipped_your_contract' ) @@ -786,7 +786,7 @@ export const createUniqueBettorBonusNotification = async ( ) => { const privateUser = await getPrivateUser(contractCreatorId) if (!privateUser) return - const { sendToBrowser, sendToEmail } = await getDestinationsForUser( + const { sendToBrowser, sendToEmail } = getNotificationDestinationsForUser( privateUser, 'unique_bettors_on_your_contract' ) @@ -876,7 +876,7 @@ export const createNewContractNotification = async ( ) => { const privateUser = await getPrivateUser(userId) if (!privateUser) return - const { sendToBrowser, sendToEmail } = await getDestinationsForUser( + const { sendToBrowser, sendToEmail } = getNotificationDestinationsForUser( privateUser, reason ) diff --git a/functions/src/create-user.ts b/functions/src/create-user.ts index ab5f014a..ab70b4e6 100644 --- a/functions/src/create-user.ts +++ b/functions/src/create-user.ts @@ -1,11 +1,7 @@ import * as admin from 'firebase-admin' import { z } from 'zod' -import { - getDefaultNotificationSettings, - PrivateUser, - User, -} from '../../common/user' +import { PrivateUser, User } from '../../common/user' import { getUser, getUserByUsername, getValues } from './utils' import { randomString } from '../../common/util/random' import { @@ -22,6 +18,7 @@ import { track } from './analytics' import { APIError, newEndpoint, validate } from './api' import { Group } from '../../common/group' import { SUS_STARTING_BALANCE, STARTING_BALANCE } from '../../common/economy' +import { getDefaultNotificationPreferences } from '../../common/user-notification-preferences' const bodySchema = z.object({ deviceToken: z.string().optional(), @@ -83,7 +80,7 @@ export const createuser = newEndpoint(opts, async (req, auth) => { email, initialIpAddress: req.ip, initialDeviceToken: deviceToken, - notificationPreferences: getDefaultNotificationSettings(auth.uid), + notificationPreferences: getDefaultNotificationPreferences(auth.uid), } await firestore.collection('private-users').doc(auth.uid).create(privateUser) diff --git a/functions/src/email-templates/500-mana.html b/functions/src/email-templates/500-mana.html deleted file mode 100644 index c8f6a171..00000000 --- a/functions/src/email-templates/500-mana.html +++ /dev/null @@ -1,321 +0,0 @@ - - - - - Manifold Markets 7th Day Anniversary Gift! - - - - - - - - - - - - - - - -
- -
- - - - - - -
- -
- - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - -
-
-
-

- Hi {{name}},

-
-
-
-

Thanks for - using Manifold Markets. Running low - on mana (M$)? Click the link below to receive a one time gift of M$500!

-
-
-

-
- - - - -
- - - - -
- - Claim M$500 - -
-
-
-
-

Did - you know, besides making correct predictions, there are - plenty of other ways to earn mana?

- -

 

-

Cheers, -

-

David - from Manifold

-

 

-
-
-
-

- -

 

-

Cheers,

-

David from Manifold

-

 

-
-
-
- -
-
- -
- - - -
- -
- - - - diff --git a/functions/src/email-templates/interesting-markets.html b/functions/src/email-templates/interesting-markets.html index 7c3e653d..0cee6269 100644 --- a/functions/src/email-templates/interesting-markets.html +++ b/functions/src/email-templates/interesting-markets.html @@ -443,7 +443,7 @@ click here to manage your notifications. + " target="_blank">click here to unsubscribe from this type of notification.

diff --git a/functions/src/email-templates/market-answer-comment.html b/functions/src/email-templates/market-answer-comment.html index a19aa7c3..4b98730f 100644 --- a/functions/src/email-templates/market-answer-comment.html +++ b/functions/src/email-templates/market-answer-comment.html @@ -529,7 +529,7 @@ click here to manage your notifications. + " target="_blank">click here to unsubscribe from this type of notification.
- - - -
-
- - - - - -
- -
- - - - - - -
- - - - - - - - - -
-
-

This e-mail has been sent to {{name}}, - click here to manage your notifications. -

-
-
-
-
-
-
-
- -
- - - - - - \ No newline at end of file diff --git a/functions/src/email-templates/creating-market.html b/functions/src/email-templates/creating-market.html index c73f7458..bf163f69 100644 --- a/functions/src/email-templates/creating-market.html +++ b/functions/src/email-templates/creating-market.html @@ -494,7 +494,7 @@ click here to manage your notifications. + " target="_blank">click here to unsubscribe from this type of notification.

diff --git a/functions/src/email-templates/market-answer.html b/functions/src/email-templates/market-answer.html index b2d7f727..e3d42b9d 100644 --- a/functions/src/email-templates/market-answer.html +++ b/functions/src/email-templates/market-answer.html @@ -369,7 +369,7 @@ click here to manage your notifications. + " target="_blank">click here to unsubscribe from this type of notification.
diff --git a/functions/src/email-templates/market-close.html b/functions/src/email-templates/market-close.html index ee7976b0..4abd225e 100644 --- a/functions/src/email-templates/market-close.html +++ b/functions/src/email-templates/market-close.html @@ -487,7 +487,7 @@ click here to manage your notifications. + " target="_blank">click here to unsubscribe from this type of notification. diff --git a/functions/src/email-templates/market-comment.html b/functions/src/email-templates/market-comment.html index 23e20dac..ce0669f1 100644 --- a/functions/src/email-templates/market-comment.html +++ b/functions/src/email-templates/market-comment.html @@ -369,7 +369,7 @@ click here to manage your notifications. + " target="_blank">click here to unsubscribe from this type of notification. diff --git a/functions/src/email-templates/market-resolved-no-bets.html b/functions/src/email-templates/market-resolved-no-bets.html index ff5f541f..5d886adf 100644 --- a/functions/src/email-templates/market-resolved-no-bets.html +++ b/functions/src/email-templates/market-resolved-no-bets.html @@ -470,7 +470,7 @@ click here to manage your notifications. + " target="_blank">click here to unsubscribe from this type of notification. diff --git a/functions/src/email-templates/market-resolved.html b/functions/src/email-templates/market-resolved.html index de29a0f1..767202b6 100644 --- a/functions/src/email-templates/market-resolved.html +++ b/functions/src/email-templates/market-resolved.html @@ -502,7 +502,7 @@ click here to manage your notifications. + " target="_blank">click here to unsubscribe from this type of notification. diff --git a/functions/src/email-templates/new-market-from-followed-user.html b/functions/src/email-templates/new-market-from-followed-user.html index 877d554f..49633fb2 100644 --- a/functions/src/email-templates/new-market-from-followed-user.html +++ b/functions/src/email-templates/new-market-from-followed-user.html @@ -318,7 +318,7 @@ click here to manage your notifications. + " target="_blank">click here to unsubscribe from this type of notification.

diff --git a/functions/src/email-templates/new-unique-bettor.html b/functions/src/email-templates/new-unique-bettor.html index 30da8b99..51026121 100644 --- a/functions/src/email-templates/new-unique-bettor.html +++ b/functions/src/email-templates/new-unique-bettor.html @@ -376,7 +376,7 @@ click here to manage your notifications. + " target="_blank">click here to unsubscribe from this type of notification. diff --git a/functions/src/email-templates/new-unique-bettors.html b/functions/src/email-templates/new-unique-bettors.html index eb4c04e2..09c44d03 100644 --- a/functions/src/email-templates/new-unique-bettors.html +++ b/functions/src/email-templates/new-unique-bettors.html @@ -480,7 +480,7 @@ click here to manage your notifications. + " target="_blank">click here to unsubscribe from this type of notification. diff --git a/functions/src/email-templates/one-week.html b/functions/src/email-templates/one-week.html index b8e233d5..e7d14a7e 100644 --- a/functions/src/email-templates/one-week.html +++ b/functions/src/email-templates/one-week.html @@ -283,7 +283,7 @@ click here to manage your notifications. + " target="_blank">click here to unsubscribe from this type of notification.

diff --git a/functions/src/email-templates/thank-you.html b/functions/src/email-templates/thank-you.html index 7ac72d0a..beef11ee 100644 --- a/functions/src/email-templates/thank-you.html +++ b/functions/src/email-templates/thank-you.html @@ -218,7 +218,7 @@ click here to manage your notifications. + " target="_blank">click here to unsubscribe from this type of notification.

diff --git a/functions/src/email-templates/welcome.html b/functions/src/email-templates/welcome.html index dccec695..d6caaa0c 100644 --- a/functions/src/email-templates/welcome.html +++ b/functions/src/email-templates/welcome.html @@ -290,7 +290,7 @@ click here to manage your notifications. + " target="_blank">click here to unsubscribe from this type of notification.

diff --git a/functions/src/emails.ts b/functions/src/emails.ts index bb9f7195..98309ebe 100644 --- a/functions/src/emails.ts +++ b/functions/src/emails.ts @@ -2,11 +2,7 @@ import { DOMAIN } from '../../common/envs/constants' import { Bet } from '../../common/bet' import { getProbability } from '../../common/calculate' import { Contract } from '../../common/contract' -import { - notification_subscription_types, - PrivateUser, - User, -} from '../../common/user' +import { PrivateUser, User } from '../../common/user' import { formatLargeNumber, formatMoney, @@ -18,11 +14,12 @@ import { formatNumericProbability } from '../../common/pseudo-numeric' import { sendTemplateEmail, sendTextEmail } from './send-email' import { getUser } from './utils' import { buildCardUrl, getOpenGraphProps } from '../../common/contract-details' -import { - notification_reason_types, - getDestinationsForUser, -} from '../../common/notification' +import { notification_reason_types } from '../../common/notification' import { Dictionary } from 'lodash' +import { + getNotificationDestinationsForUser, + notification_preference, +} from '../../common/user-notification-preferences' export const sendMarketResolutionEmail = async ( reason: notification_reason_types, @@ -36,8 +33,10 @@ export const sendMarketResolutionEmail = async ( resolutionProbability?: number, resolutions?: { [outcome: string]: number } ) => { - const { sendToEmail, urlToManageThisNotification: unsubscribeUrl } = - await getDestinationsForUser(privateUser, reason) + const { sendToEmail, unsubscribeUrl } = getNotificationDestinationsForUser( + privateUser, + reason + ) if (!privateUser || !privateUser.email || !sendToEmail) return const user = await getUser(privateUser.id) @@ -154,7 +153,7 @@ export const sendWelcomeEmail = async ( const firstName = name.split(' ')[0] const unsubscribeUrl = `${DOMAIN}/notifications?tab=settings§ion=${ - 'onboarding_flow' as keyof notification_subscription_types + 'onboarding_flow' as notification_preference }` return await sendTemplateEmail( @@ -222,7 +221,7 @@ export const sendOneWeekBonusEmail = async ( const firstName = name.split(' ')[0] const unsubscribeUrl = `${DOMAIN}/notifications?tab=settings§ion=${ - 'onboarding_flow' as keyof notification_subscription_types + 'onboarding_flow' as notification_preference }` return await sendTemplateEmail( privateUser.email, @@ -255,7 +254,7 @@ export const sendCreatorGuideEmail = async ( const firstName = name.split(' ')[0] const unsubscribeUrl = `${DOMAIN}/notifications?tab=settings§ion=${ - 'onboarding_flow' as keyof notification_subscription_types + 'onboarding_flow' as notification_preference }` return await sendTemplateEmail( privateUser.email, @@ -289,7 +288,7 @@ export const sendThankYouEmail = async ( const firstName = name.split(' ')[0] const unsubscribeUrl = `${DOMAIN}/notifications?tab=settings§ion=${ - 'thank_you_for_purchases' as keyof notification_subscription_types + 'thank_you_for_purchases' as notification_preference }` return await sendTemplateEmail( @@ -312,8 +311,10 @@ export const sendMarketCloseEmail = async ( privateUser: PrivateUser, contract: Contract ) => { - const { sendToEmail, urlToManageThisNotification: unsubscribeUrl } = - await getDestinationsForUser(privateUser, reason) + const { sendToEmail, unsubscribeUrl } = getNotificationDestinationsForUser( + privateUser, + reason + ) if (!privateUser.email || !sendToEmail) return @@ -350,8 +351,10 @@ export const sendNewCommentEmail = async ( answerText?: string, answerId?: string ) => { - const { sendToEmail, urlToManageThisNotification: unsubscribeUrl } = - await getDestinationsForUser(privateUser, reason) + const { sendToEmail, unsubscribeUrl } = getNotificationDestinationsForUser( + privateUser, + reason + ) if (!privateUser || !privateUser.email || !sendToEmail) return const { question } = contract @@ -425,8 +428,10 @@ export const sendNewAnswerEmail = async ( // Don't send the creator's own answers. if (privateUser.id === creatorId) return - const { sendToEmail, urlToManageThisNotification: unsubscribeUrl } = - await getDestinationsForUser(privateUser, reason) + const { sendToEmail, unsubscribeUrl } = getNotificationDestinationsForUser( + privateUser, + reason + ) if (!privateUser.email || !sendToEmail) return const { question, creatorUsername, slug } = contract @@ -465,7 +470,7 @@ export const sendInterestingMarketsEmail = async ( return const unsubscribeUrl = `${DOMAIN}/notifications?tab=settings§ion=${ - 'trending_markets' as keyof notification_subscription_types + 'trending_markets' as notification_preference }` const { name } = user @@ -516,8 +521,10 @@ export const sendNewFollowedMarketEmail = async ( privateUser: PrivateUser, contract: Contract ) => { - const { sendToEmail, urlToManageThisNotification: unsubscribeUrl } = - await getDestinationsForUser(privateUser, reason) + const { sendToEmail, unsubscribeUrl } = getNotificationDestinationsForUser( + privateUser, + reason + ) if (!privateUser.email || !sendToEmail) return const user = await getUser(privateUser.id) if (!user) return @@ -553,8 +560,10 @@ export const sendNewUniqueBettorsEmail = async ( userBets: Dictionary<[Bet, ...Bet[]]>, bonusAmount: number ) => { - const { sendToEmail, urlToManageThisNotification: unsubscribeUrl } = - await getDestinationsForUser(privateUser, reason) + const { sendToEmail, unsubscribeUrl } = getNotificationDestinationsForUser( + privateUser, + reason + ) if (!privateUser.email || !sendToEmail) return const user = await getUser(privateUser.id) if (!user) return diff --git a/functions/src/scripts/create-new-notification-preferences.ts b/functions/src/scripts/create-new-notification-preferences.ts index 2796f2f7..4ba2e25e 100644 --- a/functions/src/scripts/create-new-notification-preferences.ts +++ b/functions/src/scripts/create-new-notification-preferences.ts @@ -1,8 +1,8 @@ import * as admin from 'firebase-admin' import { initAdmin } from './script-init' -import { getDefaultNotificationSettings } from 'common/user' import { getAllPrivateUsers, isProd } from 'functions/src/utils' +import { getDefaultNotificationPreferences } from 'common/user-notification-preferences' initAdmin() const firestore = admin.firestore() @@ -17,7 +17,7 @@ async function main() { .collection('private-users') .doc(privateUser.id) .update({ - notificationPreferences: getDefaultNotificationSettings( + notificationPreferences: getDefaultNotificationPreferences( privateUser.id, privateUser, disableEmails diff --git a/functions/src/scripts/create-private-users.ts b/functions/src/scripts/create-private-users.ts index 21e117cf..762e801a 100644 --- a/functions/src/scripts/create-private-users.ts +++ b/functions/src/scripts/create-private-users.ts @@ -3,8 +3,9 @@ import * as admin from 'firebase-admin' import { initAdmin } from './script-init' initAdmin() -import { getDefaultNotificationSettings, PrivateUser, User } from 'common/user' +import { PrivateUser, User } from 'common/user' import { STARTING_BALANCE } from 'common/economy' +import { getDefaultNotificationPreferences } from 'common/user-notification-preferences' const firestore = admin.firestore() @@ -21,7 +22,7 @@ async function main() { id: user.id, email, username, - notificationPreferences: getDefaultNotificationSettings(user.id), + notificationPreferences: getDefaultNotificationPreferences(user.id), } if (user.totalDeposits === undefined) { diff --git a/functions/src/unsubscribe.ts b/functions/src/unsubscribe.ts index da7b507f..418282c7 100644 --- a/functions/src/unsubscribe.ts +++ b/functions/src/unsubscribe.ts @@ -1,79 +1,227 @@ import * as admin from 'firebase-admin' import { EndpointDefinition } from './api' -import { getUser } from './utils' +import { getPrivateUser } from './utils' import { PrivateUser } from '../../common/user' +import { NOTIFICATION_DESCRIPTIONS } from '../../common/notification' +import { notification_preference } from '../../common/user-notification-preferences' export const unsubscribe: EndpointDefinition = { opts: { method: 'GET', minInstances: 1 }, handler: async (req, res) => { const id = req.query.id as string - let type = req.query.type as string + const type = req.query.type as string if (!id || !type) { - res.status(400).send('Empty id or type parameter.') + res.status(400).send('Empty id or subscription type parameter.') + return + } + console.log(`Unsubscribing ${id} from ${type}`) + const notificationSubscriptionType = type as notification_preference + if (notificationSubscriptionType === undefined) { + res.status(400).send('Invalid subscription type parameter.') return } - if (type === 'market-resolved') type = 'market-resolve' - - if ( - ![ - 'market-resolve', - 'market-comment', - 'market-answer', - 'generic', - 'weekly-trending', - ].includes(type) - ) { - res.status(400).send('Invalid type parameter.') - return - } - - const user = await getUser(id) + const user = await getPrivateUser(id) if (!user) { res.send('This user is not currently subscribed or does not exist.') return } - const { name } = user + const previousDestinations = + user.notificationPreferences[notificationSubscriptionType] + + console.log(previousDestinations) + const { email } = user const update: Partial = { - ...(type === 'market-resolve' && { - unsubscribedFromResolutionEmails: true, - }), - ...(type === 'market-comment' && { - unsubscribedFromCommentEmails: true, - }), - ...(type === 'market-answer' && { - unsubscribedFromAnswerEmails: true, - }), - ...(type === 'generic' && { - unsubscribedFromGenericEmails: true, - }), - ...(type === 'weekly-trending' && { - unsubscribedFromWeeklyTrendingEmails: true, - }), + notificationPreferences: { + ...user.notificationPreferences, + [notificationSubscriptionType]: previousDestinations.filter( + (destination) => destination !== 'email' + ), + }, } await firestore.collection('private-users').doc(id).update(update) - if (type === 'market-resolve') - res.send( - `${name}, you have been unsubscribed from market resolution emails on Manifold Markets.` - ) - else if (type === 'market-comment') - res.send( - `${name}, you have been unsubscribed from market comment emails on Manifold Markets.` - ) - else if (type === 'market-answer') - res.send( - `${name}, you have been unsubscribed from market answer emails on Manifold Markets.` - ) - else if (type === 'weekly-trending') - res.send( - `${name}, you have been unsubscribed from weekly trending emails on Manifold Markets.` - ) - else res.send(`${name}, you have been unsubscribed.`) + res.send( + ` + + + + + Manifold Markets 7th Day Anniversary Gift! + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + + + +
+ + banner logo + +
+
+

+ Hello!

+
+
+
+

+ + ${email} has been unsubscribed from email notifications related to: + +
+
+ + ${NOTIFICATION_DESCRIPTIONS[notificationSubscriptionType].detailed}. +

+
+
+
+ Click + here + to manage the rest of your notification settings. + +
+ +
+

+
+
+
+
+
+ + +` + ) }, } diff --git a/web/components/notification-settings.tsx b/web/components/notification-settings.tsx index d18896bd..8730ce7f 100644 --- a/web/components/notification-settings.tsx +++ b/web/components/notification-settings.tsx @@ -1,11 +1,7 @@ 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 { PrivateUser } from 'common/user' import { updatePrivateUser } from 'web/lib/firebase/users' import { Col } from 'web/components/layout/col' import { @@ -30,6 +26,11 @@ import { 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 @@ -38,7 +39,7 @@ export function NotificationSettings(props: { const { navigateToSection, privateUser } = props const [showWatchModal, setShowWatchModal] = useState(false) - const emailsEnabled: Array = [ + 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', @@ -74,7 +75,7 @@ export function NotificationSettings(props: { // 'probability_updates_on_watched_markets', // 'limit_order_fills', ] - const browserDisabled: Array = [ + const browserDisabled: Array = [ 'trending_markets', 'profit_loss_updates', 'onboarding_flow', @@ -83,91 +84,82 @@ export function NotificationSettings(props: { type SectionData = { label: string - subscriptionTypeToDescription: { - [key in keyof Partial]: string - } + subscriptionTypes: Partial[] } const comments: SectionData = { label: 'New Comments', - subscriptionTypeToDescription: { - all_comments_on_watched_markets: 'All new comments', - all_comments_on_contracts_with_shares_in_on_watched_markets: `Only on markets you're invested in`, + 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: - 'Only replies to your comments', - all_replies_to_my_answers_on_watched_markets: - 'Only replies to your answers', - // comments_by_followed_users_on_watched_markets: 'By followed users', - }, + 'all_replies_to_my_comments_on_watched_markets', + 'all_replies_to_my_answers_on_watched_markets', + ], } const answers: SectionData = { label: 'New Answers', - subscriptionTypeToDescription: { - all_answers_on_watched_markets: 'All new answers', - all_answers_on_contracts_with_shares_in_on_watched_markets: `Only on markets you're invested in`, - // answers_by_followed_users_on_watched_markets: 'By followed users', - // answers_by_market_creator_on_watched_markets: 'By market creator', - }, + subscriptionTypes: [ + 'all_answers_on_watched_markets', + 'all_answers_on_contracts_with_shares_in_on_watched_markets', + ], } const updates: SectionData = { label: 'Updates & Resolutions', - subscriptionTypeToDescription: { - market_updates_on_watched_markets: 'All creator updates', - market_updates_on_watched_markets_with_shares_in: `Only creator updates on markets you're invested in`, - resolutions_on_watched_markets: 'All market resolutions', - resolutions_on_watched_markets_with_shares_in: `Only market resolutions you're invested in`, - // probability_updates_on_watched_markets: 'Probability updates', - }, + 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', - subscriptionTypeToDescription: { - your_contract_closed: 'Your market has closed (and needs resolution)', - all_comments_on_my_markets: 'Comments on your markets', - all_answers_on_my_markets: 'Answers on your markets', - subsidized_your_market: 'Your market was subsidized', - tips_on_your_markets: 'Likes on your markets', - }, + 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', - subscriptionTypeToDescription: { - betting_streaks: 'Prediction streak bonuses', - referral_bonuses: 'Referral bonuses from referring users', - unique_bettors_on_your_contract: 'Unique bettor bonuses on your markets', - }, + subscriptionTypes: [ + 'betting_streaks', + 'referral_bonuses', + 'unique_bettors_on_your_contract', + ], } const otherBalances: SectionData = { label: 'Other', - subscriptionTypeToDescription: { - loan_income: 'Automatic loans from your profitable bets', - limit_order_fills: 'Limit order fills', - tips_on_your_comments: 'Tips on your comments', - }, + subscriptionTypes: [ + 'loan_income', + 'limit_order_fills', + 'tips_on_your_comments', + ], } const userInteractions: SectionData = { label: 'Users', - subscriptionTypeToDescription: { - tagged_user: 'A user tagged you', - on_new_follow: 'Someone followed you', - contract_from_followed_user: 'New markets created by users you follow', - }, + subscriptionTypes: [ + 'tagged_user', + 'on_new_follow', + 'contract_from_followed_user', + ], } const generalOther: SectionData = { label: 'Other', - subscriptionTypeToDescription: { - trending_markets: 'Weekly interesting markets', - thank_you_for_purchases: 'Thank you notes for your purchases', - onboarding_flow: 'Explanatory emails to help you get started', - // profit_loss_updates: 'Weekly profit/loss updates', - }, + subscriptionTypes: [ + 'trending_markets', + 'thank_you_for_purchases', + 'onboarding_flow', + ], } function NotificationSettingLine(props: { description: string - subscriptionTypeKey: keyof notification_subscription_types + subscriptionTypeKey: notification_preference destinations: notification_destination_types[] }) { const { description, subscriptionTypeKey, destinations } = props @@ -237,9 +229,7 @@ export function NotificationSettings(props: { ) } - const getUsersSavedPreference = ( - key: keyof notification_subscription_types - ) => { + const getUsersSavedPreference = (key: notification_preference) => { return privateUser.notificationPreferences[key] ?? [] } @@ -248,17 +238,17 @@ export function NotificationSettings(props: { data: SectionData }) { const { icon, data } = props - const { label, subscriptionTypeToDescription } = data + const { label, subscriptionTypes } = data const expand = navigateToSection && - Object.keys(subscriptionTypeToDescription).includes(navigateToSection) + Object.keys(subscriptionTypes).includes(navigateToSection) // 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-' + - Object.keys(subscriptionTypeToDescription).join('-'), + Object.keys(subscriptionTypes).join('-'), store: storageStore(safeLocalStorage()), }) @@ -287,13 +277,13 @@ export function NotificationSettings(props: { )} - {Object.entries(subscriptionTypeToDescription).map(([key, value]) => ( + {subscriptionTypes.map((subType) => ( ))}