From a87598021e1b11a787364a94ef2ac30e1006ba4f Mon Sep 17 00:00:00 2001 From: Don Strawsburg Date: Wed, 29 Jan 2025 08:13:07 -0500 Subject: [PATCH] working --- .env | 10 +- application.log | 1 + package.json | 8 +- pnpm-lock.yaml | 26 ++ src/app/(auth)/login/discord/route.ts | 2 +- src/app/(auth)/login/login.tsx | 4 +- .../reset-password/[token]/reset-password.tsx | 4 +- .../reset-password/send-reset-email.tsx | 4 +- src/app/(auth)/signup/signup.tsx | 4 +- src/app/(auth)/verify-email/verify-code.tsx | 6 +- src/app/api/auth/signin/route.tsx | 5 +- src/app/api/users/route.ts | 2 +- .../components/PopNav-keep}/page.tsx | 20 +- src/app/components/PopNav/page.tsx | 99 +++++ src/app/components/PopNavDialog/page.tsx | 400 ++++++++++++++++++ src/components/icons copy.tsx | 115 ----- src/lib/email/index.tsx | 8 +- 17 files changed, 573 insertions(+), 145 deletions(-) rename src/{components/PopNav => app/components/PopNav-keep}/page.tsx (96%) create mode 100644 src/app/components/PopNav/page.tsx create mode 100644 src/app/components/PopNavDialog/page.tsx delete mode 100644 src/components/icons copy.tsx diff --git a/.env b/.env index 5ec3beb..7118096 100644 --- a/.env +++ b/.env @@ -11,12 +11,12 @@ DATABASE_URL="postgresql://postgres:cul8rman@portainer.dev.gofwd.group:5433/ball # DATABASE_URL='postgresql://postgres:cul8rman@r710.gofwd.group:5433/luciatest' NEXT_PUBLIC_APP_URL='http://localhost:3000' -MOCK_SEND_EMAIL=true +MOCK_SEND_EMAIL=false -SMTP_HOST='smtp.example-host.com' -SMTP_PORT=25 -SMTP_USER='smtp_example_username' -SMTP_PASSWORD='smtp_example_password' +SMTP_HOST='smtp-relay.brevo.com' +SMTP_PORT=587 +SMTP_USER='79f6e8001@smtp-brevo.com' +SMTP_PASSWORD='RWg5dz6x1kVAtEnS' DISCORD_CLIENT_ID='discord_client_id' DISCORD_CLIENT_SECRET='discord_client_secret' diff --git a/application.log b/application.log index 73dec9a..67d1962 100644 --- a/application.log +++ b/application.log @@ -1 +1,2 @@ [2025-01-28T04:58:56.488Z] [INFO] 📨 Email sent to: don@strawsburg.com with template: EmailVerification and props: {"code":"70365595"} +[2025-01-28T22:29:40.567Z] [INFO] 📨 Email sent to: dstrawsb@gmail.com with template: EmailVerification and props: {"code":"36553870"} diff --git a/package.json b/package.json index aaf0636..4bcf2d7 100644 --- a/package.json +++ b/package.json @@ -30,12 +30,12 @@ "@react-email/render": "^1.0.4", "@t3-oss/env-nextjs": "^0.12.0", "@tanstack/react-query": "^4.35.3", - - "@trpc/react-query": "^10.45.2", - "@trpc/server": "^10.45.2", "@trpc/client": "^10.45.2", "@trpc/next": "^10.45.2", + "@trpc/react-query": "^10.45.2", + "@trpc/server": "^10.45.2", "@types/bcryptjs": "^2.4.6", + "@types/js-cookie": "^3.0.6", "arctic": "^3.2.1", "bcryptjs": "^2.4.3", "class-variance-authority": "^0.7.0", @@ -43,6 +43,7 @@ "dotenv": "^16.4.7", "fontsource-roboto": "^4.0.0", "framer-motion": "^11.18.0", + "js-cookies": "^1.0.4", "lucia": "^3.2.2", "lucide-react": "^0.460.0", "next": "15.1.0", @@ -69,6 +70,7 @@ "@auth/drizzle-adapter": "^1.7.4", "@types/bun": "^1.1.13", "@types/node": "^20.17.10", + "@types/nodemailer": "^6.4.17", "@types/pg": "^8.11.10", "@types/react": "^18", "@types/react-dom": "^18", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3ac324b..38d05ee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -86,6 +86,9 @@ importers: '@types/bcryptjs': specifier: ^2.4.6 version: 2.4.6 + '@types/js-cookie': + specifier: ^3.0.6 + version: 3.0.6 arctic: specifier: ^3.2.1 version: 3.2.1 @@ -107,6 +110,9 @@ importers: framer-motion: specifier: ^11.18.0 version: 11.18.2(@emotion/is-prop-valid@1.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + js-cookies: + specifier: ^1.0.4 + version: 1.0.4 lucia: specifier: ^3.2.2 version: 3.2.2 @@ -180,6 +186,9 @@ importers: '@types/node': specifier: ^20.17.10 version: 20.17.16 + '@types/nodemailer': + specifier: ^6.4.17 + version: 6.4.17 '@types/pg': specifier: ^8.11.10 version: 8.11.11 @@ -2072,12 +2081,18 @@ packages: '@types/cookie@0.6.0': resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + '@types/js-cookie@3.0.6': + resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} + '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} '@types/node@20.17.16': resolution: {integrity: sha512-vOTpLduLkZXePLxHiHsBLp98mHGnl8RptV4YAO3HfKO5UHjDvySGbxKtpYfy8Sx5+WKcgc45qNreJJRVM3L6mw==} + '@types/nodemailer@6.4.17': + resolution: {integrity: sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==} + '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -3240,6 +3255,9 @@ packages: resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} engines: {node: '>=14'} + js-cookies@1.0.4: + resolution: {integrity: sha512-cO1SHDH7zJsi8FihHmDtcWx90mWmrfGOrcLKPeaEX6tLyuTK2wnzgdmNa34Q6rNAd6VhQUgjDt5Eyl90VI/Fpg==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -5918,12 +5936,18 @@ snapshots: '@types/cookie@0.6.0': {} + '@types/js-cookie@3.0.6': {} + '@types/json5@0.0.29': {} '@types/node@20.17.16': dependencies: undici-types: 6.19.8 + '@types/nodemailer@6.4.17': + dependencies: + '@types/node': 20.17.16 + '@types/parse-json@4.0.2': {} '@types/pg@8.11.11': @@ -7299,6 +7323,8 @@ snapshots: js-cookie@3.0.5: {} + js-cookies@1.0.4: {} + js-tokens@4.0.0: {} js-yaml@4.1.0: diff --git a/src/app/(auth)/login/discord/route.ts b/src/app/(auth)/login/discord/route.ts index 89c01c2..0ae6351 100644 --- a/src/app/(auth)/login/discord/route.ts +++ b/src/app/(auth)/login/discord/route.ts @@ -9,7 +9,7 @@ export async function GET(): Promise { scopes: ["identify", "email"], }); - cookies().set("discord_oauth_state", state, { + (await cookies()).set("discord_oauth_state", state, { path: "/", secure: env.NODE_ENV === "production", httpOnly: true, diff --git a/src/app/(auth)/login/login.tsx b/src/app/(auth)/login/login.tsx index c1b5e7a..8778a5e 100644 --- a/src/app/(auth)/login/login.tsx +++ b/src/app/(auth)/login/login.tsx @@ -1,7 +1,7 @@ "use client"; import Link from "next/link"; -import { useFormState } from "react-dom"; +import { useActionState } from "react"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; @@ -13,7 +13,7 @@ import { Label } from "@/components/ui/label"; import { SubmitButton } from "@/components/submit-button"; export function Login() { - const [state, formAction] = useFormState(login, null); + const [state, formAction] = useActionState(login, null); return ( diff --git a/src/app/(auth)/reset-password/[token]/reset-password.tsx b/src/app/(auth)/reset-password/[token]/reset-password.tsx index a3f4add..9d20c89 100644 --- a/src/app/(auth)/reset-password/[token]/reset-password.tsx +++ b/src/app/(auth)/reset-password/[token]/reset-password.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect } from "react"; -import { useFormState } from "react-dom"; +import { useActionState } from "react-dom"; import { toast } from "sonner"; import { ExclamationTriangleIcon } from "@/components/icons"; import { SubmitButton } from "@/components/submit-button"; @@ -10,7 +10,7 @@ import { Label } from "@/components/ui/label"; import { resetPassword } from "@/lib/auth/actions"; export function ResetPassword({ token }: { token: string }) { - const [state, formAction] = useFormState(resetPassword, null); + const [state, formAction] = useActionState(resetPassword, null); useEffect(() => { if (state?.error) { diff --git a/src/app/(auth)/reset-password/send-reset-email.tsx b/src/app/(auth)/reset-password/send-reset-email.tsx index e37e48a..685d520 100644 --- a/src/app/(auth)/reset-password/send-reset-email.tsx +++ b/src/app/(auth)/reset-password/send-reset-email.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect } from "react"; -import { useFormState } from "react-dom"; +import { useActionState } from "react-dom"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { toast } from "sonner"; @@ -14,7 +14,7 @@ import { ExclamationTriangleIcon } from "@/components/icons"; import { Paths } from "@/lib/constants"; export function SendResetEmail() { - const [state, formAction] = useFormState(sendPasswordResetLink, null); + const [state, formAction] = useActionState(sendPasswordResetLink, null); const router = useRouter(); useEffect(() => { diff --git a/src/app/(auth)/signup/signup.tsx b/src/app/(auth)/signup/signup.tsx index 3467be9..0a06fdd 100644 --- a/src/app/(auth)/signup/signup.tsx +++ b/src/app/(auth)/signup/signup.tsx @@ -1,6 +1,6 @@ "use client"; -import { useFormState } from "react-dom"; +import { useActionState } from "react"; import Link from "next/link"; import { PasswordInput } from "@/components/password-input"; import { Button } from "@/components/ui/button"; @@ -13,7 +13,7 @@ import { signup } from "@/lib/auth/actions"; import { SubmitButton } from "@/components/submit-button"; export function Signup() { - const [state, formAction] = useFormState(signup, null); + const [state, formAction] = useActionState(signup, null); return ( diff --git a/src/app/(auth)/verify-email/verify-code.tsx b/src/app/(auth)/verify-email/verify-code.tsx index d1eab32..31aafcc 100644 --- a/src/app/(auth)/verify-email/verify-code.tsx +++ b/src/app/(auth)/verify-email/verify-code.tsx @@ -2,15 +2,15 @@ import { Input } from "@/components/ui/input"; import { Label } from "@radix-ui/react-label"; import { useEffect, useRef } from "react"; -import { useFormState } from "react-dom"; +import { useActionState } from "react-dom"; import { toast } from "sonner"; import { ExclamationTriangleIcon } from "@/components/icons"; import { logout, verifyEmail, resendVerificationEmail as resendEmail } from "@/lib/auth/actions"; import { SubmitButton } from "@/components/submit-button"; export const VerifyCode = () => { - const [verifyEmailState, verifyEmailAction] = useFormState(verifyEmail, null); - const [resendState, resendAction] = useFormState(resendEmail, null); + const [verifyEmailState, verifyEmailAction] = useActionState(verifyEmail, null); + const [resendState, resendAction] = useActionState(resendEmail, null); const codeFormRef = useRef(null); useEffect(() => { diff --git a/src/app/api/auth/signin/route.tsx b/src/app/api/auth/signin/route.tsx index 709d74a..ae977a2 100644 --- a/src/app/api/auth/signin/route.tsx +++ b/src/app/api/auth/signin/route.tsx @@ -20,7 +20,10 @@ export async function POST(request: Request) { } // Compare the provided password with the stored hashed password - const isPasswordValid = await bcrypt.compare(password, user.password_hash); + if (!user.hashedPassword) { + return NextResponse.json({ error: 'Invalid email or password' }, { status: 401 }); + } + const isPasswordValid = await bcrypt.compare(password, user.hashedPassword); if (!isPasswordValid) { return NextResponse.json({ error: 'Invalid email or password' }, { status: 401 }); diff --git a/src/app/api/users/route.ts b/src/app/api/users/route.ts index ca473a6..88d6a7c 100644 --- a/src/app/api/users/route.ts +++ b/src/app/api/users/route.ts @@ -7,7 +7,7 @@ import { NextResponse } from "next/server"; export async function GET(request: Request) { try { - // const { email, password } = await request.json(); + const { email, password } = await request.json(); // Fetch the user from the database const data = await db.select().from(users) diff --git a/src/components/PopNav/page.tsx b/src/app/components/PopNav-keep/page.tsx similarity index 96% rename from src/components/PopNav/page.tsx rename to src/app/components/PopNav-keep/page.tsx index 26e7da3..7f9b773 100644 --- a/src/components/PopNav/page.tsx +++ b/src/app/components/PopNav-keep/page.tsx @@ -1,6 +1,5 @@ "use client"; - -import { Fragment, useState } from "react"; +import { Fragment, useEffect, useState } from "react"; import { Dialog, DialogBackdrop, @@ -21,6 +20,9 @@ import { ShoppingBagIcon, XMarkIcon, } from "@heroicons/react/24/outline"; +import { validateRequest } from "@/lib/auth/validate-request"; +import { User } from "lucia"; +import Cookies from "js-cookie"; const navigation = { categories: [ @@ -86,8 +88,18 @@ const navigation = { }; export default function PopNav() { - const [open, setOpen] = useState(false); + const [open, setOpen] = useState(false); + const [user, setUser] = useState(null); + useEffect(() => { + const fetchUser = async () => { + const result = null; //(await validateRequest()); +/* if (result.user) { + setUser(result.user); + } */ + }; + fetchUser(); + }, []); return (
{/* Mobile menu */} @@ -153,7 +165,7 @@ export default function PopNav() { {item.name}
))} diff --git a/src/app/components/PopNav/page.tsx b/src/app/components/PopNav/page.tsx new file mode 100644 index 0000000..8ef12f4 --- /dev/null +++ b/src/app/components/PopNav/page.tsx @@ -0,0 +1,99 @@ +import { Fragment, } from "react"; +import { + Dialog, + DialogBackdrop, + DialogPanel, + Popover, + PopoverButton, + PopoverGroup, + PopoverPanel, + Tab, + TabGroup, + TabList, + TabPanel, + TabPanels, +} from "@headlessui/react"; +import { + Bars3Icon, + MagnifyingGlassIcon, + ShoppingBagIcon, + XMarkIcon, +} from "@heroicons/react/24/outline"; +import { validateRequest } from "@/lib/auth/validate-request"; +import { User } from "lucia"; +import {cookies} from 'next/headers'; +import PopNavDialog from "../PopNavDialog/page"; + +const navigation = { + categories: [ + { + id: "armory", + name: "Armory", + featured: [ + { + name: "Build Alpha", + href: "#", + imageSrc: + "https://images.unsplash.com/photo-1700774607099-8c4631ee9764?q=80&w=2940&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", + imageAlt: "Rad AR15.", + }, + { + name: "Build Beta", + href: "#", + imageSrc: + "https://images.unsplash.com/photo-1669489890884-baff10f74b49?q=80&w=2899&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", + imageAlt: "Rad AR15.", + }, + ], + sections: [ + { + id: "lower-parts", + name: "Lower Parts", + items: [ + { name: "Lower Receivers", href: "/Products/lowers" }, + { name: "Grips", href: "/Products/grips" }, + { name: "Magazines", href: "/Products/magazines" }, + { name: "Stocks", href: "/Products/stocks" }, + { name: "Triggers", href: "/Products/triggers" }, + { name: "Parts", href: "/Products/parts" }, + ], + }, + { + id: "upper-parts", + name: "Upper Parts", + items: [ + { name: "Upper Receiver", href: "/Products/uppers" }, + { name: "Barrels", href: "/Products/barrels" }, + { name: "Handguards", href: "/Products/handguards" }, + { name: "Muzzle Devices", href: "/Products/muzzle-devices" }, + ], + }, + { + id: "brands", + name: "Top Selling Brands", + items: [ + { name: "Radian Weapons", href: "#" }, + { name: "Noveske", href: "#" }, + { name: "Aero Precision", href: "#" }, + { name: "Primary Arms", href: "#" }, + ], + }, + ], + }, + ], + pages: [ + + { name: "Single Product", href: "/product" }, + ], +}; + +export default async function PopNav() { + const cookieStore = await cookies(); + const session = cookieStore.get('session'); + + return ( +
{session?.value} + +
+ ); +} diff --git a/src/app/components/PopNavDialog/page.tsx b/src/app/components/PopNavDialog/page.tsx new file mode 100644 index 0000000..6aff56a --- /dev/null +++ b/src/app/components/PopNavDialog/page.tsx @@ -0,0 +1,400 @@ +"use client"; +import { Fragment, useEffect, useState } from "react"; +import { + Dialog, + DialogBackdrop, + DialogPanel, + Popover, + PopoverButton, + PopoverGroup, + PopoverPanel, + Tab, + TabGroup, + TabList, + TabPanel, + TabPanels, +} from "@headlessui/react"; +import { + Bars3Icon, + MagnifyingGlassIcon, + ShoppingBagIcon, + XMarkIcon, +} from "@heroicons/react/24/outline"; +import { validateRequest } from "@/lib/auth/validate-request"; +import { User } from "lucia"; +import Cookies from "js-cookie"; + +const navigation = { + categories: [ + { + id: "armory", + name: "Armory", + featured: [ + { + name: "Build Alpha", + href: "#", + imageSrc: + "https://images.unsplash.com/photo-1700774607099-8c4631ee9764?q=80&w=2940&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", + imageAlt: "Rad AR15.", + }, + { + name: "Build Beta", + href: "#", + imageSrc: + "https://images.unsplash.com/photo-1669489890884-baff10f74b49?q=80&w=2899&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", + imageAlt: "Rad AR15.", + }, + ], + sections: [ + { + id: "lower-parts", + name: "Lower Parts", + items: [ + { name: "Lower Receivers", href: "/Products/lowers" }, + { name: "Grips", href: "/Products/grips" }, + { name: "Magazines", href: "/Products/magazines" }, + { name: "Stocks", href: "/Products/stocks" }, + { name: "Triggers", href: "/Products/triggers" }, + { name: "Parts", href: "/Products/parts" }, + ], + }, + { + id: "upper-parts", + name: "Upper Parts", + items: [ + { name: "Upper Receiver", href: "/Products/uppers" }, + { name: "Barrels", href: "/Products/barrels" }, + { name: "Handguards", href: "/Products/handguards" }, + { name: "Muzzle Devices", href: "/Products/muzzle-devices" }, + ], + }, + { + id: "brands", + name: "Top Selling Brands", + items: [ + { name: "Radian Weapons", href: "#" }, + { name: "Noveske", href: "#" }, + { name: "Aero Precision", href: "#" }, + { name: "Primary Arms", href: "#" }, + ], + }, + ], + }, + ], + pages: [ + + { name: "Single Product", href: "/product" }, + ], +}; + +export default function PopNavDialog(props:any) { + const [open, setOpen] = useState(false); + const [user, setUser] = useState(null); + + useEffect(() => { + const fetchUser = async () => { + const result = null; //(await validateRequest()); +/* if (result.user) { + setUser(result.user); + } */ + }; + fetchUser(); + }, []); + return ( + <> + {/* Mobile menu */} + + + +
+ +
+ +
+ + {/* Links */} + +
+ + {navigation.categories.map((category) => ( + + {category.name} + + ))} + +
+ + {navigation.categories.map((category) => ( + +
+ {category.featured.map((item) => ( +
+ {item.imageAlt} + + + +
+ ))} +
+ {category.sections.map((section) => ( +
+

+ {section.name} +

+ +
+ ))} +
+ ))} +
+
+ +
+ {navigation.pages.map((page) => ( + + ))} +
+ + + + +
+
+
+ +
+ {/*

+ Get free delivery on orders over $100 +

*/} + + +
+ + ); +} diff --git a/src/components/icons copy.tsx b/src/components/icons copy.tsx deleted file mode 100644 index 1fb48eb..0000000 --- a/src/components/icons copy.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { forwardRef, type SVGProps } from "react"; -import { cn } from "@/lib/utils"; - -const AnimatedSpinner = forwardRef>( - ({ className, ...props }, ref) => ( - - - - - - - - - - - - ), -); -AnimatedSpinner.displayName = "AnimatedSpinner"; - -const CreditCard = forwardRef>( - ({ className, ...props }, ref) => ( - - - - - ), -); -CreditCard.displayName = "CreditCard"; - -export { AnimatedSpinner, CreditCard }; - -export { - EyeOpenIcon, - EyeNoneIcon as EyeCloseIcon, - SunIcon, - MoonIcon, - ExclamationTriangleIcon, - ExitIcon, - EnterIcon, - GearIcon, - RocketIcon, - PlusIcon, - HamburgerMenuIcon, - Pencil2Icon, - UpdateIcon, - CheckCircledIcon, - PlayIcon, - TrashIcon, - ArchiveIcon, - ResetIcon, - DiscordLogoIcon, - FileTextIcon, - IdCardIcon, - PlusCircledIcon, - FilePlusIcon, - CheckIcon, - ChevronLeftIcon, - ChevronRightIcon, - DotsHorizontalIcon, - ArrowLeftIcon, -} from "@radix-ui/react-icons"; diff --git a/src/lib/email/index.tsx b/src/lib/email/index.tsx index be06295..d1b5a0d 100644 --- a/src/lib/email/index.tsx +++ b/src/lib/email/index.tsx @@ -19,19 +19,19 @@ export type PropsMap = { [EmailTemplate.PasswordReset]: ComponentProps; }; -const getEmailTemplate = (template: T, props: PropsMap[NoInfer]) => { +const getEmailTemplate = async (template: T, props: PropsMap[NoInfer]) => { switch (template) { case EmailTemplate.EmailVerification: return { subject: "Verify your email address", - body: render( + body: await render( , ), }; case EmailTemplate.PasswordReset: return { subject: "Reset your password", - body: render( + body: await render( , ), }; @@ -61,7 +61,7 @@ export const sendMail = async ( return; } - const { subject, body } = getEmailTemplate(template, props); + const { subject, body } = await getEmailTemplate(template, props); return transporter.sendMail({ from: EMAIL_SENDER, to, subject, html: body }); };