From ffad4050de09460a46e90546400ec436acabffe9 Mon Sep 17 00:00:00 2001 From: KMKoushik Date: Wed, 10 Apr 2024 08:35:03 +1000 Subject: [PATCH] Add click and open tracking --- .../app/(dashboard)/domains/domain-list.tsx | 137 ++++++++++++------ apps/web/src/server/api/routers/domain.ts | 24 ++- apps/web/src/server/aws/setup.ts | 6 + apps/web/src/server/service/domain-service.ts | 10 ++ apps/web/src/server/service/email-service.ts | 22 +++ apps/web/src/server/ses.ts | 3 + apps/web/src/utils/constants.ts | 1 + 7 files changed, 156 insertions(+), 47 deletions(-) diff --git a/apps/web/src/app/(dashboard)/domains/domain-list.tsx b/apps/web/src/app/(dashboard)/domains/domain-list.tsx index 24884f8..3552199 100644 --- a/apps/web/src/app/(dashboard)/domains/domain-list.tsx +++ b/apps/web/src/app/(dashboard)/domains/domain-list.tsx @@ -1,10 +1,11 @@ "use client"; -import { DomainStatus } from "@prisma/client"; +import { Domain, DomainStatus } from "@prisma/client"; import { formatDistanceToNow } from "date-fns"; import Link from "next/link"; import { Switch } from "@unsend/ui/src/switch"; import { api } from "~/trpc/react"; +import React from "react"; export default function DomainsList() { const domainsQuery = api.domain.domains.useQuery(); @@ -14,51 +15,7 @@ export default function DomainsList() {
{!domainsQuery.isLoading && domainsQuery.data?.length ? ( domainsQuery.data?.map((domain) => ( -
-
- -
-
- - {domain.name} - - -
-
-
-

- Created at -

-

- {formatDistanceToNow(new Date(domain.createdAt), { - addSuffix: true, - })} -

-
-
-

Region

- -

- 🇺🇸 {domain.region} -

-
-
-
-
-

Click tracking

- -
-
-

Open tracking

- -
-
-
-
-
+ )) ) : (
No domains
@@ -68,6 +25,94 @@ export default function DomainsList() { ); } +const DomainItem: React.FC<{ domain: Domain }> = ({ domain }) => { + const updateDomain = api.domain.updateDomain.useMutation(); + const utils = api.useUtils(); + + const [clickTracking, setClickTracking] = React.useState( + domain.clickTracking + ); + const [openTracking, setOpenTracking] = React.useState(domain.openTracking); + + function handleClickTrackingChange() { + setClickTracking(!clickTracking); + updateDomain.mutate( + { id: domain.id, clickTracking: !clickTracking }, + { + onSuccess: () => { + utils.domain.domains.invalidate(); + }, + } + ); + } + + function handleOpenTrackingChange() { + setOpenTracking(!openTracking); + updateDomain.mutate( + { id: domain.id, openTracking: !openTracking }, + { + onSuccess: () => { + utils.domain.domains.invalidate(); + }, + } + ); + } + + return ( +
+
+ +
+
+ + {domain.name} + + +
+
+
+

Created at

+

+ {formatDistanceToNow(new Date(domain.createdAt), { + addSuffix: true, + })} +

+
+
+

Region

+ +

+ 🇺🇸 {domain.region} +

+
+
+
+
+

Click tracking

+ +
+
+

Open tracking

+ +
+
+
+
+
+ ); +}; + const DomainStatusBadge: React.FC<{ status: DomainStatus }> = ({ status }) => { let badgeColor = "bg-gray-400/10 text-gray-500 border-gray-400/10"; // Default color switch (status) { diff --git a/apps/web/src/server/api/routers/domain.ts b/apps/web/src/server/api/routers/domain.ts index 4a09465..b1f73d9 100644 --- a/apps/web/src/server/api/routers/domain.ts +++ b/apps/web/src/server/api/routers/domain.ts @@ -7,7 +7,11 @@ import { teamProcedure, } from "~/server/api/trpc"; import { db } from "~/server/db"; -import { createDomain, getDomain } from "~/server/service/domain-service"; +import { + createDomain, + getDomain, + updateDomain, +} from "~/server/service/domain-service"; export const domainRouter = createTRPCRouter({ createDomain: teamProcedure @@ -21,6 +25,9 @@ export const domainRouter = createTRPCRouter({ where: { teamId: ctx.team.id, }, + orderBy: { + createdAt: "desc", + }, }); return domains; @@ -31,4 +38,19 @@ export const domainRouter = createTRPCRouter({ .query(async ({ ctx, input }) => { return getDomain(input.id); }), + + updateDomain: teamProcedure + .input( + z.object({ + id: z.number(), + clickTracking: z.boolean().optional(), + openTracking: z.boolean().optional(), + }) + ) + .mutation(async ({ ctx, input }) => { + return updateDomain(input.id, { + clickTracking: input.clickTracking, + openTracking: input.openTracking, + }); + }), }); diff --git a/apps/web/src/server/aws/setup.ts b/apps/web/src/server/aws/setup.ts index bebbc0b..24d664c 100644 --- a/apps/web/src/server/aws/setup.ts +++ b/apps/web/src/server/aws/setup.ts @@ -78,6 +78,12 @@ async function setupSESConfiguration() { topicArn, [...GENERAL_EVENTS, "OPEN"] ); + + await setWebhookConfiguration(APP_SETTINGS.SES_CONFIGURATION_FULL, topicArn, [ + ...GENERAL_EVENTS, + "CLICK", + "OPEN", + ]); } async function setWebhookConfiguration( diff --git a/apps/web/src/server/service/domain-service.ts b/apps/web/src/server/service/domain-service.ts index 044a859..852cf89 100644 --- a/apps/web/src/server/service/domain-service.ts +++ b/apps/web/src/server/service/domain-service.ts @@ -67,3 +67,13 @@ export async function getDomain(id: number) { return domain; } + +export async function updateDomain( + id: number, + data: { clickTracking?: boolean; openTracking?: boolean } +) { + return db.domain.update({ + where: { id }, + data, + }); +} diff --git a/apps/web/src/server/service/email-service.ts b/apps/web/src/server/service/email-service.ts index 8883e5b..489cbb5 100644 --- a/apps/web/src/server/service/email-service.ts +++ b/apps/web/src/server/service/email-service.ts @@ -1,6 +1,7 @@ import { EmailContent } from "~/types"; import { db } from "../db"; import { sendEmailThroughSes } from "../ses"; +import { APP_SETTINGS } from "~/utils/constants"; export async function sendEmail( emailContent: EmailContent & { teamId: number } @@ -30,6 +31,10 @@ export async function sendEmail( text, html, region: domain.region, + configurationSetName: getConfigurationSetName( + domain.clickTracking, + domain.openTracking + ), }); if (messageId) { @@ -47,3 +52,20 @@ export async function sendEmail( }); } } + +function getConfigurationSetName( + clickTracking: boolean, + openTracking: boolean +) { + if (clickTracking && openTracking) { + return APP_SETTINGS.SES_CONFIGURATION_FULL; + } + if (clickTracking) { + return APP_SETTINGS.SES_CONFIGURATION_CLICK_TRACKING; + } + if (openTracking) { + return APP_SETTINGS.SES_CONFIGURATION_OPEN_TRACKING; + } + + return APP_SETTINGS.SES_CONFIGURATION_GENERAL; +} diff --git a/apps/web/src/server/ses.ts b/apps/web/src/server/ses.ts index 818d0eb..b0aac9c 100644 --- a/apps/web/src/server/ses.ts +++ b/apps/web/src/server/ses.ts @@ -97,8 +97,10 @@ export async function sendEmailThroughSes({ text, html, region = "us-east-1", + configurationSetName, }: EmailContent & { region?: string; + configurationSetName: string; }) { const sesClient = getSesClient(region); const command = new SendEmailCommand({ @@ -128,6 +130,7 @@ export async function sendEmailThroughSes({ }, }, }, + ConfigurationSetName: configurationSetName, }); try { diff --git a/apps/web/src/utils/constants.ts b/apps/web/src/utils/constants.ts index 33d6c44..b4768d3 100644 --- a/apps/web/src/utils/constants.ts +++ b/apps/web/src/utils/constants.ts @@ -5,4 +5,5 @@ export const APP_SETTINGS = { SES_CONFIGURATION_GENERAL: `SES_CONFIGURATION_GENERAL_${env.NODE_ENV}`, SES_CONFIGURATION_CLICK_TRACKING: `SES_CONFIGURATION_CLICK_TRACKING_${env.NODE_ENV}`, SES_CONFIGURATION_OPEN_TRACKING: `SES_CONFIGURATION_OPEN_TRACKING_${env.NODE_ENV}`, + SES_CONFIGURATION_FULL: `SES_CONFIGURATION_FULL_${env.NODE_ENV}`, };