diff --git a/pages/api/project/[projectId].ts b/pages/api/project/[projectId].ts index 4ff07d66..6b02fdce 100644 --- a/pages/api/project/[projectId].ts +++ b/pages/api/project/[projectId].ts @@ -16,7 +16,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const body = req.body as { enableNotification?: boolean, webhookUrl?: string, - enableWebhook?: boolean + enableWebhook?: boolean, + notificationEmail?: string } const project = (await projectService.get(projectId, { @@ -36,7 +37,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) data: { enableNotification: body.enableNotification, enableWebhook: body.enableWebhook, - webhook: body.webhookUrl + webhook: body.webhookUrl, + notificationEmail: body.notificationEmail }, }) diff --git a/pages/dashboard/project/[projectId].tsx b/pages/dashboard/project/[projectId].tsx index 5ea926ad..7298c336 100644 --- a/pages/dashboard/project/[projectId].tsx +++ b/pages/dashboard/project/[projectId].tsx @@ -1,4 +1,4 @@ -import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Box, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Button, Center, Checkbox, Code, Container, Divider, Flex, FormControl, Heading, HStack, Input, InputGroup, InputRightElement, Link, Spacer, Spinner, StackDivider, Switch, Tab, TabList, TabPanel, TabPanels, Tabs, Tag, Text, Textarea, toast, Tooltip, useDisclosure, useToast, VStack } from '@chakra-ui/react' +import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Box, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Button, Center, Checkbox, Code, Container, Divider, Flex, FormControl, FormHelperText, FormLabel, Heading, HStack, Input, InputGroup, InputRightElement, Link, Spacer, Spinner, StackDivider, Switch, Tab, TabList, TabPanel, TabPanels, Tabs, Tag, Text, Textarea, toast, Tooltip, useDisclosure, useToast, VStack } from '@chakra-ui/react' import { Comment, Page, Project } from '@prisma/client' import { session, signIn } from 'next-auth/client' import { useRouter } from 'next/router' @@ -6,7 +6,7 @@ import React, { useRef } from 'react' import { useMutation, useQuery } from 'react-query' import { ProjectService } from '../../../service/project.service' import { CommentItem, CommentWrapper } from '../../../service/comment.service' -import { apiClient } from '../../../utils.client' +import { apiClient, validateEmail } from '../../../utils.client' import dayjs from 'dayjs' import { useForm } from 'react-hook-form' import { UserSession } from '../../../service' @@ -259,6 +259,7 @@ function Settings(props: { const enableNotificationMutation = useMutation(updateProjectSettings) const enableWebhookMutation = useMutation(updateProjectSettings) const updateWebhookUrlMutation = useMutation(updateProjectSettings) + const updateNotificationEmailMutation = useMutation(updateProjectSettings) const deleteProjectMutation = useMutation(deleteProject, { onSuccess() { toast({ @@ -284,6 +285,7 @@ function Settings(props: { } const webhookInputRef = useRef(null) + const notificationEmailInputRef = useRef(null) const uploadMutation = useMutation(upload, { onSuccess(data) { @@ -324,6 +326,41 @@ function Settings(props: { return res.data.data } + const onSaveNotificationEmail = async _ => { + const value = notificationEmailInputRef.current.value + + if(!validateEmail(value)) { + toast({ + title: 'Email address is not valid', + status: 'error', + position: 'top' + }) + return + } + + updateNotificationEmailMutation.mutate({ + projectId: props.project.id, + body: { + notificationEmail: value + } + }, { + onSuccess() { + toast({ + title: 'Updated', + status: 'success', + position: 'top' + }) + }, + onError() { + toast({ + title: 'Something went wrong', + status: 'error', + position: 'top' + }) + } + }) + } + const onSaveWebhookUrl = async _ => { const value = webhookInputRef.current.value @@ -472,11 +509,18 @@ function Settings(props: { Email Notification - - - Advanced Notification Settings - - + + Project Notification Email + + + + + + + + This overrides the user's notification settings. + + @@ -524,7 +568,7 @@ function Settings(props: { ) } -type ProjectServerSideProps = Pick +type ProjectServerSideProps = Pick export async function getServerSideProps(ctx) { const projectService = new ProjectService(ctx.req) @@ -559,7 +603,8 @@ export async function getServerSideProps(ctx) { token: project.token, enableNotification: project.enableNotification, enableWebhook: project.enableWebhook, - webhook: project.webhook + webhook: project.webhook, + notificationEmail: project.notificationEmail } as ProjectServerSideProps } diff --git a/pages/user.tsx b/pages/user.tsx index 3e70901e..e81de626 100644 --- a/pages/user.tsx +++ b/pages/user.tsx @@ -5,20 +5,10 @@ import { useMutation } from "react-query" import { Footer } from "../components/Footer" import { Navbar } from "../components/Navbar" import { UserSession } from "../service" -import { apiClient } from "../utils.client" +import { apiClient, validateEmail } from "../utils.client" import { getSession, prisma } from "../utils.server" import { Head } from "../components/Head" - -// From https://stackoverflow.com/questions/46155/how-to-validate-an-email-address-in-javascript -function validateEmail(email) { - if (email === '') { - return true - } - const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - return re.test(String(email).toLowerCase()); -} - const updateUserSettings = async (params: { notificationEmail?: string, enableNewCommentNotification?: boolean diff --git a/prisma/pgsql/schema.prisma b/prisma/pgsql/schema.prisma index dc81bcc5..108740da 100644 --- a/prisma/pgsql/schema.prisma +++ b/prisma/pgsql/schema.prisma @@ -92,6 +92,7 @@ model Project { fetchLatestCommentsAt DateTime? @map(name: "fetch_latest_comments_at") enableNotification Boolean? @default(true) @map(name: "enable_notification") + notificationEmail String? @map(name: "notification_email") webhook String? enableWebhook Boolean? diff --git a/prisma/sqlite/migrations/20211022055055_add_project_notification_email/migration.sql b/prisma/sqlite/migrations/20211022055055_add_project_notification_email/migration.sql new file mode 100644 index 00000000..81be13e1 --- /dev/null +++ b/prisma/sqlite/migrations/20211022055055_add_project_notification_email/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "projects" ADD COLUMN "notification_email" TEXT; diff --git a/prisma/sqlite/schema.prisma b/prisma/sqlite/schema.prisma index 2c56f2e8..11b61858 100644 --- a/prisma/sqlite/schema.prisma +++ b/prisma/sqlite/schema.prisma @@ -92,6 +92,7 @@ model Project { fetchLatestCommentsAt DateTime? @map(name: "fetch_latest_comments_at") enableNotification Boolean? @default(true) @map(name: "enable_notification") + notificationEmail String? @map(name: "notification_email") webhook String? enableWebhook Boolean? diff --git a/service/notification.service.ts b/service/notification.service.ts index dc05202d..582e3d5f 100644 --- a/service/notification.service.ts +++ b/service/notification.service.ts @@ -26,6 +26,7 @@ export class NotificationService extends RequestScopeService { }, select: { enableNotification: true, + notificationEmail: true, owner: { select: { id: true, @@ -62,9 +63,9 @@ export class NotificationService extends RequestScopeService { }) const notificationEmail = - project.owner.notificationEmail || project.owner.email + project.notificationEmail || project.owner.notificationEmail || project.owner.email - if (project.owner.enableNewCommentNotification) { + if (project.owner.enableNewCommentNotification || project.notificationEmail) { let unsubscribeToken = this.tokenService.genUnsubscribeNewCommentToken( project.owner.id, ) diff --git a/utils.client.ts b/utils.client.ts index 3768f6e7..ac991917 100644 --- a/utils.client.ts +++ b/utils.client.ts @@ -8,3 +8,12 @@ export const apiClient = axios.create({ }); export const VERSION = '1.2.1' + +// From https://stackoverflow.com/questions/46155/how-to-validate-an-email-address-in-javascript +export function validateEmail(email: string) { + if (email === '') { + return true + } + const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return re.test(String(email).toLowerCase()); +}