From 63cd9846cbdcc33ee8e69aa8adbe086e2a297f7e Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Thu, 12 Sep 2024 12:34:33 +0530 Subject: [PATCH 01/14] Feat: added the determine find function to calculate the tedx payment amount --- src/.env.example | 5 ++++- src/app/api/submit-form/route.ts | 6 +++++ src/utils/determinePrice.ts | 38 ++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 src/utils/determinePrice.ts diff --git a/src/.env.example b/src/.env.example index 7d15426..8fea3e6 100644 --- a/src/.env.example +++ b/src/.env.example @@ -3,4 +3,7 @@ TURSO_AUTH_TOKEN = "" GOOGLE_ID = "" GOOGLE_SECRET = "" NEXT_PUBLIC_SITE_URL = "" -NEXTAUTH_SECRET = "" \ No newline at end of file +NEXTAUTH_SECRET = "" +SJEC_DISCOUNT_PRICE = 0 +REFERAL_DISCOUNT_PRICE = 0 +REGULAR_PRICE = 0 \ No newline at end of file diff --git a/src/app/api/submit-form/route.ts b/src/app/api/submit-form/route.ts index 1f38a18..35880ef 100644 --- a/src/app/api/submit-form/route.ts +++ b/src/app/api/submit-form/route.ts @@ -2,6 +2,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { NextRequest, NextResponse } from "next/server"; import { RegistrationFormSchema, TRegistrationForm } from "@/utils/zod-schemas"; import prisma from "@/server/db"; +import determinePrice from "@/utils/determinePrice"; export async function GET() { return NextResponse.json({ message: "Hello from the API!" }); @@ -34,6 +35,8 @@ export async function POST(req: NextRequest, res: NextResponse) { createdById, } = body; + let price = await determinePrice(email, referralId); + const newFormEntry = await prisma.form.create({ data: { name, @@ -58,3 +61,6 @@ export async function POST(req: NextRequest, res: NextResponse) { ); } } + + + diff --git a/src/utils/determinePrice.ts b/src/utils/determinePrice.ts new file mode 100644 index 0000000..b8dc1d4 --- /dev/null +++ b/src/utils/determinePrice.ts @@ -0,0 +1,38 @@ +import prisma from "@/server/db"; + +async function determinePrice(email: string, referralId: any) { + const sjecDiscountPrice = parseFloat(process.env.SJEC_DISCOUNT_PRICE ?? '800'); + const referralDiscountPrice = parseFloat(process.env.REFERAL_DISCOUNT_PRICE ?? '900'); + const regularPrice = 1000; // Default regular price + + let price; + + if (email.endsWith("@sjec.ac.in")) { + price = sjecDiscountPrice; + } else if (referralId) { + try { + const referral = await prisma.referral.findUnique({ + where: { id: referralId }, + }); + + if (referral) { + if (!referral.isUsed) { + price = referralDiscountPrice; + } else { + throw new Error("Referral code already used"); + } + } else { + throw new Error("Invalid referral code"); + } + } catch (error:any) { + console.error('Error determining price:', error.message); + price = regularPrice; + } + } else { + price = regularPrice; + } + + return price; + } + + export default determinePrice; \ No newline at end of file From f479f33291d7237ff2d07833cd8477b9e55fb2c6 Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Thu, 12 Sep 2024 20:07:02 +0530 Subject: [PATCH 02/14] Add getErrorMessage utility function --- src/utils/getErrorMessage.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/utils/getErrorMessage.ts diff --git a/src/utils/getErrorMessage.ts b/src/utils/getErrorMessage.ts new file mode 100644 index 0000000..1a36b22 --- /dev/null +++ b/src/utils/getErrorMessage.ts @@ -0,0 +1,16 @@ +const getErrorMessage = (error: unknown): string => { + let message: string; + + if (error instanceof Error) { + message = error.message; + } else if (error && typeof error === "object" && "message" in error) { + message = String(error.message); + } else if (typeof error === "string") { + message = error; + } else { + message = "Something went wrong"; + } + return message; +}; + +export default getErrorMessage; From c279f378bf63baada0346422c5608f74af72c996 Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Thu, 12 Sep 2024 20:09:05 +0530 Subject: [PATCH 03/14] Feat: Added verify-email page --- package-lock.json | 100 ++++++++++++++++++++++++++++++++++ package.json | 1 + src/app/verify-email/page.tsx | 60 ++++++++++++++++++++ 3 files changed, 161 insertions(+) create mode 100644 src/app/verify-email/page.tsx diff --git a/package-lock.json b/package-lock.json index 2b3d322..14b5a2e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@next-auth/prisma-adapter": "^1.0.7", "@prisma/adapter-libsql": "^5.19.1", "@prisma/client": "^5.19.1", + "axios": "^1.7.7", "jest": "^29.7.0", "next": "14.2.6", "next-auth": "^4.24.7", @@ -2361,6 +2362,12 @@ "tslib": "^2.4.0" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -2385,6 +2392,17 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/axobject-query": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", @@ -2841,6 +2859,18 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -3099,6 +3129,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/detect-libc": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", @@ -4036,6 +4075,26 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -4061,6 +4120,20 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -6227,6 +6300,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -7173,6 +7267,12 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/package.json b/package.json index 6c3a816..c7b0a92 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@next-auth/prisma-adapter": "^1.0.7", "@prisma/adapter-libsql": "^5.19.1", "@prisma/client": "^5.19.1", + "axios": "^1.7.7", "jest": "^29.7.0", "next": "14.2.6", "next-auth": "^4.24.7", diff --git a/src/app/verify-email/page.tsx b/src/app/verify-email/page.tsx new file mode 100644 index 0000000..19ce96a --- /dev/null +++ b/src/app/verify-email/page.tsx @@ -0,0 +1,60 @@ +"use client"; + +import getErrorMessage from "@/utils/getErrorMessage"; +import axios from "axios"; +import { get } from "http"; +import Link from "next/link"; +import React, { useEffect, useState } from "react"; + +export default function VerifyEmailPage() { + const [token, setToken] = useState(""); + const [verified, setVerified] = useState(false); + const [error, setError] = useState(false); + + const verifyUserEmail = async () => { + try { + await axios.post("/api/users/verify-email", { token }); + setVerified(true); + } catch (error: unknown) { + setError(true); + console.log(getErrorMessage(error)); + } + }; + + useEffect(() => { + const urlToken = window.location.search.split("=")[1]; + setToken(urlToken || ""); + }, []); + + useEffect(() => { + if (token.length > 0) { + verifyUserEmail(); + } + }, [token]); + + return ( +
+

Email Verification

+ {verified ? ( +
+

+ Your email has been successfully verified! +

+

You can now proceed to login.

+ +
+ ) : ( +
+

+ Error +

+

+ There was an error verifying your email. Please try again later. +

+
+ )} +
+ ); +} \ No newline at end of file From dc7313c39dc5d87824255552223149e9991fc3b2 Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Thu, 12 Sep 2024 20:54:22 +0530 Subject: [PATCH 04/14] Refactor: Add useSearchParams hook to get query params --- src/app/api/verify-email/route.ts | 0 src/app/verify-email/page.tsx | 11 ++++++----- 2 files changed, 6 insertions(+), 5 deletions(-) create mode 100644 src/app/api/verify-email/route.ts diff --git a/src/app/api/verify-email/route.ts b/src/app/api/verify-email/route.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/app/verify-email/page.tsx b/src/app/verify-email/page.tsx index 19ce96a..cae9bbf 100644 --- a/src/app/verify-email/page.tsx +++ b/src/app/verify-email/page.tsx @@ -2,11 +2,12 @@ import getErrorMessage from "@/utils/getErrorMessage"; import axios from "axios"; -import { get } from "http"; import Link from "next/link"; +import { useSearchParams } from "next/navigation"; import React, { useEffect, useState } from "react"; export default function VerifyEmailPage() { + const searchParams = useSearchParams(); const [token, setToken] = useState(""); const [verified, setVerified] = useState(false); const [error, setError] = useState(false); @@ -22,9 +23,9 @@ export default function VerifyEmailPage() { }; useEffect(() => { - const urlToken = window.location.search.split("=")[1]; + const urlToken = searchParams.get("token"); setToken(urlToken || ""); - }, []); + }, [searchParams]); useEffect(() => { if (token.length > 0) { @@ -42,7 +43,7 @@ export default function VerifyEmailPage() {

You can now proceed to login.

) : ( @@ -57,4 +58,4 @@ export default function VerifyEmailPage() { )} ); -} \ No newline at end of file +} From 8aae4ee061fa06cddbf4241b325fe8612a0b99b7 Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Thu, 12 Sep 2024 21:57:16 +0530 Subject: [PATCH 05/14] Feat: Added send mail function using node mailer --- package-lock.json | 21 ++++++++++++++++++ package.json | 2 ++ src/app/api/verify-email/route.ts | 36 +++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/package-lock.json b/package-lock.json index 14b5a2e..709c83a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,12 +16,14 @@ "jest": "^29.7.0", "next": "14.2.6", "next-auth": "^4.24.7", + "nodemailer": "^6.9.15", "react": "^18", "react-dom": "^18", "zod": "^3.23.8" }, "devDependencies": { "@types/node": "^20", + "@types/nodemailer": "^6.4.15", "@types/react": "^18", "@types/react-dom": "^18", "eslint": "^8", @@ -1875,6 +1877,16 @@ "undici-types": "~6.19.2" } }, + "node_modules/@types/nodemailer": { + "version": "6.4.15", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.15.tgz", + "integrity": "sha512-0EBJxawVNjPkng1zm2vopRctuWVCxk34JcIlRuXSf54habUWdz1FB7wHDqOqvDa8Mtpt0Q3LTXQkAs2LNyK5jQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/prop-types": { "version": "15.7.12", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", @@ -6550,6 +6562,15 @@ "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", "license": "MIT" }, + "node_modules/nodemailer": { + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.15.tgz", + "integrity": "sha512-AHf04ySLC6CIfuRtRiEYtGEXgRfa6INgWGluDhnxTZhHSKvrBu7lc1VVchQ0d8nPc4cFaZoPq8vkyNoZr0TpGQ==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", diff --git a/package.json b/package.json index c7b0a92..f06d574 100644 --- a/package.json +++ b/package.json @@ -19,12 +19,14 @@ "jest": "^29.7.0", "next": "14.2.6", "next-auth": "^4.24.7", + "nodemailer": "^6.9.15", "react": "^18", "react-dom": "^18", "zod": "^3.23.8" }, "devDependencies": { "@types/node": "^20", + "@types/nodemailer": "^6.4.15", "@types/react": "^18", "@types/react-dom": "^18", "eslint": "^8", diff --git a/src/app/api/verify-email/route.ts b/src/app/api/verify-email/route.ts index e69de29..5ea8cbe 100644 --- a/src/app/api/verify-email/route.ts +++ b/src/app/api/verify-email/route.ts @@ -0,0 +1,36 @@ +import nodemailer from "nodemailer"; + +export interface EmailOptions { + from: string; + to: string; + subject: string; + html: string; + text: string; +} + +interface sendEmail { + email: string; + name: string; + emailVeificationLink: string; +} + +export const sendEmail = async (options: sendEmail) => { + const transporter = nodemailer.createTransport({ + service: "gmail", + auth: { + user: process.env.GMAIL_USER, + pass: process.env.GMAIL_PASS, + }, + }); + + const mailOptions: EmailOptions = { + from: `"Tedx SJEC" <${process.env.GMAIL_USER}>`, + to: options.email, + subject: "Tedx SJEC Email verification", + html: `

Click on the link below to register for Tiara 2024

Register`, + text: `Click on the link below to register for Tiara 2024 ${options.emailVeificationLink}`, + }; + + const mailResponse = await transporter.sendMail(mailOptions); + return mailResponse; +}; From 0f0decba28b00c9cf2f79f9d9fa1440d3413ec91 Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Thu, 12 Sep 2024 22:01:09 +0530 Subject: [PATCH 06/14] Refactor: Move sendEmail function to src/utils/sendMail.ts --- src/app/api/verify-email/route.ts | 36 ------------------------------- src/utils/sendMail.ts | 36 +++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 36 deletions(-) create mode 100644 src/utils/sendMail.ts diff --git a/src/app/api/verify-email/route.ts b/src/app/api/verify-email/route.ts index 5ea8cbe..e69de29 100644 --- a/src/app/api/verify-email/route.ts +++ b/src/app/api/verify-email/route.ts @@ -1,36 +0,0 @@ -import nodemailer from "nodemailer"; - -export interface EmailOptions { - from: string; - to: string; - subject: string; - html: string; - text: string; -} - -interface sendEmail { - email: string; - name: string; - emailVeificationLink: string; -} - -export const sendEmail = async (options: sendEmail) => { - const transporter = nodemailer.createTransport({ - service: "gmail", - auth: { - user: process.env.GMAIL_USER, - pass: process.env.GMAIL_PASS, - }, - }); - - const mailOptions: EmailOptions = { - from: `"Tedx SJEC" <${process.env.GMAIL_USER}>`, - to: options.email, - subject: "Tedx SJEC Email verification", - html: `

Click on the link below to register for Tiara 2024

Register`, - text: `Click on the link below to register for Tiara 2024 ${options.emailVeificationLink}`, - }; - - const mailResponse = await transporter.sendMail(mailOptions); - return mailResponse; -}; diff --git a/src/utils/sendMail.ts b/src/utils/sendMail.ts new file mode 100644 index 0000000..5ea8cbe --- /dev/null +++ b/src/utils/sendMail.ts @@ -0,0 +1,36 @@ +import nodemailer from "nodemailer"; + +export interface EmailOptions { + from: string; + to: string; + subject: string; + html: string; + text: string; +} + +interface sendEmail { + email: string; + name: string; + emailVeificationLink: string; +} + +export const sendEmail = async (options: sendEmail) => { + const transporter = nodemailer.createTransport({ + service: "gmail", + auth: { + user: process.env.GMAIL_USER, + pass: process.env.GMAIL_PASS, + }, + }); + + const mailOptions: EmailOptions = { + from: `"Tedx SJEC" <${process.env.GMAIL_USER}>`, + to: options.email, + subject: "Tedx SJEC Email verification", + html: `

Click on the link below to register for Tiara 2024

Register`, + text: `Click on the link below to register for Tiara 2024 ${options.emailVeificationLink}`, + }; + + const mailResponse = await transporter.sendMail(mailOptions); + return mailResponse; +}; From 06927e1573e7875e86ea8151c3b37750efaebd4d Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Thu, 12 Sep 2024 22:27:01 +0530 Subject: [PATCH 07/14] Feat: successfully tested mailing option with postman --- src/app/api/verify-email/route.ts | 19 +++++++++++++++++++ src/utils/sendMail.ts | 3 +++ 2 files changed, 22 insertions(+) diff --git a/src/app/api/verify-email/route.ts b/src/app/api/verify-email/route.ts index e69de29..5570474 100644 --- a/src/app/api/verify-email/route.ts +++ b/src/app/api/verify-email/route.ts @@ -0,0 +1,19 @@ +import { NextRequest, NextResponse } from "next/server"; +import sendEmail from "@/utils/sendMail"; + +export async function POST(req: NextRequest) { + const body = await req.json(); + console.log(body); + const mailResponse = await sendEmail({ + email: body.email, + name: body.name, + emailVeificationLink: `http://localhost:3000/verify-email?token=${body.token}`, + }); + + return NextResponse.json({ message: "Email sent successfully!", mailResponse }); + +} + +export async function GET() { + return NextResponse.json({ message: "Hello from the Send mail!" }); +} \ No newline at end of file diff --git a/src/utils/sendMail.ts b/src/utils/sendMail.ts index 5ea8cbe..139eb4d 100644 --- a/src/utils/sendMail.ts +++ b/src/utils/sendMail.ts @@ -34,3 +34,6 @@ export const sendEmail = async (options: sendEmail) => { const mailResponse = await transporter.sendMail(mailOptions); return mailResponse; }; + + +export default sendEmail; \ No newline at end of file From 0812e653f8ac865e0b8041698590fdac2be20803 Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Thu, 12 Sep 2024 23:11:17 +0530 Subject: [PATCH 08/14] Refactor: verify email through otp process --- src/app/api/send-mail/route.ts | 19 +++++++++++++++++++ src/app/api/verify-email/route.ts | 12 ++++++------ src/app/page.tsx | 29 +++++++++++++++++++++++------ src/app/verify-email/page.tsx | 7 ++++--- src/utils/sendMail.ts | 6 +++--- 5 files changed, 55 insertions(+), 18 deletions(-) create mode 100644 src/app/api/send-mail/route.ts diff --git a/src/app/api/send-mail/route.ts b/src/app/api/send-mail/route.ts new file mode 100644 index 0000000..f272cf6 --- /dev/null +++ b/src/app/api/send-mail/route.ts @@ -0,0 +1,19 @@ +import { NextRequest, NextResponse } from "next/server"; +import sendEmail from "@/utils/sendMail"; + +export async function POST(req: NextRequest) { + const body = await req.json(); + console.log(body); + const mailResponse = await sendEmail({ + email: body.email, + name: body.name, + OTP: `http://localhost:3000/verify-email?token=${body.token}`, + }); + + return NextResponse.json({ message: "Email sent successfully!",mailResponse }); + +} + +export async function GET() { + return NextResponse.json({ message: "Hello from the Send mail!" }); +} \ No newline at end of file diff --git a/src/app/api/verify-email/route.ts b/src/app/api/verify-email/route.ts index 5570474..0c47a07 100644 --- a/src/app/api/verify-email/route.ts +++ b/src/app/api/verify-email/route.ts @@ -4,13 +4,13 @@ import sendEmail from "@/utils/sendMail"; export async function POST(req: NextRequest) { const body = await req.json(); console.log(body); - const mailResponse = await sendEmail({ - email: body.email, - name: body.name, - emailVeificationLink: `http://localhost:3000/verify-email?token=${body.token}`, - }); + // const mailResponse = await sendEmail({ + // email: body.email, + // name: body.name, + // emailVeificationLink: `http://localhost:3000/verify-email?token=${body.token}`, + // }); - return NextResponse.json({ message: "Email sent successfully!", mailResponse }); + return NextResponse.json({ message: "Email sent successfully!" }); } diff --git a/src/app/page.tsx b/src/app/page.tsx index 9e73346..c8d799c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,8 +1,25 @@ +"use client"; + import { getServerSideSession } from "@/lib/get-server-session"; -export default async function Home() { - const session = await getServerSideSession(); - if (!session) { - return
Please login
; - } - return
Hello {session?.user.role}
; +import { sendEmail } from "@/utils/sendMail"; +import axios from "axios"; +export default function Home() { + const session = ""; + + const sendVerificationEmail = async () => { + await axios.post("/api/send-mail", { + email: "joywinbennis0987@gmail.com", + name: "Joywin", + token: "1234", + }); + }; + +// if (!session) { +// return ( +//
+// +//
+// ); +// } + return
Hello
; } diff --git a/src/app/verify-email/page.tsx b/src/app/verify-email/page.tsx index cae9bbf..0892bd4 100644 --- a/src/app/verify-email/page.tsx +++ b/src/app/verify-email/page.tsx @@ -5,6 +5,7 @@ import axios from "axios"; import Link from "next/link"; import { useSearchParams } from "next/navigation"; import React, { useEffect, useState } from "react"; +import { sendEmail } from "@/utils/sendMail"; export default function VerifyEmailPage() { const searchParams = useSearchParams(); @@ -35,16 +36,16 @@ export default function VerifyEmailPage() { return (
-

Email Verification

+

Email Verification

{verified ? (

Your email has been successfully verified!

You can now proceed to login.

- + */}
) : (
diff --git a/src/utils/sendMail.ts b/src/utils/sendMail.ts index 139eb4d..e8c6fa0 100644 --- a/src/utils/sendMail.ts +++ b/src/utils/sendMail.ts @@ -11,7 +11,7 @@ export interface EmailOptions { interface sendEmail { email: string; name: string; - emailVeificationLink: string; + OTP: string; } export const sendEmail = async (options: sendEmail) => { @@ -27,8 +27,8 @@ export const sendEmail = async (options: sendEmail) => { from: `"Tedx SJEC" <${process.env.GMAIL_USER}>`, to: options.email, subject: "Tedx SJEC Email verification", - html: `

Click on the link below to register for Tiara 2024

Register`, - text: `Click on the link below to register for Tiara 2024 ${options.emailVeificationLink}`, + html: `

Click on the link below to register for Tiara 2024

Register`, + text: `Click on the link below to register for Tiara 2024 ${options.OTP}`, }; const mailResponse = await transporter.sendMail(mailOptions); From 4c16376c8af872a13968f079486275e2f33a873d Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Thu, 12 Sep 2024 23:19:20 +0530 Subject: [PATCH 09/14] Refactor: Add OTP generator for email verification --- package-lock.json | 18 ++++++++++++++++++ package.json | 2 ++ src/app/api/send-mail/route.ts | 17 +++++++++++++---- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 709c83a..65f8ecb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "next": "14.2.6", "next-auth": "^4.24.7", "nodemailer": "^6.9.15", + "otp-generator": "^4.0.1", "react": "^18", "react-dom": "^18", "zod": "^3.23.8" @@ -24,6 +25,7 @@ "devDependencies": { "@types/node": "^20", "@types/nodemailer": "^6.4.15", + "@types/otp-generator": "^4.0.2", "@types/react": "^18", "@types/react-dom": "^18", "eslint": "^8", @@ -1887,6 +1889,13 @@ "@types/node": "*" } }, + "node_modules/@types/otp-generator": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/otp-generator/-/otp-generator-4.0.2.tgz", + "integrity": "sha512-9+qqWzuFb332hXPbLgjUyOXlbcaTQkmkmqQjTduvNuOmPV5fW+iLv70JsVEhdUy0DWi4kY34++HDCaWl6N0AYg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/prop-types": { "version": "15.7.12", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", @@ -6818,6 +6827,15 @@ "node": ">= 0.8.0" } }, + "node_modules/otp-generator": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/otp-generator/-/otp-generator-4.0.1.tgz", + "integrity": "sha512-2TJ52vUftA0+J3eque4wwVtpaL4/NdIXDL0gFWFJFVUAZwAN7+9tltMhL7GCNYaHJtuONoier8Hayyj4HLbSag==", + "license": "MIT", + "engines": { + "node": ">=14.10.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", diff --git a/package.json b/package.json index f06d574..26c0063 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "next": "14.2.6", "next-auth": "^4.24.7", "nodemailer": "^6.9.15", + "otp-generator": "^4.0.1", "react": "^18", "react-dom": "^18", "zod": "^3.23.8" @@ -27,6 +28,7 @@ "devDependencies": { "@types/node": "^20", "@types/nodemailer": "^6.4.15", + "@types/otp-generator": "^4.0.2", "@types/react": "^18", "@types/react-dom": "^18", "eslint": "^8", diff --git a/src/app/api/send-mail/route.ts b/src/app/api/send-mail/route.ts index f272cf6..333a750 100644 --- a/src/app/api/send-mail/route.ts +++ b/src/app/api/send-mail/route.ts @@ -1,19 +1,28 @@ import { NextRequest, NextResponse } from "next/server"; import sendEmail from "@/utils/sendMail"; +import otpGenerator from "otp-generator"; export async function POST(req: NextRequest) { const body = await req.json(); + const otp = otpGenerator.generate(6, { + upperCaseAlphabets: false, + lowerCaseAlphabets: false, + specialChars: false, + }); + console.log(body); const mailResponse = await sendEmail({ email: body.email, name: body.name, - OTP: `http://localhost:3000/verify-email?token=${body.token}`, + OTP: otp, }); - return NextResponse.json({ message: "Email sent successfully!",mailResponse }); - + return NextResponse.json({ + message: "Email sent successfully!", + mailResponse, + }); } export async function GET() { return NextResponse.json({ message: "Hello from the Send mail!" }); -} \ No newline at end of file +} From 8444f84ac3783a79a9836f0fc02a644129456c55 Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Thu, 12 Sep 2024 23:19:42 +0530 Subject: [PATCH 10/14] Feat: Successful email verification through otp --- src/utils/sendMail.ts | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/utils/sendMail.ts b/src/utils/sendMail.ts index e8c6fa0..ab88457 100644 --- a/src/utils/sendMail.ts +++ b/src/utils/sendMail.ts @@ -26,14 +26,33 @@ export const sendEmail = async (options: sendEmail) => { const mailOptions: EmailOptions = { from: `"Tedx SJEC" <${process.env.GMAIL_USER}>`, to: options.email, - subject: "Tedx SJEC Email verification", - html: `

Click on the link below to register for Tiara 2024

Register`, - text: `Click on the link below to register for Tiara 2024 ${options.OTP}`, + subject: "Tedx SJEC - Your OTP for Email Verification", + html: ` +

Hello ${options.name},

+

Thank you for registering for Tedx 2024.

+

Your One-Time Password (OTP) for email verification is:

+

${options.OTP}

+

Please enter this OTP to complete your registration. The OTP is valid for 10 minutes.

+

Thank you!

+

Tedx SJEC Team

+ `, + text: ` + Hello ${options.name}, + + Thank you for registering for Tedx 2024. + + Your One-Time Password (OTP) for email verification is: ${options.OTP} + + Please enter this OTP to complete your registration. The OTP is valid for 10 minutes. + + Thank you! + + Tedx SJEC Team + `, }; const mailResponse = await transporter.sendMail(mailOptions); return mailResponse; }; - -export default sendEmail; \ No newline at end of file +export default sendEmail; From bfe181c6078535a24c1bd247d7b24e887dc5a455 Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Thu, 12 Sep 2024 23:26:28 +0530 Subject: [PATCH 11/14] Refactor: Update VerificationRequest model to include OTP for email verification --- prisma/dev.db | Bin 94208 -> 102400 bytes prisma/dev.db-journal | Bin 8720 -> 25136 bytes .../20240912175524_email/migration.sql | 24 ++++++++++++++++++ prisma/schema.prisma | 8 +++--- 4 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 prisma/migrations/20240912175524_email/migration.sql diff --git a/prisma/dev.db b/prisma/dev.db index 2b1122dcb81ab6f678d52c4f62ff7033632fe088..d4a05b3574799bfede85e7191e8c44770211209a 100644 GIT binary patch delta 648 zcmZp8z}m2YZGyDmR0akHNgx&nVrC!~oTy{WICW#f5`GRQz6T8ag?tY-3kqD}t8e6E z;tFl#Wi@4BVC76PH8(afN;B29G)qa=H8C|v*0nG)HPSUrv@l6AG%`v|Gc~a^GB!&} zF-tZ!NKP~|vrIEJO*2n5v@l9cN=h*|HB2=zw6HL?ut+pWH8M^$GDG%;bE_&h=Fr;&k?iGih|k&vOesi~1kd}?lDW)4{D%j81&$sA1l>lygh^WWGk zsIZt{M}s+(kr=lzsWXR8u9a6^WUye7g8&?Da$po#&ZxkspvYauz{TOmz<-`sndcX` zJXap)JI+oHKc3Az4D3%p5Ev-cY{E@0qU_?<){HIkC5cHnsd=g8@zWW77=>BjEMW=f zAXmo_SA`HqCm&Y@C7474bACy|ULdCI^V^O%_1f*qE_J9PA3P3#Q-qW)xFN7uru=6qj U?GyuIejw)CENJkCe^Gz}0E+ynTmS$7 delta 313 zcmZozz}E19b%M0u1O^5MaUg~P-ibQKj1x8{Ea7Kk!zdz2W@c@~jZy1|LQ}F)&j}vr$NxUECdLkU7F2s4}o_F}Q9K zgl+{ki0vf&8h1`Z|;CcbA3{DpkaHVZP{;;V1uVB!jG5HMB5FOiD^I zH#JN(F|@ESwy;PvNHsD}H8M&yOf@tyG)go#NwzdFFf=hSGB7eRurxF>G&eOhG7*VS z%}vbAVVw9pLG4Q;46@_bn_TO6iI0-Y=kbUQ<% znFzahtTAI_W@eKE#Cc5?K-$=tu|<6HWm#=TiOK(E^N_9CJXQKFBO3z)qiv)y$gar? zWHnhp+{tHUKe4$2-2fNYV3$IX-zHAm`y1~KUC?w1-?rzN3Xr7sul3FpDQB#;J zz9c_8H7`CpwGzY?6LSu7bqsM;2=(&}40ToT^mB2IP*4I%2_RG|Dfs(=1w}YCl)(Cx zHqX(1$H?N!$0k0xPuH2N$%>a<+}N10MS60MmiA;ey@QkAYqC#{)ppp-r_aXBQq6s6 FJphB3Ka>Cf diff --git a/prisma/migrations/20240912175524_email/migration.sql b/prisma/migrations/20240912175524_email/migration.sql new file mode 100644 index 0000000..f7bc7ca --- /dev/null +++ b/prisma/migrations/20240912175524_email/migration.sql @@ -0,0 +1,24 @@ +/* + Warnings: + + - You are about to drop the column `token` on the `VerificationRequest` table. All the data in the column will be lost. + - Added the required column `otp` to the `VerificationRequest` table without a default value. This is not possible if the table is not empty. + +*/ +-- RedefineTables +PRAGMA defer_foreign_keys=ON; +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_VerificationRequest" ( + "id" TEXT NOT NULL PRIMARY KEY, + "identifier" TEXT NOT NULL, + "otp" TEXT NOT NULL, + "expires" DATETIME NOT NULL, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME NOT NULL +); +INSERT INTO "new_VerificationRequest" ("created_at", "expires", "id", "identifier", "updated_at") SELECT "created_at", "expires", "id", "identifier", "updated_at" FROM "VerificationRequest"; +DROP TABLE "VerificationRequest"; +ALTER TABLE "new_VerificationRequest" RENAME TO "VerificationRequest"; +CREATE UNIQUE INDEX "VerificationRequest_identifier_otp_key" ON "VerificationRequest"("identifier", "otp"); +PRAGMA foreign_keys=ON; +PRAGMA defer_foreign_keys=OFF; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1b4fac9..de8607b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -97,11 +97,11 @@ model Form { model VerificationRequest { id String @id @default(cuid()) - identifier String - token String @unique + identifier String // Like email or phone number + otp String expires DateTime created_at DateTime @default(now()) updated_at DateTime @updatedAt - @@unique([identifier, token]) -} + @@unique([identifier, otp]) // Ensure OTP is unique for a given identifier +} \ No newline at end of file From 08ee61786307579c75cbf646e252bd3afe343558 Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Thu, 12 Sep 2024 23:37:39 +0530 Subject: [PATCH 12/14] feat: save verification otp request in database --- src/app/api/send-mail/route.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/app/api/send-mail/route.ts b/src/app/api/send-mail/route.ts index 333a750..586eb7c 100644 --- a/src/app/api/send-mail/route.ts +++ b/src/app/api/send-mail/route.ts @@ -1,6 +1,7 @@ import { NextRequest, NextResponse } from "next/server"; import sendEmail from "@/utils/sendMail"; import otpGenerator from "otp-generator"; +import prisma from "@/server/db"; export async function POST(req: NextRequest) { const body = await req.json(); @@ -10,6 +11,17 @@ export async function POST(req: NextRequest) { specialChars: false, }); + const expiresIn = 10; // 10 minutes + const expiresAt = new Date(Date.now() + expiresIn * 60 * 1000); + + await prisma.verificationRequest.create({ + data: { + identifier: body.email, + otp, + expires: expiresAt, + }, + }); + console.log(body); const mailResponse = await sendEmail({ email: body.email, From 2745c0a98d5ec8215c51f1e5fe0c5b06b5b2bfd5 Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Fri, 13 Sep 2024 00:00:10 +0530 Subject: [PATCH 13/14] Feat: successfully send otp and verified otp --- src/app/api/verify-email/route.ts | 19 --------- src/app/api/verify-mail/route.ts | 47 ++++++++++++++++++++ src/app/page.tsx | 71 ++++++++++++++++++++++++------- 3 files changed, 102 insertions(+), 35 deletions(-) delete mode 100644 src/app/api/verify-email/route.ts create mode 100644 src/app/api/verify-mail/route.ts diff --git a/src/app/api/verify-email/route.ts b/src/app/api/verify-email/route.ts deleted file mode 100644 index 0c47a07..0000000 --- a/src/app/api/verify-email/route.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import sendEmail from "@/utils/sendMail"; - -export async function POST(req: NextRequest) { - const body = await req.json(); - console.log(body); - // const mailResponse = await sendEmail({ - // email: body.email, - // name: body.name, - // emailVeificationLink: `http://localhost:3000/verify-email?token=${body.token}`, - // }); - - return NextResponse.json({ message: "Email sent successfully!" }); - -} - -export async function GET() { - return NextResponse.json({ message: "Hello from the Send mail!" }); -} \ No newline at end of file diff --git a/src/app/api/verify-mail/route.ts b/src/app/api/verify-mail/route.ts new file mode 100644 index 0000000..2c869fc --- /dev/null +++ b/src/app/api/verify-mail/route.ts @@ -0,0 +1,47 @@ +import { NextRequest, NextResponse } from "next/server"; +import sendEmail from "@/utils/sendMail"; +import prisma from "@/server/db"; + +export async function POST(req: NextRequest) { + const body = await req.json(); + console.log(body); + const { identifier, otp } = body; + + const request = await prisma.verificationRequest.findFirst({ + where: { + identifier, + otp, + expires: { + gte: new Date(), + }, + }, + orderBy: { + created_at: "desc", + }, + }); + + if (!request) { + return NextResponse.json( + { message: "Invalid or expired OTP", status: 400 }, + { status: 200 } + ); + } + + await prisma.verificationRequest.deleteMany({ + where: { + identifier, + }, + }); + + return NextResponse.json( + { + message: "OTP verified successfully back!", + status: 200, + }, + { status: 200 } + ); +} + +export async function GET() { + return NextResponse.json({ message: "Hello from the Send mail!" }); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index c8d799c..2374118 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,25 +1,64 @@ "use client"; -import { getServerSideSession } from "@/lib/get-server-session"; -import { sendEmail } from "@/utils/sendMail"; +import { useState } from "react"; import axios from "axios"; + export default function Home() { - const session = ""; + const [otp, setOtp] = useState(""); const sendVerificationEmail = async () => { - await axios.post("/api/send-mail", { - email: "joywinbennis0987@gmail.com", - name: "Joywin", - token: "1234", - }); + try { + await axios.post("/api/send-mail", { + email: "joywinbennis0987@gmail.com", + name: "Joywin", + }); + alert("Verification email sent!"); + } catch (error) { + console.error("Error sending email:", error); + } }; -// if (!session) { -// return ( -//
-// -//
-// ); -// } - return
Hello
; + return ( +
+

+ Welcome to the OTP Verification Page +

+ +
+ + setOtp(e.target.value)} + className="border border-gray-300 rounded-lg p-2 text-gray-700 focus:outline-none focus:border-blue-500" + /> + +
+
+ ); } From 04f94fb497e15f3c71718578d3c3fdf7e054bb19 Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Fri, 13 Sep 2024 00:16:49 +0530 Subject: [PATCH 14/14] Refactor: Remove unused code for email verification page --- src/app/verify-email/page.tsx | 62 ----------------------------------- 1 file changed, 62 deletions(-) delete mode 100644 src/app/verify-email/page.tsx diff --git a/src/app/verify-email/page.tsx b/src/app/verify-email/page.tsx deleted file mode 100644 index 0892bd4..0000000 --- a/src/app/verify-email/page.tsx +++ /dev/null @@ -1,62 +0,0 @@ -"use client"; - -import getErrorMessage from "@/utils/getErrorMessage"; -import axios from "axios"; -import Link from "next/link"; -import { useSearchParams } from "next/navigation"; -import React, { useEffect, useState } from "react"; -import { sendEmail } from "@/utils/sendMail"; - -export default function VerifyEmailPage() { - const searchParams = useSearchParams(); - const [token, setToken] = useState(""); - const [verified, setVerified] = useState(false); - const [error, setError] = useState(false); - - const verifyUserEmail = async () => { - try { - await axios.post("/api/users/verify-email", { token }); - setVerified(true); - } catch (error: unknown) { - setError(true); - console.log(getErrorMessage(error)); - } - }; - - useEffect(() => { - const urlToken = searchParams.get("token"); - setToken(urlToken || ""); - }, [searchParams]); - - useEffect(() => { - if (token.length > 0) { - verifyUserEmail(); - } - }, [token]); - - return ( -
-

Email Verification

- {verified ? ( -
-

- Your email has been successfully verified! -

-

You can now proceed to login.

- {/* */} -
- ) : ( -
-

- Error -

-

- There was an error verifying your email. Please try again later. -

-
- )} -
- ); -}