Add new market from followed user email notification
This commit is contained in:
parent
2c922cbae6
commit
4398fa9bda
|
@ -22,7 +22,9 @@ import {
|
|||
sendMarketResolutionEmail,
|
||||
sendNewAnswerEmail,
|
||||
sendNewCommentEmail,
|
||||
sendNewFollowedMarketEmail,
|
||||
} from './emails'
|
||||
import { filterDefined } from '../../common/util/array'
|
||||
const firestore = admin.firestore()
|
||||
|
||||
type recipients_to_reason_texts = {
|
||||
|
@ -103,51 +105,14 @@ export const createNotification = async (
|
|||
privateUser,
|
||||
sourceContract
|
||||
)
|
||||
} else if (reason === 'tagged_user') {
|
||||
// TODO: send email to tagged user in new contract
|
||||
} else if (reason === 'subsidized_your_market') {
|
||||
// TODO: send email to creator of market that was subsidized
|
||||
} else if (reason === 'contract_from_followed_user') {
|
||||
// TODO: send email to follower of user who created market
|
||||
} else if (reason === 'on_new_follow') {
|
||||
// TODO: send email to user who was followed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const notifyUsersFollowers = async (
|
||||
userToReasonTexts: recipients_to_reason_texts
|
||||
) => {
|
||||
const followers = await firestore
|
||||
.collectionGroup('follows')
|
||||
.where('userId', '==', sourceUser.id)
|
||||
.get()
|
||||
|
||||
followers.docs.forEach((doc) => {
|
||||
const followerUserId = doc.ref.parent.parent?.id
|
||||
if (
|
||||
followerUserId &&
|
||||
shouldReceiveNotification(followerUserId, userToReasonTexts)
|
||||
) {
|
||||
userToReasonTexts[followerUserId] = {
|
||||
reason: 'contract_from_followed_user',
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const notifyTaggedUsers = (
|
||||
userToReasonTexts: recipients_to_reason_texts,
|
||||
userIds: (string | undefined)[]
|
||||
) => {
|
||||
userIds.forEach((id) => {
|
||||
if (id && shouldReceiveNotification(id, userToReasonTexts))
|
||||
userToReasonTexts[id] = {
|
||||
reason: 'tagged_user',
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// The following functions modify the userToReasonTexts object in place.
|
||||
const userToReasonTexts: recipients_to_reason_texts = {}
|
||||
|
||||
|
@ -157,15 +122,6 @@ export const createNotification = async (
|
|||
reason: 'on_new_follow',
|
||||
}
|
||||
return await sendNotificationsIfSettingsPermit(userToReasonTexts)
|
||||
} else if (
|
||||
sourceType === 'contract' &&
|
||||
sourceUpdateType === 'created' &&
|
||||
sourceContract
|
||||
) {
|
||||
if (sourceContract.visibility === 'public')
|
||||
await notifyUsersFollowers(userToReasonTexts)
|
||||
await notifyTaggedUsers(userToReasonTexts, recipients ?? [])
|
||||
return await sendNotificationsIfSettingsPermit(userToReasonTexts)
|
||||
} else if (
|
||||
sourceType === 'contract' &&
|
||||
sourceUpdateType === 'closed' &&
|
||||
|
@ -283,52 +239,57 @@ export const createCommentOrAnswerOrUpdatedContractNotification = async (
|
|||
reason
|
||||
)
|
||||
|
||||
// Browser notifications
|
||||
if (sendToBrowser && !browserRecipientIdsList.includes(userId)) {
|
||||
await createBrowserNotification(userId, reason)
|
||||
browserRecipientIdsList.push(userId)
|
||||
}
|
||||
if (sendToEmail && !emailRecipientIdsList.includes(userId)) {
|
||||
if (sourceType === 'comment') {
|
||||
const { repliedToType, repliedToAnswerText, repliedToId, bet } =
|
||||
repliedUsersInfo?.[userId] ?? {}
|
||||
// TODO: change subject of email title to be more specific, i.e.: replied to you on/tagged you on/comment
|
||||
await sendNewCommentEmail(
|
||||
reason,
|
||||
privateUser,
|
||||
sourceUser,
|
||||
sourceContract,
|
||||
sourceText,
|
||||
sourceId,
|
||||
bet,
|
||||
repliedToAnswerText,
|
||||
repliedToType === 'answer' ? repliedToId : undefined
|
||||
)
|
||||
} else if (sourceType === 'answer')
|
||||
await sendNewAnswerEmail(
|
||||
reason,
|
||||
privateUser,
|
||||
sourceUser.name,
|
||||
sourceText,
|
||||
sourceContract,
|
||||
sourceUser.avatarUrl
|
||||
)
|
||||
else if (
|
||||
sourceType === 'contract' &&
|
||||
sourceUpdateType === 'resolved' &&
|
||||
resolutionData
|
||||
|
||||
// Emails notifications
|
||||
if (!sendToEmail || emailRecipientIdsList.includes(userId)) return
|
||||
if (sourceType === 'comment') {
|
||||
const { repliedToType, repliedToAnswerText, repliedToId, bet } =
|
||||
repliedUsersInfo?.[userId] ?? {}
|
||||
// TODO: change subject of email title to be more specific, i.e.: replied to you on/tagged you on/comment
|
||||
await sendNewCommentEmail(
|
||||
reason,
|
||||
privateUser,
|
||||
sourceUser,
|
||||
sourceContract,
|
||||
sourceText,
|
||||
sourceId,
|
||||
bet,
|
||||
repliedToAnswerText,
|
||||
repliedToType === 'answer' ? repliedToId : undefined
|
||||
)
|
||||
emailRecipientIdsList.push(userId)
|
||||
} else if (sourceType === 'answer') {
|
||||
await sendNewAnswerEmail(
|
||||
reason,
|
||||
privateUser,
|
||||
sourceUser.name,
|
||||
sourceText,
|
||||
sourceContract,
|
||||
sourceUser.avatarUrl
|
||||
)
|
||||
emailRecipientIdsList.push(userId)
|
||||
} else if (
|
||||
sourceType === 'contract' &&
|
||||
sourceUpdateType === 'resolved' &&
|
||||
resolutionData
|
||||
) {
|
||||
await sendMarketResolutionEmail(
|
||||
reason,
|
||||
privateUser,
|
||||
resolutionData.userInvestments[userId] ?? 0,
|
||||
resolutionData.userPayouts[userId] ?? 0,
|
||||
sourceUser,
|
||||
resolutionData.creatorPayout,
|
||||
sourceContract,
|
||||
resolutionData.outcome,
|
||||
resolutionData.resolutionProbability,
|
||||
resolutionData.resolutions
|
||||
)
|
||||
await sendMarketResolutionEmail(
|
||||
reason,
|
||||
privateUser,
|
||||
resolutionData.userInvestments[userId] ?? 0,
|
||||
resolutionData.userPayouts[userId] ?? 0,
|
||||
sourceUser,
|
||||
resolutionData.creatorPayout,
|
||||
sourceContract,
|
||||
resolutionData.outcome,
|
||||
resolutionData.resolutionProbability,
|
||||
resolutionData.resolutions
|
||||
)
|
||||
emailRecipientIdsList.push(userId)
|
||||
}
|
||||
}
|
||||
|
@ -852,3 +813,79 @@ export const createUniqueBettorBonusNotification = async (
|
|||
|
||||
// TODO send email notification
|
||||
}
|
||||
|
||||
export const createNewContractNotification = async (
|
||||
contractCreator: User,
|
||||
contract: Contract,
|
||||
idempotencyKey: string,
|
||||
text: string,
|
||||
mentionedUserIds: string[]
|
||||
) => {
|
||||
if (contract.visibility !== 'public') return
|
||||
|
||||
const sendNotificationsIfSettingsAllow = async (
|
||||
userId: string,
|
||||
reason: notification_reason_types
|
||||
) => {
|
||||
const privateUser = await getPrivateUser(userId)
|
||||
if (!privateUser) return
|
||||
const { sendToBrowser, sendToEmail } = await getDestinationsForUser(
|
||||
privateUser,
|
||||
reason
|
||||
)
|
||||
if (sendToBrowser) {
|
||||
const notificationRef = firestore
|
||||
.collection(`/users/${userId}/notifications`)
|
||||
.doc(idempotencyKey)
|
||||
const notification: Notification = {
|
||||
id: idempotencyKey,
|
||||
userId: userId,
|
||||
reason,
|
||||
createdTime: Date.now(),
|
||||
isSeen: false,
|
||||
sourceId: contract.id,
|
||||
sourceType: 'contract',
|
||||
sourceUpdateType: 'created',
|
||||
sourceUserName: contractCreator.name,
|
||||
sourceUserUsername: contractCreator.username,
|
||||
sourceUserAvatarUrl: contractCreator.avatarUrl,
|
||||
sourceText: text,
|
||||
sourceSlug: contract.slug,
|
||||
sourceTitle: contract.question,
|
||||
sourceContractSlug: contract.slug,
|
||||
sourceContractId: contract.id,
|
||||
sourceContractTitle: contract.question,
|
||||
sourceContractCreatorUsername: contract.creatorUsername,
|
||||
}
|
||||
await notificationRef.set(removeUndefinedProps(notification))
|
||||
}
|
||||
if (!sendToEmail) return
|
||||
if (reason === 'contract_from_followed_user')
|
||||
await sendNewFollowedMarketEmail(reason, userId, privateUser, contract)
|
||||
}
|
||||
const followersSnapshot = await firestore
|
||||
.collectionGroup('follows')
|
||||
.where('userId', '==', contractCreator.id)
|
||||
.get()
|
||||
|
||||
const followerUserIds = filterDefined(
|
||||
followersSnapshot.docs.map((doc) => {
|
||||
const followerUserId = doc.ref.parent.parent?.id
|
||||
return followerUserId && followerUserId != contractCreator.id
|
||||
? followerUserId
|
||||
: undefined
|
||||
})
|
||||
)
|
||||
|
||||
// As it is coded now, the tag notification usurps the new contract notification
|
||||
// It'd be easy to append the reason to the eventId if desired
|
||||
for (const followerUserId of followerUserIds) {
|
||||
await sendNotificationsIfSettingsAllow(
|
||||
followerUserId,
|
||||
'contract_from_followed_user'
|
||||
)
|
||||
}
|
||||
for (const mentionedUserId of mentionedUserIds) {
|
||||
await sendNotificationsIfSettingsAllow(mentionedUserId, 'tagged_user')
|
||||
}
|
||||
}
|
||||
|
|
354
functions/src/email-templates/new-market-from-followed-user.html
Normal file
354
functions/src/email-templates/new-market-from-followed-user.html
Normal file
|
@ -0,0 +1,354 @@
|
|||
<!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>New market from {{creatorName}}</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]-->
|
||||
<!--[if !mso]><!-->
|
||||
<link href="https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700" rel="stylesheet" type="text/css" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Readex+Pro" rel="stylesheet" type="text/css" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Readex+Pro" rel="stylesheet" type="text/css" />
|
||||
<style type="text/css">
|
||||
@import url(https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700);
|
||||
@import url(https://fonts.googleapis.com/css?family=Readex+Pro);
|
||||
@import url(https://fonts.googleapis.com/css?family=Readex+Pro);
|
||||
</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="margin:0px auto;max-width:600px;">
|
||||
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
style="direction:ltr;font-size:0px;padding:20px 0px 5px 0px;padding-bottom:0px;padding-left:0px;padding-right: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 align="center"
|
||||
style="background:#ffffff;font-size:0px;padding:10px 25px 10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;">
|
||||
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation"
|
||||
style="border-collapse:collapse;border-spacing:0px;">
|
||||
<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>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><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: 20px 0px 0px 0px;
|
||||
padding-bottom: 0px;
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
padding-top: 20px;
|
||||
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 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;">
|
||||
{{creatorName}}, (who you're following) just created a new market, check it out!</span></p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="center"
|
||||
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:center;color:#000000;">
|
||||
<a href="{{questionUrl}}">
|
||||
<img alt="{{questionTitle}}" width="375" height="200"
|
||||
style="border: 1px solid #4337c9;" src="{{questionImgSrc}}">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td style="border-radius: 4px;" bgcolor="#4337c9">
|
||||
<a href="{{questionUrl}}" target="_blank"
|
||||
style="padding: 6px 10px; border: 1px solid #4337c9;border-radius: 12px;font-family: Helvetica, Arial, sans-serif;font-size: 16px; color: #ffffff;text-decoration: none;font-weight:semibold;display: inline-block;">
|
||||
View market
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="margin: 0px auto; max-width: 600px">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="
|
||||
direction: ltr;
|
||||
font-size: 0px;
|
||||
padding: 0 0 20px 0;
|
||||
text-align: center;
|
||||
">
|
||||
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="margin: 0px auto; max-width: 600px">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation"
|
||||
style="width: 100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="
|
||||
direction: ltr;
|
||||
font-size: 0px;
|
||||
padding: 20px 0px 20px 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"
|
||||
width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="vertical-align: top; padding: 0">
|
||||
<table border="0" cellpadding="0" cellspacing="0"
|
||||
role="presentation" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="
|
||||
font-size: 0px;
|
||||
padding: 10px 25px;
|
||||
word-break: break-word;
|
||||
">
|
||||
<div style="
|
||||
font-family: Ubuntu, Helvetica, Arial,
|
||||
sans-serif;
|
||||
font-size: 11px;
|
||||
line-height: 22px;
|
||||
text-align: center;
|
||||
color: #000000;
|
||||
">
|
||||
<p style="margin: 10px 0">
|
||||
This e-mail has been sent to
|
||||
{{name}},
|
||||
<a href="{{unsubscribeUrl}}" style="
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
" target="_blank">click here to manage your notifications</a>.
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" style="
|
||||
font-size: 0px;
|
||||
padding: 10px 25px;
|
||||
word-break: break-word;
|
||||
"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -510,3 +510,37 @@ function contractUrl(contract: Contract) {
|
|||
function imageSourceUrl(contract: Contract) {
|
||||
return buildCardUrl(getOpenGraphProps(contract))
|
||||
}
|
||||
|
||||
export const sendNewFollowedMarketEmail = async (
|
||||
reason: notification_reason_types,
|
||||
userId: string,
|
||||
privateUser: PrivateUser,
|
||||
contract: Contract
|
||||
) => {
|
||||
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
|
||||
|
||||
return await sendTemplateEmail(
|
||||
privateUser.email,
|
||||
`${creatorName} asked ${contract.question}`,
|
||||
'new-market-from-followed-user',
|
||||
{
|
||||
name: firstName,
|
||||
creatorName,
|
||||
unsubscribeUrl,
|
||||
questionTitle: contract.question,
|
||||
questionUrl: contractUrl(contract),
|
||||
questionImgSrc: imageSourceUrl(contract),
|
||||
},
|
||||
{
|
||||
from: `${creatorName} on Manifold <no-reply@manifold.markets>`,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as functions from 'firebase-functions'
|
||||
|
||||
import { getUser } from './utils'
|
||||
import { createNotification } from './create-notification'
|
||||
import { createNewContractNotification } from './create-notification'
|
||||
import { Contract } from '../../common/contract'
|
||||
import { parseMentions, richTextToString } from '../../common/util/parse'
|
||||
import { JSONContent } from '@tiptap/core'
|
||||
|
@ -21,13 +21,11 @@ export const onCreateContract = functions
|
|||
const mentioned = parseMentions(desc)
|
||||
await addUserToContractFollowers(contract.id, contractCreator.id)
|
||||
|
||||
await createNotification(
|
||||
contract.id,
|
||||
'contract',
|
||||
'created',
|
||||
await createNewContractNotification(
|
||||
contractCreator,
|
||||
contract,
|
||||
eventId,
|
||||
richTextToString(desc),
|
||||
{ contract, recipients: mentioned }
|
||||
mentioned
|
||||
)
|
||||
})
|
||||
|
|
|
@ -58,9 +58,9 @@ export function NotificationSettings(props: {
|
|||
'onboarding_flow',
|
||||
'thank_you_for_purchases',
|
||||
|
||||
'tagged_user', // missing tagged on contract description email
|
||||
'contract_from_followed_user',
|
||||
// TODO: add these
|
||||
'tagged_user',
|
||||
// 'contract_from_followed_user',
|
||||
// 'referral_bonuses',
|
||||
// 'unique_bettors_on_your_contract',
|
||||
// 'on_new_follow',
|
||||
|
@ -90,6 +90,7 @@ export function NotificationSettings(props: {
|
|||
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`,
|
||||
// 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:
|
||||
|
|
|
@ -1031,52 +1031,53 @@ function getReasonForShowingNotification(
|
|||
const { sourceType, sourceUpdateType, reason, sourceSlug } = notification
|
||||
let reasonText: string
|
||||
// TODO: we could leave out this switch and just use the reason field now that they have more information
|
||||
switch (sourceType) {
|
||||
case 'comment':
|
||||
if (reason === 'reply_to_users_answer')
|
||||
reasonText = justSummary ? 'replied' : 'replied to you on'
|
||||
else if (reason === 'tagged_user')
|
||||
reasonText = justSummary ? 'tagged you' : 'tagged you on'
|
||||
else if (reason === 'reply_to_users_comment')
|
||||
reasonText = justSummary ? 'replied' : 'replied to you on'
|
||||
else reasonText = justSummary ? `commented` : `commented on`
|
||||
break
|
||||
case 'contract':
|
||||
if (reason === 'contract_from_followed_user')
|
||||
reasonText = justSummary ? 'asked the question' : 'asked'
|
||||
else if (sourceUpdateType === 'resolved')
|
||||
reasonText = justSummary ? `resolved the question` : `resolved`
|
||||
else if (sourceUpdateType === 'closed') reasonText = `Please resolve`
|
||||
else reasonText = justSummary ? 'updated the question' : `updated`
|
||||
break
|
||||
case 'answer':
|
||||
if (reason === 'answer_on_your_contract')
|
||||
reasonText = `answered your question `
|
||||
else reasonText = `answered`
|
||||
break
|
||||
case 'follow':
|
||||
reasonText = 'followed you'
|
||||
break
|
||||
case 'liquidity':
|
||||
reasonText = 'added a subsidy to your question'
|
||||
break
|
||||
case 'group':
|
||||
reasonText = 'added you to the group'
|
||||
break
|
||||
case 'user':
|
||||
if (sourceSlug && reason === 'user_joined_to_bet_on_your_market')
|
||||
reasonText = 'joined to bet on your market'
|
||||
else if (sourceSlug) reasonText = 'joined because you shared'
|
||||
else reasonText = 'joined because of you'
|
||||
break
|
||||
case 'bet':
|
||||
reasonText = 'bet against you'
|
||||
break
|
||||
case 'challenge':
|
||||
reasonText = 'accepted your challenge'
|
||||
break
|
||||
default:
|
||||
reasonText = ''
|
||||
}
|
||||
if (reason === 'tagged_user')
|
||||
reasonText = justSummary ? 'tagged you' : 'tagged you on'
|
||||
else
|
||||
switch (sourceType) {
|
||||
case 'comment':
|
||||
if (reason === 'reply_to_users_answer')
|
||||
reasonText = justSummary ? 'replied' : 'replied to you on'
|
||||
else if (reason === 'reply_to_users_comment')
|
||||
reasonText = justSummary ? 'replied' : 'replied to you on'
|
||||
else reasonText = justSummary ? `commented` : `commented on`
|
||||
break
|
||||
case 'contract':
|
||||
if (reason === 'contract_from_followed_user')
|
||||
reasonText = justSummary ? 'asked the question' : 'asked'
|
||||
else if (sourceUpdateType === 'resolved')
|
||||
reasonText = justSummary ? `resolved the question` : `resolved`
|
||||
else if (sourceUpdateType === 'closed') reasonText = `Please resolve`
|
||||
else reasonText = justSummary ? 'updated the question' : `updated`
|
||||
break
|
||||
case 'answer':
|
||||
if (reason === 'answer_on_your_contract')
|
||||
reasonText = `answered your question `
|
||||
else reasonText = `answered`
|
||||
break
|
||||
case 'follow':
|
||||
reasonText = 'followed you'
|
||||
break
|
||||
case 'liquidity':
|
||||
reasonText = 'added a subsidy to your question'
|
||||
break
|
||||
case 'group':
|
||||
reasonText = 'added you to the group'
|
||||
break
|
||||
case 'user':
|
||||
if (sourceSlug && reason === 'user_joined_to_bet_on_your_market')
|
||||
reasonText = 'joined to bet on your market'
|
||||
else if (sourceSlug) reasonText = 'joined because you shared'
|
||||
else reasonText = 'joined because of you'
|
||||
break
|
||||
case 'bet':
|
||||
reasonText = 'bet against you'
|
||||
break
|
||||
case 'challenge':
|
||||
reasonText = 'accepted your challenge'
|
||||
break
|
||||
default:
|
||||
reasonText = ''
|
||||
}
|
||||
return reasonText
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user