From 58d7ed8cccf2e47bcbc3bf5ef68aee407805dc97 Mon Sep 17 00:00:00 2001 From: Pontus Abrahamsson Date: Tue, 14 Nov 2023 00:54:49 +0100 Subject: [PATCH] Email wip --- apps/dashboard/src/jobs/transactions.ts | 40 +++- apps/website/package.json | 2 +- apps/website/public/email/logo.png | Bin 0 -> 4386 bytes apps/website/src/middleware.ts | 2 +- bun.lockb | Bin 344136 -> 343768 bytes package.json | 1 + packages/email/emails/transactions.tsx | 249 +++++++++++++++--------- packages/email/package.json | 3 +- packages/notification/src/index.ts | 27 ++- 9 files changed, 227 insertions(+), 97 deletions(-) create mode 100644 apps/website/public/email/logo.png diff --git a/apps/dashboard/src/jobs/transactions.ts b/apps/dashboard/src/jobs/transactions.ts index b6e7bb89b0..335f197c60 100644 --- a/apps/dashboard/src/jobs/transactions.ts +++ b/apps/dashboard/src/jobs/transactions.ts @@ -1,5 +1,6 @@ import { client } from "@/trigger"; import { getTransactions } from "@midday/gocardless"; +import { TriggerEvents, trigger, triggerBulk } from "@midday/notification"; import { Database } from "@midday/supabase/src/types"; import { eventTrigger } from "@trigger.dev/sdk"; import { Supabase, SupabaseManagement } from "@trigger.dev/supabase"; @@ -82,7 +83,7 @@ client.defineJob({ await dynamicSchedule.register(payload.record.id, { type: "interval", options: { - seconds: 10 * 60, // 10 minutes + seconds: 3600 * 4, // every 4h }, }); }, @@ -139,6 +140,43 @@ client.defineJob({ .select(); if (transactionsData?.length && transactionsData.length > 0) { + // Send notification for each transaction + triggerBulk( + transactionsData.map((transaction) => ({ + name: TriggerEvents.TransactionNewInApp, + payload: { + html: "TODO", + }, + users: [ + { + subscriberId: "", + teamId: "123", + email: "", + fullName: "Pontus Abrahamsson", + avatarUrl: "https://", + }, + ], + })) + ); + + // Send email with react-email-template + trigger({ + name: TriggerEvents.TransactionNewEmail, + payload: { + subject: "New transactions", + html: "TODO", + }, + users: [ + { + subscriberId: "", + teamId: "123", + email: "", + fullName: "Pontus Abrahamsson", + avatarUrl: "https://", + }, + ], + }); + revalidateTag(`transactions_${data?.team_id}`); revalidateTag(`spending_${data?.team_id}`); revalidateTag(`metrics_${data?.team_id}`); diff --git a/apps/website/package.json b/apps/website/package.json index 1ae7fe3020..2fda4ce798 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -5,7 +5,7 @@ "scripts": { "build": "next build", "clean": "git clean -xdf .next .turbo node_modules", - "dev": "next dev", + "dev": "next dev -p 3000", "lint": "next lint", "format": "biome format --write .", "start": "next start", diff --git a/apps/website/public/email/logo.png b/apps/website/public/email/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..4117f08a24dcd1283b79a8e3579f83748c83151c GIT binary patch literal 4386 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 literal 0 HcmV?d00001 diff --git a/apps/website/src/middleware.ts b/apps/website/src/middleware.ts index 4f6188593b..7bf3f8e3a0 100644 --- a/apps/website/src/middleware.ts +++ b/apps/website/src/middleware.ts @@ -11,5 +11,5 @@ export function middleware(request: NextRequest) { } export const config = { - matcher: ["/((?!api|_next/static|_next/image|favicon.ico|auth).*)"], + matcher: ["/((?!.*\\.|api|_next/static|_next/image|favicon.ico|auth\\/).*)"], }; diff --git a/bun.lockb b/bun.lockb index ad40bfa178fda2894b3843ee99d6ac8cd4c34c9a..29c7bdb053757ed22c43cab84f2994708c24acb4 100755 GIT binary patch delta 11383 zcmeHNd3a6N-ab2fA2OOnA|fGX2}vYDCP9$MB*-L4V$NV5V>&?-ZIvR}v|LnsDQ!{1 zNsCHUd$n4sw5F=5C^gp@!uP%>Kc494`JVfHPw!vXdh+J|{oeIk!(L~fz1G=l?aQlu ztgP1jR?SPR_FJ=~j@6M$Hmi3o4=b5ESaV*GGUCvNBi42!f9#(h7ZGYSFeNTXabqlRsnkh54__)ljyQSFQ+P(0#;AVy10oGd{B&6Bd_`5ybeRT5%SB9SsW`2g~{|+;Bu#_938BCT6hDrq$*$RP~qLbNB zmiRAYc6_+RjgUAp%`xJWX-*J-f@UhOc%leoHk=D)g;PZ*)6By^=Fb#(OEo^ycr$rX^P ze1I&7_3*VA_(=&t2fIDC|cvk|*)E|idGG<3gk{|f=Z%SWJ-3DZx3dLRV1z=v+Ww9lUZ+VFza;`ovfmLgR=-^R_Fp|1#aRu z0Q2$G49rxKSz&Y0$!y3|d@_gL7tDJ7z>N174gj-#gT)YL1?`bR4rYKb3x-HR&SK2( zDEVY|xQqB?#&-qR04Ix|0%lx4Fw3WdO?6;Qguz5+PjkTxoF+P%{tPe+z9u@E{%rAI z#w<5S^2v;!2c}eLG<34`bP+uEcrn$#Fgv6}XGQNy+;WK{Yx?)@N*!kwUn4S^{`+7& z>)e&rCb}QN<(GMzgpuvw7lWB!0_NlDsO0~RT>hB(8!JRNU+gIFv{TY?GR<@N#|t}e zMEe~0ixNtAa}<|7FaXTx09PEN_HQt^{U1)+QM2fSm-A`GHmy zpUnIkU`kH-#|oT<>w?(_H}T!UOfO?L*iiDx^i7Rmu&1pg{EUN*&&~hOw5YGDKN!sPPh2kvVD; zL?^TIDPT%7L?<(Drubxze!loGW5yRCpP$;Bm1LncM+%TR%yY#jv%>kpgKdfURIwuubw`#?0R?ab}4lv*VwO zPiEW>@t-qqXr|{v1)mjsAq97X+2ay0ukdT({a_|C;}3v2LO%-s3}(4wU?wulpAx_M zGovE2r@tbAd>YJ(&zUotD21;U=H;XEYGH1Bhr_xAv7a|Kfu#;f?icQxzxE zihE_HtpjUyaA=f!H+x@UeiN(A;*+cU75nUHf5JCzc5%m2)Ajf+cOT|lc>H_a7RA+P z?QhY{{-hrC+KTAV{Rjt0s);{*TB^M9RX?*R(S6}GsQxA8} zZIR};;c)TR9gCYJ+`SSQxi!=Iu~*_n|0fgU`<~4T^R_{({&vl2>wqNL46aB%>WzCy!KLmj-V@*B7F^IKOQc41-{=aKetp-w|@r5?RfW7Na?k0u9iOFKXJ`*}MX=`qvM zy^oh3x?EsaGA+e#Vt`NdLi+~hg*_eiZ`@;W%xaL->7Y5US8AuK*K4((e8Bzqo%^2m zX6E&~7-g^vk6wSEx7I(fsik|G<(KZ3irrfozG3HEt#_ZvZuThrQAoqn`-)d(ZZ;=3 zURF1~m&cyz+Meyn%Er^*J1aN$#iVpSxuX7s&<&qOf42VgO>>(1&V*$X^fGp@ezzrK z)A~59aXh%({;RvH_nzrj`grcWQ?vB%9B< zg>T~@AMq}YNIG@-SR+gK4AxjL8n#vJUW@dd`L&#GSAFM^daqB?xYX^d7xd}i_ud7~>9y7g;Pd?{7r@+H$&DAa$3wuYz`!&+9`o~%KoKW&!&ae8%CdDai%7`14 z|BRZl+o^u-wF`UZq}9u{>6_@XU~$A+ry{4wozwk}6}(>BW1~k=Xr1<>z8`qJB;p6p zLB2H}b?fo4%pqxXlG&K2w9~?_ENkPuugBI!-tJ&B} zE(boSHTU4+0DWFJie`{MVkHitFDe; zz2g?KfB(6dRpyUNly-$-O>eY*9GR+ld2ZOTv0>2U`Z<-?jaiiz-*wv{r`*xwXRO)Z zGw0}+j@n0WopOnsxOn%ec^khRc4bQEf={-@-Y!{o?N4*#1gmzMzHN|IlzCl>mA^5} z@zc`0f(turT9*BqQc&V{boAmH*1eaow%bQ+I_p%(}5_ z+@WRFdh8r9?empu+s08D=o_?ycv?sKNT)7`0aO5H6aK&SS~I*?=pDP|<#aL!ZSu=t`_wvv#y(U6OGA zk_ptC$ErcK`QC-;nq=`c+8#YOR&A<8>#I>XB$)Rz(k+Q_fZai~+oJJJUMFa*_)lnj zOU(D8o%LI+qKkyvp-=2CU*UBV&0aLVh>sM_K{P&(y6YX|)RoF6{cN0C)3jN-+ZYjO z$KVR=1elsisrs;s0lv;`CUI`CHvvp7B+ec7b@@@(QnUup_*R^+y*)*12)l22oXOxN z6?#CdFNIr6U?XVHw(z_qt}!%jG4he>D_RrSv!QVq{6yokZKPa&fYjFv8n<3KG;J+f zYwV`Lc?5^4odmXk{fB7nMQaJ|C%r*D2JNV11xc0{vL=caELtmQ(_q&EhlqyO472cg z)dq(`PbO1NbC!_ESjx`-A4 zdm!KhbQP^F>_C928#MN*9l&v}3q(p>d)ThHS=_FR3s?v?ypv&^m($K;tMx18ZP24U)JR*w40U zO@k#c7UHi`;ZO;TgEj;j8yhBZJzx)#xGaf_hh~{=!P%nqgv~8jHkKo0d%@3F-mIV9R2J(2(lA&Eh92=VejiZnPToP@P z#Pxx8ndcywawRYo_7w@70u6tLz5sWx+0p4zxF2kO#_Xs`wEnQ~BaWTQ6Dyz5kGCk&TLbhl32GC4d4k%mdTV@dya6A;7x2?BC#X@bF)+DBxDnV4 zdSr6K^%Ox<;x6g9~_ zC`Hv2AMQgO0e%9`;9_@yxj9kaS&pJ8%4cLl(WYR*XGaRuCsuLrmSoREe= z%LKB45dbG7o=aH)`~z4C6aodn>%bepo4{OP9xwvnL^KK*4P*m38n(xxVZ`VuebsDz zUtjf%*9r9T7l6M+rvTG|aljZL6BrHT03(4c{dhmM>F@?Hxx-KeTo-TwY5+BXT0m{U z5h%lTJONbL=fM{M9&@M+SOYeIEx^MdhXFp+xo6Jj_G15Q9SfX6dbKm+(nnFm?;h&~2jzu$6J;0^FVU_8K660Lz2 z08durp&h=Q=h>qlVIKy*1Lnie17-kmKo1}u=&2cWw{+F$&>K!7kPKAQo2RSYQtVNm z1HdCCPa*S+3ZKv?0X~#@xQ1^yX8<#SHnh=JKJ=MD6b54`co@Kka|)0O^wqyfS3OL8 z=Kcoo7(_l$0Pv7UCNw*MkI>72KQtbrxd+?;_Tpn-1$+!F2iBm;Q0QD?b_S-P*}>5M zg}j|WF>Ef{IssY8TZfnraoHm?)EMVoaNY+#0JspF4P*gP`k@RpDUutZp8{KeBdBK& z@D;EH;QHZH)YlL7ayh^QFS}8+7+3^wnt#UA_3;DLIFlQS9EH3Dp(B8i0O!>)fH%OI zWIyu-6Tp#Zg7arpu^)jS17*N3z+{x;xhPJF{eb?!hq%2SfFA;<0ntsC>B|PfvxYb& zT>^dwt^j8N&LFIiv&MDcCU6Jf5huCwpJ03uXL+OuPD@C(!Q>iV5(YE>1F<4s(MMWekw^{G+mu!!)4P> z)E}?cHmOa0d%)##I!@&J;PMp{?oLdph_vt|X?m59KEb3mF|RVI-wxMXELZEAv#zOk zI#llO-^#m{ufaUnODoiLPhZVXnW<;^YBOwa`rs0fy#4g6zFN4sRU567m2J9Tx#}Be zp6su!YpnR2JyNxn_O<+5jT<`I#}Bo&@fw&hB4bQ$D}VEVq1p^ZZ}_g(K(C*r+2{_L znqLJsJ5vjI?$*?Q%G8=YciTU=s_L_|H17)TsiCOqW|r3Yxn`qx$ktlC=vLO>$<~@u zb2p#O)*@6(wz>OAZ9tG-y_K<{ezTF$sDJW6vp1h!uldhVeOh^&>v$SHG>aADZH&^C zHu|Ar&BOeWpRt3{>=bC!728pPxY3aI1?umF8XM_nf{e*_)q=`J_Yk9g%~z{y?iXxq fV`bYTq+DJX!r19HH^q<8I|du;nL|1l?UerkNCcUw delta 11713 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 diff --git a/package.json b/package.json index 95c8794722..6f21da7484 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dev": "turbo dev --parallel", "start:dashboard": "turbo dev --filter=@midday/dashboard", "dev:dashboard": "turbo dev --filter=@midday/dashboard", + "dev:website": "turbo dev --filter=@midday/website", "dev:desktop": "turbo dev --filter=@midday/desktop", "jobs:dashboard": "turbo jobs --filter=@midday/dashboard", "format": "biome format --write .", diff --git a/packages/email/emails/transactions.tsx b/packages/email/emails/transactions.tsx index 06471845cb..bc79373d4f 100644 --- a/packages/email/emails/transactions.tsx +++ b/packages/email/emails/transactions.tsx @@ -1,8 +1,10 @@ +import { cn } from "@midday/ui/utils"; import { Body, Button, Column, Container, + Font, Head, Heading, Hr, @@ -15,124 +17,197 @@ import { Tailwind, Text, } from "@react-email/components"; +import { format } from "date-fns"; import * as React from "react"; +type Transaction = { + id: string; + date: string; + amount: number; + name: string; + currency: string; +}; + interface TransactionsEmailEmailProps { - username?: string; - userImage?: string; - invitedByUsername?: string; - invitedByEmail?: string; - teamName?: string; - teamImage?: string; - inviteLink?: string; - inviteFromIp?: string; - inviteFromLocation?: string; + firstName: string; + transactions: Transaction[]; + locale: string; } -const baseUrl = process.env.VERCEL_URL - ? `https://${process.env.VERCEL_URL}` - : ""; +const defaultTransactions = [ + { + id: "1", + date: new Date().toISOString(), + amount: -1000, + currency: "USD", + name: "Spotify", + }, + { + id: "2", + date: new Date().toISOString(), + amount: 1000, + currency: "USD", + name: "H23504959", + }, + { + id: "3", + date: new Date().toISOString(), + amount: -1000, + currency: "USD", + name: "Webflow", + }, + { + id: "4", + date: new Date().toISOString(), + amount: -1000, + currency: "USD", + name: "Netflix", + }, +]; + +const baseUrl = + process.env.NODE_ENV === "production" + ? "https://midday.ai" + : "http://localhost:3000/email"; + +const baseAppUrl = + process.env.NODE_ENV === "production" + ? "https://app.midday.ai" + : "http://localhost:3001"; export const TransactionsEmail = ({ - username = "zenorocha", - userImage = `${baseUrl}/static/vercel-user.png`, - invitedByUsername = "bukinoshita", - invitedByEmail = "bukinoshita@example.com", - teamName = "My Project", - teamImage = `${baseUrl}/static/vercel-team.png`, - inviteLink = "https://vercel.com/teams/invite/foo", - inviteFromIp = "204.13.186.218", - inviteFromLocation = "São Paulo, Brazil", + firstName = "Viktor", + transactions = defaultTransactions, + locale = "en", }: TransactionsEmailEmailProps) => { - const previewText = `Join ${invitedByUsername} on Vercel`; + const previewText = `Hi ${firstName}, We found 5 transactions thats missing receipts. Feel free to attach them to ease your own or your accountants work for upcoming declerations.`; return ( - + + + + {previewText} - +
Vercel
- - Join {teamName} on Vercel + + You have 5 transactions thats + missing
+ receipts.
- Hello {username}, - - - bukinoshita ( - - {invitedByEmail} - - ) has invited you to the {teamName} team on{" "} - Vercel. + Hi {firstName}, We found{" "} + 5 transactions thats missing + receipts. Feel free to attach them to ease your own or your + accountants work for upcoming declerations. -
- - - - - - invited you to - - - - - -
+ + + + + + + + + + + + {transactions.map((transaction) => ( + + + + + + ))} + +
+ + Date + + + + To/From + + + + Amount + +
+ + {format(new Date(transaction.date), "MMM d")} + + + 0 && "text-[#00C969]" + )} + > + + {transaction.name} + + + + 0 && "text-[#00C969]" + )} + > + {Intl.NumberFormat(locale, { + style: "currency", + currency: transaction.currency, + }).format(transaction.amount)} + +
+
- - or copy and paste this URL into your browser:{" "} - - {inviteLink} - -
- This invitation was intended for{" "} - {username} .This invite was - sent from {inviteFromIp}{" "} - located in{" "} - {inviteFromLocation}. If you - were not expecting this invitation, you can ignore this email. If - you are concerned about your account's safety, please reply to - this email to get in touch with us. + Nam imperdiet congue volutpat. Nulla quis facilisis lacus. Vivamus + convallis sit amet lectus eget tincidunt. Vestibulum vehicula + rutrum nisl, sed faucibus neque. Donec lacus mi, rhoncus at dictum + eget, pulvinar at metus. Donec cursus tellus erat, a hendrerit + elit rutrum ut. Fusce quis tristique ligula. Etiam sit amet enim + vitae mauris auctor blandit id et nibh.
diff --git a/packages/email/package.json b/packages/email/package.json index 860d53d11c..c68c83d10a 100644 --- a/packages/email/package.json +++ b/packages/email/package.json @@ -7,11 +7,12 @@ "lint": "biome check .", "format": "biome format --write .", "check:types": "tsc --noEmit", - "dev": "email dev -p 3002", + "dev": "email dev -p 3003", "export": "email export" }, "dependencies": { "@react-email/components": "0.0.6", + "date-fns": "^2.30.0", "react-email": "1.9.3" }, "devDependencies": { diff --git a/packages/notification/src/index.ts b/packages/notification/src/index.ts index 873e4c698b..90e675ebca 100644 --- a/packages/notification/src/index.ts +++ b/packages/notification/src/index.ts @@ -4,7 +4,8 @@ const novu = new Novu(process.env.NOVU_API_KEY!); const API_ENDPOINT = "https://api.novu.co/v1"; export enum TriggerEvents { - TransactionNew = "transaction_new", + TransactionNewInApp = "transaction_new_in_app", + TransactionNewEmail = "transaction_new_email", } type TriggerUser = { @@ -16,15 +17,14 @@ type TriggerUser = { }; type TriggerPayload = { - event: TriggerEvents; - html?: string; + name: TriggerEvents; payload: any; users: TriggerUser[]; tenant?: string; // NOTE: Currently no way to listen for messages with tenant, we use team_id + user_id for unique }; export async function trigger(data: TriggerPayload) { - return novu.trigger(data.event, { + return novu.trigger(data.name, { to: data.users.map((user) => ({ ...user, // Prefix subscriber id with team id @@ -35,6 +35,21 @@ export async function trigger(data: TriggerPayload) { }); } +export async function triggerBulk(events: TriggerPayload[]) { + return novu.bulkTrigger( + events.map((data) => ({ + name: data.name, + to: data.users.map((user) => ({ + ...user, + // Prefix subscriber id with team id + subscriberId: `${user.teamId}_${user.subscriberId}`, + })), + payload: data.payload, + tenant: data.tenant, + })) + ); +} + type GetSubscriberPreferencesParams = { teamId: string; subscriberId: string; @@ -51,7 +66,7 @@ export async function getSubscriberPreferences({ headers: { Authorization: `ApiKey ${process.env.NOVU_API_KEY!}`, }, - }, + } ); return response.json(); @@ -86,7 +101,7 @@ export async function updateSubscriberPreference({ enabled, }, }), - }, + } ); return response.json();