diff --git a/functions/src/create-notification.ts b/functions/src/create-notification.ts index 84edf715..e2959dda 100644 --- a/functions/src/create-notification.ts +++ b/functions/src/create-notification.ts @@ -8,7 +8,7 @@ import { User } from '../../common/user' import { Contract } from '../../common/contract' import { getPrivateUser, getValues } from './utils' import { Comment } from '../../common/comment' -import { uniq } from 'lodash' +import { groupBy, uniq } from 'lodash' import { Bet, LimitBet } from '../../common/bet' import { Answer } from '../../common/answer' import { getContractBetMetrics } from '../../common/calculate' @@ -23,6 +23,7 @@ import { sendNewAnswerEmail, sendNewCommentEmail, sendNewFollowedMarketEmail, + sendNewUniqueBettorsEmail, } from './emails' import { filterDefined } from '../../common/util/array' const firestore = admin.firestore() @@ -774,44 +775,84 @@ export const createUniqueBettorBonusNotification = async ( txnId: string, contract: Contract, amount: number, + uniqueBettorIds: string[], idempotencyKey: string ) => { - console.log('createUniqueBettorBonusNotification') const privateUser = await getPrivateUser(contractCreatorId) if (!privateUser) return - const { sendToBrowser } = await getDestinationsForUser( + const { sendToBrowser, sendToEmail } = await getDestinationsForUser( privateUser, 'unique_bettors_on_your_contract' ) - if (!sendToBrowser) return - - const notificationRef = firestore - .collection(`/users/${contractCreatorId}/notifications`) - .doc(idempotencyKey) - const notification: Notification = { - id: idempotencyKey, - userId: contractCreatorId, - reason: 'unique_bettors_on_your_contract', - createdTime: Date.now(), - isSeen: false, - sourceId: txnId, - sourceType: 'bonus', - sourceUpdateType: 'created', - sourceUserName: bettor.name, - sourceUserUsername: bettor.username, - sourceUserAvatarUrl: bettor.avatarUrl, - sourceText: amount.toString(), - sourceSlug: contract.slug, - sourceTitle: contract.question, - // Perhaps not necessary, but just in case - sourceContractSlug: contract.slug, - sourceContractId: contract.id, - sourceContractTitle: contract.question, - sourceContractCreatorUsername: contract.creatorUsername, + if (sendToBrowser) { + const notificationRef = firestore + .collection(`/users/${contractCreatorId}/notifications`) + .doc(idempotencyKey) + const notification: Notification = { + id: idempotencyKey, + userId: contractCreatorId, + reason: 'unique_bettors_on_your_contract', + createdTime: Date.now(), + isSeen: false, + sourceId: txnId, + sourceType: 'bonus', + sourceUpdateType: 'created', + sourceUserName: bettor.name, + sourceUserUsername: bettor.username, + sourceUserAvatarUrl: bettor.avatarUrl, + sourceText: amount.toString(), + sourceSlug: contract.slug, + sourceTitle: contract.question, + // Perhaps not necessary, but just in case + sourceContractSlug: contract.slug, + sourceContractId: contract.id, + sourceContractTitle: contract.question, + sourceContractCreatorUsername: contract.creatorUsername, + } + await notificationRef.set(removeUndefinedProps(notification)) } - return await notificationRef.set(removeUndefinedProps(notification)) - // TODO send email notification + if (!sendToEmail) return + const uniqueBettorsExcludingCreator = uniqueBettorIds.filter( + (id) => id !== contractCreatorId + ) + // only send on 1st and 6th bettor + if ( + uniqueBettorsExcludingCreator.length !== 1 && + uniqueBettorsExcludingCreator.length !== 6 + ) + return + const totalNewBettorsToReport = + uniqueBettorsExcludingCreator.length === 1 ? 1 : 5 + + const mostRecentUniqueBettors = await getValues<User>( + firestore + .collection('users') + .where( + 'id', + 'in', + uniqueBettorsExcludingCreator.slice( + uniqueBettorsExcludingCreator.length - totalNewBettorsToReport, + uniqueBettorsExcludingCreator.length + ) + ) + ) + + const bets = await getValues<Bet>( + firestore.collection('contracts').doc(contract.id).collection('bets') + ) + // group bets by bettors + const bettorsToTheirBets = groupBy(bets, (bet) => bet.userId) + await sendNewUniqueBettorsEmail( + 'unique_bettors_on_your_contract', + contractCreatorId, + privateUser, + contract, + uniqueBettorsExcludingCreator.length, + mostRecentUniqueBettors, + bettorsToTheirBets, + Math.round(amount * totalNewBettorsToReport) + ) } export const createNewContractNotification = async ( diff --git a/functions/src/email-templates/new-unique-bettor.html b/functions/src/email-templates/new-unique-bettor.html new file mode 100644 index 00000000..30da8b99 --- /dev/null +++ b/functions/src/email-templates/new-unique-bettor.html @@ -0,0 +1,397 @@ +<!DOCTYPE html> +<html style=" + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 14px; + margin: 0; + "> + +<head> + <meta name="viewport" content="width=device-width" /> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <title>New unique predictors on your market</title> + + <style type="text/css"> + img { + max-width: 100%; + } + + body { + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: none; + width: 100% !important; + height: 100%; + line-height: 1.6em; + } + + body { + background-color: #f6f6f6; + } + + @media only screen and (max-width: 640px) { + body { + padding: 0 !important; + } + + h1 { + font-weight: 800 !important; + margin: 20px 0 5px !important; + } + + h2 { + font-weight: 800 !important; + margin: 20px 0 5px !important; + } + + h3 { + font-weight: 800 !important; + margin: 20px 0 5px !important; + } + + h4 { + font-weight: 800 !important; + margin: 20px 0 5px !important; + } + + h1 { + font-size: 22px !important; + } + + h2 { + font-size: 18px !important; + } + + h3 { + font-size: 16px !important; + } + + .container { + padding: 0 !important; + width: 100% !important; + } + + .content { + padding: 0 !important; + } + + .content-wrap { + padding: 10px !important; + } + + .invoice { + width: 100% !important; + } + } + </style> +</head> + +<body itemscope itemtype="http://schema.org/EmailMessage" style=" + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 14px; + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: none; + width: 100% !important; + height: 100%; + line-height: 1.6em; + background-color: #f6f6f6; + margin: 0; + " bgcolor="#f6f6f6"> + <table class="body-wrap" style=" + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 14px; + width: 100%; + background-color: #f6f6f6; + margin: 0; + " bgcolor="#f6f6f6"> + <tr style=" + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 14px; + margin: 0; + "> + <td style=" + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 14px; + vertical-align: top; + margin: 0; + " valign="top"></td> + <td class="container" width="600" style=" + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 14px; + vertical-align: top; + display: block !important; + max-width: 600px !important; + clear: both !important; + margin: 0 auto; + " valign="top"> + <div class="content" style=" + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 14px; + max-width: 600px; + display: block; + margin: 0 auto; + padding: 20px; + "> + <table class="main" width="100%" cellpadding="0" cellspacing="0" style=" + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 14px; + border-radius: 3px; + background-color: #fff; + margin: 0; + border: 1px solid #e9e9e9; + " bgcolor="#fff"> + <tr style=" + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 14px; + margin: 0; + "> + <td class="content-wrap aligncenter" style=" + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 14px; + vertical-align: top; + text-align: center; + margin: 0; + padding: 20px; + " align="center" valign="top"> + <table width="100%" cellpadding="0" cellspacing="0" style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 14px; + margin: 0; + width: 90%; + "> + <tr style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 14px; + margin: 0; + "> + <td class="content-block" style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 16px; + vertical-align: top; + margin: 0; + padding: 0 0 0px 0; + text-align: left; + " valign="top"> + <a href="https://manifold.markets" target="_blank"> + <img src="https://manifold.markets/logo-banner.png" width="300" style="height: auto" + alt="Manifold Markets" /> + </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;"> + </span>Hi {{name}},</p> + </div> + </td> + </tr> + <tr> + <td align="left" + style="font-size:0px;padding:10px 25px;padding-top:0px;padding-bottom:20px;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;"> + Your market <a href='{{marketUrl}}'>{{marketTitle}}</a> just got its first prediction from a user! + <br/> + <br/> + We sent you a <span style='color: #11b981'>{{bonusString}}</span> bonus for + creating a market that appeals to others, and we'll do so for each new predictor. + <br/> + <br/> + Keep up the good work and check out your newest predictor below! + </span></p> + </div> + </td> + </tr> + <tr style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 16px; + margin: 0; + "> + <td class="content-block aligncenter" style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 16px; + vertical-align: top; + text-align: center; + margin: 0; + padding: 0; + " align="center" valign="top"> + <table class="invoice" style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 16px; + text-align: left; + width: 80%; + margin: 40px auto; + margin-top: 10px; + "> + <tr style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 16px; + margin-top: 10px; + "> + <td style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 14px; + margin: 0; + padding: 10px 0; + " valign="top"> + <div> + <img src="{{bettor1AvatarUrl}}" width="30" height="30" style=" + border-radius: 30px; + overflow: hidden; + vertical-align: middle; + margin-right: 4px; + " alt="" /> + <span style="font-weight: bold">{{bettor1Name}}</span> + {{bet1Description}} + </div> + </td> + </tr> + <tr style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 16px; + margin: 0; + "> + <td style="padding: 20px 0 0 0; margin: 0"> + <div align="center"> + <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="border-spacing: 0; border-collapse: collapse; mso-table-lspace:0pt; mso-table-rspace:0pt;font-family:arial,helvetica,sans-serif;"><tr><td style="font-family:arial,helvetica,sans-serif;" align="center"><v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="" style="height:37px; v-text-anchor:middle; width:110px;" arcsize="11%" stroke="f" fillcolor="#3AAEE0"><w:anchorlock/><center style="color:#FFFFFF;font-family:arial,helvetica,sans-serif;"><![endif]--> + <a href="{{marketUrl}}" target="_blank" style=" + box-sizing: border-box; + display: inline-block; + font-family: arial, helvetica, sans-serif; + text-decoration: none; + -webkit-text-size-adjust: none; + text-align: center; + color: #ffffff; + background-color: #11b981; + border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + width: auto; + max-width: 100%; + overflow-wrap: break-word; + word-break: break-word; + word-wrap: break-word; + mso-border-alt: none; + "> + <span style=" + display: block; + padding: 10px 20px; + line-height: 120%; + "><span style=" + font-size: 16px; + font-weight: bold; + line-height: 18.8px; + ">View market</span></span> + </a> + <!--[if mso]></center></v:roundrect></td></tr></table><![endif]--> + </div> + </td> + </tr> + </table> + </td> + </tr> + </table> + </td> + </tr> + </table> + <div class="footer" style=" + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 14px; + width: 100%; + clear: both; + color: #999; + margin: 0; + padding: 20px; + "> + <table width="100%" style=" + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 14px; + margin: 0; + "> + <tr style=" + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 14px; + margin: 0; + "> + <td class="aligncenter content-block" style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 12px; + vertical-align: top; + color: #999; + text-align: center; + margin: 0; + padding: 0 0 20px; + " align="center" valign="top"> + Questions? Come ask in + <a href="https://discord.gg/eHQBNBqXuh" style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 12px; + color: #999; + text-decoration: underline; + margin: 0; + ">our Discord</a>! Or, + <a href="{{unsubscribeUrl}}" style=" + color: inherit; + text-decoration: none; + " target="_blank">click here to manage your notifications</a>. + </td> + </tr> + </table> + </div> + </div> + </td> + <td style=" + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 14px; + vertical-align: top; + margin: 0; + " valign="top"></td> + </tr> + </table> +</body> + +</html> \ No newline at end of file diff --git a/functions/src/email-templates/new-unique-bettors.html b/functions/src/email-templates/new-unique-bettors.html new file mode 100644 index 00000000..eb4c04e2 --- /dev/null +++ b/functions/src/email-templates/new-unique-bettors.html @@ -0,0 +1,501 @@ +<!DOCTYPE html> +<html style=" + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 14px; + margin: 0; + "> + +<head> + <meta name="viewport" content="width=device-width" /> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <title>New unique predictors on your market</title> + + <style type="text/css"> + img { + max-width: 100%; + } + + body { + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: none; + width: 100% !important; + height: 100%; + line-height: 1.6em; + } + + body { + background-color: #f6f6f6; + } + + @media only screen and (max-width: 640px) { + body { + padding: 0 !important; + } + + h1 { + font-weight: 800 !important; + margin: 20px 0 5px !important; + } + + h2 { + font-weight: 800 !important; + margin: 20px 0 5px !important; + } + + h3 { + font-weight: 800 !important; + margin: 20px 0 5px !important; + } + + h4 { + font-weight: 800 !important; + margin: 20px 0 5px !important; + } + + h1 { + font-size: 22px !important; + } + + h2 { + font-size: 18px !important; + } + + h3 { + font-size: 16px !important; + } + + .container { + padding: 0 !important; + width: 100% !important; + } + + .content { + padding: 0 !important; + } + + .content-wrap { + padding: 10px !important; + } + + .invoice { + width: 100% !important; + } + } + </style> +</head> + +<body itemscope itemtype="http://schema.org/EmailMessage" style=" + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 14px; + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: none; + width: 100% !important; + height: 100%; + line-height: 1.6em; + background-color: #f6f6f6; + margin: 0; + " bgcolor="#f6f6f6"> + <table class="body-wrap" style=" + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 14px; + width: 100%; + background-color: #f6f6f6; + margin: 0; + " bgcolor="#f6f6f6"> + <tr style=" + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 14px; + margin: 0; + "> + <td style=" + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 14px; + vertical-align: top; + margin: 0; + " valign="top"></td> + <td class="container" width="600" style=" + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 14px; + vertical-align: top; + display: block !important; + max-width: 600px !important; + clear: both !important; + margin: 0 auto; + " valign="top"> + <div class="content" style=" + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 14px; + max-width: 600px; + display: block; + margin: 0 auto; + padding: 20px; + "> + <table class="main" width="100%" cellpadding="0" cellspacing="0" style=" + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 14px; + border-radius: 3px; + background-color: #fff; + margin: 0; + border: 1px solid #e9e9e9; + " bgcolor="#fff"> + <tr style=" + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 14px; + margin: 0; + "> + <td class="content-wrap aligncenter" style=" + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 14px; + vertical-align: top; + text-align: center; + margin: 0; + padding: 20px; + " align="center" valign="top"> + <table width="100%" cellpadding="0" cellspacing="0" style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 14px; + margin: 0; + width: 90%; + "> + <tr style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 14px; + margin: 0; + "> + <td class="content-block" style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 16px; + vertical-align: top; + margin: 0; + padding: 0 0 0px 0; + text-align: left; + " valign="top"> + <a href="https://manifold.markets" target="_blank"> + <img src="https://manifold.markets/logo-banner.png" width="300" style="height: auto" + alt="Manifold Markets" /> + </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;"> + </span>Hi {{name}},</p> + </div> + </td> + </tr> + <tr> + <td align="left" + style="font-size:0px;padding:10px 25px;padding-top:0px;padding-bottom:20px;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;"> + Your market <a href='{{marketUrl}}'>{{marketTitle}}</a> got predictions from a total of {{totalPredictors}} users! + <br/> + <br/> + We sent you a <span style='color: #11b981'>{{bonusString}}</span> bonus for getting {{newPredictors}} new predictors, + and we'll continue to do so for each new predictor, (although we won't send you any more emails about it for this market). + <br/> + <br/> + Keep up the good work and check out your newest predictors below! + </span></p> + </div> + </td> + </tr> + <tr style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 16px; + margin: 0; + "> + <td class="content-block aligncenter" style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 16px; + vertical-align: top; + text-align: center; + margin: 0; + padding: 0; + " align="center" valign="top"> + <table class="invoice" style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 16px; + text-align: left; + width: 80%; + margin: 40px auto; + margin-top: 10px; + "> + <tr style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 16px; + margin-top: 10px; + "> + <td style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 14px; + margin: 0; + padding: 10px 0; + " valign="top"> + <div> + <img src="{{bettor1AvatarUrl}}" width="30" height="30" style=" + border-radius: 30px; + overflow: hidden; + vertical-align: middle; + margin-right: 4px; + " alt="" /> + <span style="font-weight: bold">{{bettor1Name}}</span> + {{bet1Description}} + </div> + </td> + </tr><tr style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 16px; + margin-top: 10px; + "> + <td style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 14px; + margin: 0; + padding: 10px 0; + " valign="top"> + <div> + <img src="{{bettor2AvatarUrl}}" width="30" height="30" style=" + border-radius: 30px; + overflow: hidden; + vertical-align: middle; + margin-right: 4px; + " alt="" /> + <span style="font-weight: bold">{{bettor2Name}}</span> + {{bet2Description}} + </div> + </td> + </tr><tr style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 16px; + margin-top: 10px; + "> + <td style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 14px; + margin: 0; + padding: 10px 0; + " valign="top"> + <div> + <img src="{{bettor3AvatarUrl}}" width="30" height="30" style=" + border-radius: 30px; + overflow: hidden; + vertical-align: middle; + margin-right: 4px; + " alt="" /> + <span style="font-weight: bold">{{bettor3Name}}</span> + {{bet3Description}} + </div> + </td> + </tr><tr style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 16px; + margin-top: 10px; + "> + <td style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 14px; + margin: 0; + padding: 10px 0; + " valign="top"> + <div> + <img src="{{bettor4AvatarUrl}}" width="30" height="30" style=" + border-radius: 30px; + overflow: hidden; + vertical-align: middle; + margin-right: 4px; + " alt="" /> + <span style="font-weight: bold">{{bettor4Name}}</span> + {{bet4Description}} + </div> + </td> + </tr><tr style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 16px; + margin-top: 10px; + "> + <td style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 14px; + margin: 0; + padding: 10px 0; + " valign="top"> + <div> + <img src="{{bettor5AvatarUrl}}" width="30" height="30" style=" + border-radius: 30px; + overflow: hidden; + vertical-align: middle; + margin-right: 4px; + " alt="" /> + <span style="font-weight: bold">{{bettor5Name}}</span> + {{bet5Description}} + </div> + </td> + </tr> + <tr style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 16px; + margin: 0; + "> + <td style="padding: 20px 0 0 0; margin: 0"> + <div align="center"> + <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="border-spacing: 0; border-collapse: collapse; mso-table-lspace:0pt; mso-table-rspace:0pt;font-family:arial,helvetica,sans-serif;"><tr><td style="font-family:arial,helvetica,sans-serif;" align="center"><v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="" style="height:37px; v-text-anchor:middle; width:110px;" arcsize="11%" stroke="f" fillcolor="#3AAEE0"><w:anchorlock/><center style="color:#FFFFFF;font-family:arial,helvetica,sans-serif;"><![endif]--> + <a href="{{marketUrl}}" target="_blank" style=" + box-sizing: border-box; + display: inline-block; + font-family: arial, helvetica, sans-serif; + text-decoration: none; + -webkit-text-size-adjust: none; + text-align: center; + color: #ffffff; + background-color: #11b981; + border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + width: auto; + max-width: 100%; + overflow-wrap: break-word; + word-break: break-word; + word-wrap: break-word; + mso-border-alt: none; + "> + <span style=" + display: block; + padding: 10px 20px; + line-height: 120%; + "><span style=" + font-size: 16px; + font-weight: bold; + line-height: 18.8px; + ">View market</span></span> + </a> + <!--[if mso]></center></v:roundrect></td></tr></table><![endif]--> + </div> + </td> + </tr> + </table> + </td> + </tr> + </table> + </td> + </tr> + </table> + <div class="footer" style=" + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 14px; + width: 100%; + clear: both; + color: #999; + margin: 0; + padding: 20px; + "> + <table width="100%" style=" + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 14px; + margin: 0; + "> + <tr style=" + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 14px; + margin: 0; + "> + <td class="aligncenter content-block" style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 12px; + vertical-align: top; + color: #999; + text-align: center; + margin: 0; + padding: 0 0 20px; + " align="center" valign="top"> + Questions? Come ask in + <a href="https://discord.gg/eHQBNBqXuh" style=" + font-family: 'Helvetica Neue', Helvetica, Arial, + sans-serif; + box-sizing: border-box; + font-size: 12px; + color: #999; + text-decoration: underline; + margin: 0; + ">our Discord</a>! Or, + <a href="{{unsubscribeUrl}}" style=" + color: inherit; + text-decoration: none; + " target="_blank">click here to manage your notifications</a>. + </td> + </tr> + </table> + </div> + </div> + </td> + <td style=" + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + box-sizing: border-box; + font-size: 14px; + vertical-align: top; + margin: 0; + " valign="top"></td> + </tr> + </table> +</body> + +</html> \ No newline at end of file diff --git a/functions/src/emails.ts b/functions/src/emails.ts index da6a5b41..adeb3d12 100644 --- a/functions/src/emails.ts +++ b/functions/src/emails.ts @@ -22,6 +22,7 @@ import { notification_reason_types, getDestinationsForUser, } from '../../common/notification' +import { Dictionary } from 'lodash' export const sendMarketResolutionEmail = async ( reason: notification_reason_types, @@ -544,3 +545,63 @@ export const sendNewFollowedMarketEmail = async ( } ) } +export const sendNewUniqueBettorsEmail = async ( + reason: notification_reason_types, + userId: string, + privateUser: PrivateUser, + contract: Contract, + totalPredictors: number, + newPredictors: User[], + userBets: Dictionary<[Bet, ...Bet[]]>, + bonusAmount: number +) => { + const { sendToEmail, urlToManageThisNotification: unsubscribeUrl } = + await getDestinationsForUser(privateUser, reason) + if (!privateUser.email || !sendToEmail) return + const user = await getUser(privateUser.id) + if (!user) return + + const { name } = user + const firstName = name.split(' ')[0] + const creatorName = contract.creatorName + // make the emails stack for the same contract + const subject = `You made a popular market! ${ + contract.question.length > 50 + ? contract.question.slice(0, 50) + '...' + : contract.question + } just got ${ + newPredictors.length + } new predictions. Check out who's betting on it inside.` + const templateData: Record<string, string> = { + name: firstName, + creatorName, + totalPredictors: totalPredictors.toString(), + bonusString: formatMoney(bonusAmount), + marketTitle: contract.question, + marketUrl: contractUrl(contract), + unsubscribeUrl, + newPredictors: newPredictors.length.toString(), + } + + newPredictors.forEach((p, i) => { + templateData[`bettor${i + 1}Name`] = p.name + if (p.avatarUrl) templateData[`bettor${i + 1}AvatarUrl`] = p.avatarUrl + const bet = userBets[p.id][0] + if (bet) { + const { amount, sale } = bet + templateData[`bet${i + 1}Description`] = `${ + sale || amount < 0 ? 'sold' : 'bought' + } ${formatMoney(Math.abs(amount))}` + } + }) + + return await sendTemplateEmail( + privateUser.email, + subject, + newPredictors.length === 1 ? 'new-unique-bettor' : 'new-unique-bettors', + templateData, + { + from: `Manifold Markets <no-reply@manifold.markets>`, + } + ) +} diff --git a/functions/src/on-create-bet.ts b/functions/src/on-create-bet.ts index 5dbebfc3..f2c6b51a 100644 --- a/functions/src/on-create-bet.ts +++ b/functions/src/on-create-bet.ts @@ -28,8 +28,9 @@ import { User } from '../../common/user' const firestore = admin.firestore() const BONUS_START_DATE = new Date('2022-07-13T15:30:00.000Z').getTime() -export const onCreateBet = functions.firestore - .document('contracts/{contractId}/bets/{betId}') +export const onCreateBet = functions + .runWith({ secrets: ['MAILGUN_KEY'] }) + .firestore.document('contracts/{contractId}/bets/{betId}') .onCreate(async (change, context) => { const { contractId } = context.params as { contractId: string @@ -198,6 +199,7 @@ const updateUniqueBettorsAndGiveCreatorBonus = async ( result.txn.id, contract, result.txn.amount, + newUniqueBettorIds, eventId + '-unique-bettor-bonus' ) } diff --git a/web/components/notification-settings.tsx b/web/components/notification-settings.tsx index 65804991..128a89ef 100644 --- a/web/components/notification-settings.tsx +++ b/web/components/notification-settings.tsx @@ -60,11 +60,14 @@ export function NotificationSettings(props: { 'tagged_user', // missing tagged on contract description email 'contract_from_followed_user', + 'unique_bettors_on_your_contract', // TODO: add these + // one-click unsubscribe only unsubscribes them from that type only, (well highlighted), then a link to manage the rest of their notifications + // 'profit_loss_updates', - changes in markets you have shares in + // biggest winner, here are the rest of your markets + // 'referral_bonuses', - // 'unique_bettors_on_your_contract', // 'on_new_follow', - // 'profit_loss_updates', // 'tips_on_your_markets', // 'tips_on_your_comments', // maybe the following?