diff --git a/apps/web/pages/api/jobs/notify/[type].ts b/apps/web/pages/api/jobs/notify/[type].ts index 1b3d4be80..443ee614c 100644 --- a/apps/web/pages/api/jobs/notify/[type].ts +++ b/apps/web/pages/api/jobs/notify/[type].ts @@ -12,6 +12,7 @@ import { createNewMemberNotifications, NewMemberJoinNotification, } from '@acter/jobs/new-member-notifications' +import { createPostMentionNotifications } from '@acter/jobs/post-mention-notifications' import { createPostNotifications, PostJobVariables, @@ -51,6 +52,10 @@ const notificationTypeMap: Record< checks: (body: PostJobVariables) => !!body.id, fn: createPostNotifications, }, + [NotificationQueueType.NEW_MENTION]: { + checks: (body: PostJobVariables) => !!body.id, + fn: createPostMentionNotifications, + }, } const l = getLogger('notifyHandler') diff --git a/packages/components/user/profile/settings/index.tsx b/packages/components/user/profile/settings/index.tsx index c51822be1..3cc82b545 100644 --- a/packages/components/user/profile/settings/index.tsx +++ b/packages/components/user/profile/settings/index.tsx @@ -77,6 +77,10 @@ export const ProfileSettings: FC = () => { + ({ + getJobData: async (job) => { + const postMention = await prisma.postMention.findFirst({ + where: { + id: job.id, + }, + }) + const post = await prisma.post.findFirst({ + include: { + Acter: true, + Author: true, + }, + where: { + id: postMention.postId, + }, + }) + return { post, postMention } + }, + getFollowing: async ({ post }) => { + return await prisma.acter.findFirst({ + include: { + ActerType: true, + Parent: { + include: { + ActerType: true, + }, + }, + }, + where: { + id: post.Acter.id, + }, + }) + }, + getFollowersWhere: ({ postMention }) => ({ + Follower: { + id: postMention.acterId, + }, + }), + getNotificationEmail: ({ data: { post }, notification }) => + createPostMentionEmailNotification({ + post, + notification, + }), + getNotificationEmailSubject: ({ notification }) => + `New mention on post on ${notification.OnActer.name} via Acter`, + getPost: ({ post }) => post as Post, + type: NotificationType.NEW_MENTION, + getNotificationUrlPath: (postId, following) => + following.ActerType.name === ActerTypes.ACTIVITY + ? `activities?activity=${slugify(following.name)}&post=${postId}` + : `forum/${postId}`, +}) diff --git a/services/jobs/post-mention-notifications/index.ts b/services/jobs/post-mention-notifications/index.ts new file mode 100644 index 000000000..35f64cbb1 --- /dev/null +++ b/services/jobs/post-mention-notifications/index.ts @@ -0,0 +1,2 @@ +export { createPostMentionNotifications } from './create-post-mention-notifications' +export type { PostMentionJobVariables } from './types' diff --git a/services/jobs/post-mention-notifications/template/index.tsx b/services/jobs/post-mention-notifications/template/index.tsx new file mode 100644 index 000000000..dfb1e894c --- /dev/null +++ b/services/jobs/post-mention-notifications/template/index.tsx @@ -0,0 +1,74 @@ +import { assert } from 'console' +import { + render, + MjmlButton, + MjmlColumn, + MjmlSection, + MjmlText, +} from 'mjml-react' + +import { CreateEmailReturn } from '@acter/lib/email' +import { getNotificationUrl } from '@acter/lib/notification/get-notification-url' +import { getArticle } from '@acter/lib/string/get-article' +import { Acter, Notification, Post } from '@acter/schema' + +import { EmailLayout } from '../../templates/layout' +import { PostMentionEmailBlock } from './post-mention-email-block' + +export type PostWithActerAndAuthor = Omit & { + Acter: ActerNameAndID + Author: ActerNameAndID +} + +type ActerNameAndID = Pick + +type CreatePostEmailNotificationParams = { + notification: Notification + post: PostWithActerAndAuthor +} + +export const createPostMentionEmailNotification = ({ + notification, + post, +}: CreatePostEmailNotificationParams): CreateEmailReturn => { + assert(!!post.Acter?.name, 'Post Acter name required') + assert(!!post.Author?.name, 'Post Author name required') + assert(!!post.createdAt, 'Post created at required') + + const notificationUrl = getNotificationUrl(notification) + const postType = post.parentId ? 'comment' : 'post' + const { html } = render( + + + + + A new {postType} created on {post.Acter.name} mentioned you. + + + + + + + + Go To Post + + + + + ) + const { OnActer } = notification + const aAn = getArticle(OnActer.name) + const text = `A new ${postType} was created on ${aAn} ${OnActer.ActerType.name} you follow on Acter, ${OnActer.name}. To see it, visit: ${notificationUrl}` + + return { html, text } +} diff --git a/services/jobs/post-mention-notifications/template/post-mention-email-block.tsx b/services/jobs/post-mention-notifications/template/post-mention-email-block.tsx new file mode 100644 index 000000000..e20232b61 --- /dev/null +++ b/services/jobs/post-mention-notifications/template/post-mention-email-block.tsx @@ -0,0 +1,44 @@ +import React, { FC } from 'react' + +import Markdown from 'markdown-to-jsx' +import { MjmlColumn, MjmlSection, MjmlText } from 'mjml-react' + +import { DATE_TIME_FORMAT_LONG } from '@acter/lib/constants' +import { parseAndFormat } from '@acter/lib/datetime/parse-and-format' +import { Post } from '@acter/schema' + +export interface PostMentionEmailBlockProps { + post: Post + notificationUrl: string +} + +export const PostMentionEmailBlock: FC = ({ + post, + notificationUrl, +}) => { + const sentAt = parseAndFormat({ + dateString: post.createdAt, + formatString: DATE_TIME_FORMAT_LONG, + }) + return ( + <> + + + + On {sentAt} {post.Author.name}{' '} + mentioned you in the following post: + + + + + + +
+ {post.content} +
+
+
+
+ + ) +} diff --git a/services/jobs/post-mention-notifications/types.ts b/services/jobs/post-mention-notifications/types.ts new file mode 100644 index 000000000..9613a595a --- /dev/null +++ b/services/jobs/post-mention-notifications/types.ts @@ -0,0 +1,12 @@ +import { PostMention } from '@acter/schema' + +import { PostWithActerAndAuthor } from '../post-notifications/template' + +export interface PostMentionJobVariables { + id: string +} + +export interface PostMentionJobData { + postMention: PostMention + post: PostWithActerAndAuthor +}