Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions pages/api/project/[projectId].ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, {
Expand All @@ -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
},
})

Expand Down
63 changes: 54 additions & 9 deletions pages/dashboard/project/[projectId].tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
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'
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'
Expand Down Expand Up @@ -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({
Expand All @@ -284,6 +285,7 @@ function Settings(props: {
}

const webhookInputRef = useRef<HTMLInputElement>(null)
const notificationEmailInputRef = useRef<HTMLInputElement>(null)

const uploadMutation = useMutation(upload, {
onSuccess(data) {
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -472,11 +509,18 @@ function Settings(props: {
<Heading as="h1" size="md">Email Notification</Heading>

</HStack>
<Box>
<Link href="/user" fontSize="sm">
Advanced Notification Settings
</Link>
</Box>
<FormControl>
<FormLabel>Project Notification Email</FormLabel>
<InputGroup>
<Input defaultValue={props.project.notificationEmail} type="email" ref={notificationEmailInputRef}></Input>
<InputRightElement width='16'>
<Button size="sm" isLoading={updateNotificationEmailMutation.isLoading} onClick={onSaveNotificationEmail}>Save</Button>
</InputRightElement>
</InputGroup>
<FormHelperText>
This overrides the user's <Link href="/user" color="gray.900">notification settings</Link>.
</FormHelperText>
</FormControl>
</VStack>

<VStack alignItems="start">
Expand Down Expand Up @@ -524,7 +568,7 @@ function Settings(props: {
)
}

type ProjectServerSideProps = Pick<Project, 'ownerId' | 'id' | 'title' | 'token' | 'enableNotification' | 'webhook' | 'enableWebhook'>
type ProjectServerSideProps = Pick<Project, 'ownerId' | 'id' | 'title' | 'token' | 'enableNotification' | 'webhook' | 'enableWebhook' | 'notificationEmail'>

export async function getServerSideProps(ctx) {
const projectService = new ProjectService(ctx.req)
Expand Down Expand Up @@ -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
}

Expand Down
12 changes: 1 addition & 11 deletions pages/user.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions prisma/pgsql/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "projects" ADD COLUMN "notification_email" TEXT;
1 change: 1 addition & 0 deletions prisma/sqlite/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
5 changes: 3 additions & 2 deletions service/notification.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export class NotificationService extends RequestScopeService {
},
select: {
enableNotification: true,
notificationEmail: true,
owner: {
select: {
id: true,
Expand Down Expand Up @@ -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,
)
Expand Down
9 changes: 9 additions & 0 deletions utils.client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}