diff --git a/package-lock.json b/package-lock.json index 9e1ca0f..63c666f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "@radix-ui/react-slot": "^1.1.0", "@react-email/components": "^0.0.32", "@react-email/html": "^0.0.11", + "@react-email/render": "^1.0.4", "@react-email/section": "^0.0.16", "@react-email/text": "^0.0.11", "@sentry/nextjs": "^8.35.0", @@ -3668,23 +3669,6 @@ "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, - "node_modules/@react-email/components/node_modules/@react-email/render": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.0.4.tgz", - "integrity": "sha512-8ZXi89d8igBDE6W3zlHBa3GEDWKEUFDAa7i8MvVxnRViQuvsRbibK3ltuPgixxRI5+HgGNCSreBHQKZCkhUdyw==", - "dependencies": { - "html-to-text": "9.0.5", - "prettier": "3.4.2", - "react-promise-suspense": "0.3.4" - }, - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "react": "^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^18.0 || ^19.0 || ^19.0.0-rc" - } - }, "node_modules/@react-email/container": { "version": "0.0.15", "resolved": "https://registry.npmjs.org/@react-email/container/-/container-0.0.15.tgz", @@ -3796,12 +3780,12 @@ } }, "node_modules/@react-email/render": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.0.1.tgz", - "integrity": "sha512-W3gTrcmLOVYnG80QuUp22ReIT/xfLsVJ+n7ghSlG2BITB8evNABn1AO2rGQoXuK84zKtDAlxCdm3hRyIpZdGSA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.0.4.tgz", + "integrity": "sha512-8ZXi89d8igBDE6W3zlHBa3GEDWKEUFDAa7i8MvVxnRViQuvsRbibK3ltuPgixxRI5+HgGNCSreBHQKZCkhUdyw==", "dependencies": { "html-to-text": "9.0.5", - "js-beautify": "^1.14.11", + "prettier": "3.4.2", "react-promise-suspense": "0.3.4" }, "engines": { @@ -5403,11 +5387,11 @@ "dev": true }, "node_modules/abbrev": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", - "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.0.tgz", + "integrity": "sha512-+/kfrslGQ7TNV2ecmQwMJj/B65g5KVq1/L3SGVZ3tCYGqlzFuFCGBZJtMP99wH3NpEUyAjn0zPdPUg0D+DwrOA==", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/acorn": { @@ -10798,15 +10782,15 @@ } }, "node_modules/js-beautify": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz", - "integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.2.tgz", + "integrity": "sha512-mcG6CHJxxih+EFAbd5NEBwrosIs6MoJmiNLFYN6kj5SeJMf7n29Ii/H4lt6zGTvmdB9AApuj5cs4zydjuLeqjw==", "dependencies": { "config-chain": "^1.1.13", "editorconfig": "^1.0.4", - "glob": "^10.3.3", + "glob": "^11.0.0", "js-cookie": "^3.0.5", - "nopt": "^7.2.0" + "nopt": "^8.0.0" }, "bin": { "css-beautify": "js/bin/css-beautify.js", @@ -10826,33 +10810,58 @@ } }, "node_modules/js-beautify/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz", + "integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==", "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" }, + "engines": { + "node": "20 || >=22" + }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/js-beautify/node_modules/jackspeak": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", + "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-beautify/node_modules/lru-cache": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", + "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/js-beautify/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -10866,6 +10875,21 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/js-beautify/node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/js-cookie": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", @@ -11588,17 +11612,17 @@ "license": "MIT" }, "node_modules/nopt": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", - "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", + "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", "dependencies": { - "abbrev": "^2.0.0" + "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/normalize-path": { @@ -12871,6 +12895,23 @@ "node": ">=18" } }, + "node_modules/resend/node_modules/@react-email/render": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.0.1.tgz", + "integrity": "sha512-W3gTrcmLOVYnG80QuUp22ReIT/xfLsVJ+n7ghSlG2BITB8evNABn1AO2rGQoXuK84zKtDAlxCdm3hRyIpZdGSA==", + "dependencies": { + "html-to-text": "9.0.5", + "js-beautify": "^1.14.11", + "react-promise-suspense": "0.3.4" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "react": "^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^18.0 || ^19.0 || ^19.0.0-rc" + } + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", diff --git a/package.json b/package.json index c66d300..d47bfeb 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@radix-ui/react-slot": "^1.1.0", "@react-email/components": "^0.0.32", "@react-email/html": "^0.0.11", + "@react-email/render": "^1.0.4", "@react-email/section": "^0.0.16", "@react-email/text": "^0.0.11", "@sentry/nextjs": "^8.35.0", diff --git a/src/components/email/friend-request-template.tsx b/src/components/email/friend-request-template.tsx index 4001325..bbcd22f 100644 --- a/src/components/email/friend-request-template.tsx +++ b/src/components/email/friend-request-template.tsx @@ -9,6 +9,7 @@ import { Button, Hr, } from "@react-email/components"; +import { render } from "@react-email/render"; import * as React from "react"; interface FriendRequestEmailProps { @@ -16,7 +17,7 @@ interface FriendRequestEmailProps { fromUsername: string; } -export const FriendRequestEmail = ({ +const FriendRequestEmailTemplate = ({ username, fromUsername, }: FriendRequestEmailProps) => { @@ -115,3 +116,15 @@ const footer = { fontSize: "14px", lineHeight: "24px", }; + +export const FriendRequestEmail = (props: FriendRequestEmailProps) => { + const component = ; + const text = render(component, { + plainText: true, + }); + + return { + component, + text, + }; +}; diff --git a/src/components/email/game-complete-template.tsx b/src/components/email/game-complete-template.tsx index d2504c3..3d9bd61 100644 --- a/src/components/email/game-complete-template.tsx +++ b/src/components/email/game-complete-template.tsx @@ -9,6 +9,8 @@ import { Section, Heading, } from "@react-email/components"; +import { render } from "@react-email/render"; +import * as React from "react"; interface GameCompleteEmailProps { username: string; @@ -17,7 +19,7 @@ interface GameCompleteEmailProps { gameId: string; } -export const GameCompleteEmail = ({ +const GameCompleteEmailTemplate = ({ username, winnerUsername, isWinner, @@ -47,10 +49,7 @@ export const GameCompleteEmail = ({ Want to see the final scores? Check out the game details: - + View Game Details @@ -111,3 +110,15 @@ const h1 = { padding: "0", textAlign: "center" as const, }; + +export const GameCompleteEmail = (props: GameCompleteEmailProps) => { + const component = ; + const text = render(component, { + plainText: true, + }); + + return { + component, + text, + }; +}; diff --git a/src/components/email/welcome-template.tsx b/src/components/email/welcome-template.tsx index b1cc415..b63a430 100644 --- a/src/components/email/welcome-template.tsx +++ b/src/components/email/welcome-template.tsx @@ -9,14 +9,15 @@ import { Button, Hr, } from "@react-email/components"; +import { render } from "@react-email/render"; import * as React from "react"; interface WelcomeEmailProps { username: string; } -export const WelcomeEmail = ({ username }: WelcomeEmailProps) => { - const previewText = `Welcome to Blitzer - Let's start scoring!`; +const WelcomeEmailTemplate = ({ username }: WelcomeEmailProps) => { + const previewText = `Welcome to Blitzer - Let's start scoring!`; return ( @@ -42,8 +43,7 @@ export const WelcomeEmail = ({ username }: WelcomeEmailProps) => {
- If you have any questions, just reply to this email - we're - always happy to help. + This inbox isn't monitored, replies won't be read. @@ -112,3 +112,15 @@ const footer = { fontSize: "14px", lineHeight: "24px", }; + +export const WelcomeEmail = (props: WelcomeEmailProps) => { + const component = ; + const text = render(component, { + plainText: true, + }); + + return { + component, + text, + }; +}; diff --git a/src/server/email.ts b/src/server/email.ts index c04e089..7ba9f4f 100644 --- a/src/server/email.ts +++ b/src/server/email.ts @@ -21,7 +21,8 @@ export async function sendWelcomeEmail(params: { from: sender, to: [params.email], subject: "Welcome to Blitzer!", - react: WelcomeEmail({ username: params.username }), + react: WelcomeEmail({ username: params.username }).component, + text: await WelcomeEmail({ username: params.username }).text, }); if (error) { @@ -58,7 +59,13 @@ export async function sendGameCompleteEmail(params: { winnerUsername: params.winnerUsername, isWinner: params.isWinner, gameId: params.gameId, - }), + }).component, + text: await GameCompleteEmail({ + username: params.username, + winnerUsername: params.winnerUsername, + isWinner: params.isWinner, + gameId: params.gameId, + }).text, }); if (error) { @@ -89,7 +96,11 @@ export async function sendFriendRequestEmail(params: { react: FriendRequestEmail({ username: params.username, fromUsername: params.fromUsername, - }), + }).component, + text: await FriendRequestEmail({ + username: params.username, + fromUsername: params.fromUsername, + }).text, }); if (error) {