From eab20aa096d279024fd94dd3b7f5a6d88b1ef645 Mon Sep 17 00:00:00 2001 From: IhorMasechko Date: Mon, 7 Apr 2025 20:35:07 +0300 Subject: [PATCH 01/18] Added Gmail messenger --- veterans/app/api/sendEmail/route.test.ts | 67 +++---- veterans/app/api/sendEmail/route.ts | 24 ++- veterans/helpers/handleSendEmail.ts | 2 + veterans/package-lock.json | 230 +++-------------------- veterans/package.json | 3 +- 5 files changed, 70 insertions(+), 256 deletions(-) diff --git a/veterans/app/api/sendEmail/route.test.ts b/veterans/app/api/sendEmail/route.test.ts index 44c4f023..9574729f 100644 --- a/veterans/app/api/sendEmail/route.test.ts +++ b/veterans/app/api/sendEmail/route.test.ts @@ -1,9 +1,17 @@ +jest.mock('nodemailer', () => ({ + createTransport: jest.fn().mockReturnValue({ + sendMail: jest.fn().mockResolvedValue({ messageId: 'test-id' }), + }), +})); + jest.mock('next/server', () => ({ NextResponse: { - json: jest.fn().mockImplementation((body, { status }) => ({ - status, - json: async () => body, - })), + json: jest.fn().mockImplementation((body, { status }) => { + return new Response(JSON.stringify(body), { + status, + headers: { 'Content-Type': 'application/json' }, + }); + }), }, })); @@ -14,12 +22,6 @@ describe('POST /api/sendEmail', () => { }); it('should return error if "to" is missing', async () => { - jest.doMock('resend', () => ({ - Resend: jest.fn(() => ({ - emails: { send: jest.fn() }, - })), - })); - const { POST } = await import('./route'); const req = { json: jest.fn().mockResolvedValue({ to: '', message: 'Hello' }), @@ -33,12 +35,6 @@ describe('POST /api/sendEmail', () => { }); it('should send an email successfully and return a success response', async () => { - jest.doMock('resend', () => ({ - Resend: jest.fn(() => ({ - emails: { send: jest.fn().mockResolvedValue({ status: 'success' }) }, - })), - })); - const { POST } = await import('./route'); const req = { json: jest @@ -51,35 +47,26 @@ describe('POST /api/sendEmail', () => { expect(response.status).toBe(200); expect(jsonResponse.message).toBe('Email sent successfully!'); + expect(jsonResponse.response).toHaveProperty('messageId', 'test-id'); }); - describe('Error Handling', () => { - beforeEach(() => { - jest.resetModules(); - jest.clearAllMocks(); + it('should return a 500 error if sending email fails', async () => { + const nodemailer = await import('nodemailer'); + (nodemailer.createTransport as jest.Mock).mockReturnValueOnce({ + sendMail: jest.fn().mockRejectedValue(new Error('Test error message')), }); - it('should return a 500 error if sending email fails', async () => { - jest.doMock('resend', () => ({ - Resend: jest.fn(() => ({ - emails: { - send: jest.fn().mockRejectedValue(new Error('Test error message')), - }, - })), - })); - - const { POST } = await import('./route'); - const req = { - json: jest - .fn() - .mockResolvedValue({ to: 'test@example.com', message: 'Hello' }), - } as unknown as Request; + const { POST } = await import('./route'); + const req = { + json: jest + .fn() + .mockResolvedValue({ to: 'test@example.com', message: 'Hello' }), + } as unknown as Request; - const response = await POST(req); - const jsonResponse = await response.json(); + const response = await POST(req); + const jsonResponse = await response.json(); - expect(response.status).toBe(500); - expect(jsonResponse.error).toBe('Test error message'); - }); + expect(response.status).toBe(500); + expect(jsonResponse.error).toBe('Test error message'); }); }); diff --git a/veterans/app/api/sendEmail/route.ts b/veterans/app/api/sendEmail/route.ts index c21dc15d..e94de0c7 100644 --- a/veterans/app/api/sendEmail/route.ts +++ b/veterans/app/api/sendEmail/route.ts @@ -1,7 +1,5 @@ import { NextResponse } from 'next/server'; -import { Resend } from 'resend'; - -const resend = new Resend(process.env.RESEND_API_KEY); +import nodemailer from 'nodemailer'; export async function POST(req: Request) { const { to, message } = await req.json(); @@ -14,13 +12,23 @@ export async function POST(req: Request) { } try { - const response = await resend.emails.send({ - from: 'RAZOM ', - to: [to], - subject: 'Your Verification Code', - text: message, + const transporter = nodemailer.createTransport({ + service: 'gmail', + auth: { + user: process.env.GMAIL_USER, + pass: process.env.GMAIL_PASSWORD, + }, }); + const mailOptions = { + from: process.env.GMAIL_USER, + to, + subject: 'Verify Your Email Address', + text: message, + }; + + const response = await transporter.sendMail(mailOptions); + return NextResponse.json( { message: 'Email sent successfully!', response }, { status: 200 }, diff --git a/veterans/helpers/handleSendEmail.ts b/veterans/helpers/handleSendEmail.ts index f566b0f0..dc433910 100644 --- a/veterans/helpers/handleSendEmail.ts +++ b/veterans/helpers/handleSendEmail.ts @@ -14,6 +14,8 @@ export const handleSendEmail = async ( body: JSON.stringify({ to: email, message }), }); + await response.json(); + if (response.ok) { // eslint-disable-next-line no-alert alert('Verification email sent! Check your inbox.'); diff --git a/veterans/package-lock.json b/veterans/package-lock.json index 999a9683..a4037925 100644 --- a/veterans/package-lock.json +++ b/veterans/package-lock.json @@ -23,12 +23,12 @@ "https-browserify": "^1.0.0", "next": "^14.2.18", "next-auth": "^4.24.11", + "nodemailer": "^6.10.0", "os-browserify": "^0.3.0", "process": "^0.11.10", "react-error-boundary": "^5.0.0", "react-hook-form": "^7.54.2", "react-spinners": "^0.13.8", - "resend": "^4.2.0", "stream-browserify": "^3.0.0", "stream-http": "^3.2.0", "url": "^0.11.3", @@ -47,6 +47,7 @@ "@types/cors": "^2.8.17", "@types/jest": "^29.5.13", "@types/node": "^20.16.14", + "@types/nodemailer": "^6.4.17", "@typescript-eslint/eslint-plugin": "^8.12.2", "@typescript-eslint/parser": "^8.12.2", "dotenv-cli": "^7.4.1", @@ -6045,39 +6046,6 @@ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", "license": "BSD-3-Clause" }, - "node_modules/@react-email/render": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.0.5.tgz", - "integrity": "sha512-CA69HYXPk21HhtAXATIr+9JJwpDNmAFCvdMUjWmeoD1+KhJ9NAxusMRxKNeibdZdslmq3edaeOKGbdQ9qjK8LQ==", - "license": "MIT", - "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/render/node_modules/prettier": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", - "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, "node_modules/@rollup/pluginutils": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", @@ -6098,19 +6066,6 @@ "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", "license": "MIT" }, - "node_modules/@selderee/plugin-htmlparser2": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz", - "integrity": "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==", - "license": "MIT", - "dependencies": { - "domhandler": "^5.0.3", - "selderee": "^0.11.0" - }, - "funding": { - "url": "https://ko-fi.com/killymxi" - } - }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -7433,6 +7388,16 @@ "form-data": "^4.0.0" } }, + "node_modules/@types/nodemailer": { + "version": "6.4.17", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz", + "integrity": "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", @@ -10214,6 +10179,7 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -10406,32 +10372,6 @@ "csstype": "^3.0.2" } }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, "node_modules/domexception": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", @@ -10446,35 +10386,6 @@ "node": ">=12" } }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, "node_modules/dot-prop": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", @@ -10705,6 +10616,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -12705,41 +12617,6 @@ "dev": true, "license": "MIT" }, - "node_modules/html-to-text": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz", - "integrity": "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==", - "license": "MIT", - "dependencies": { - "@selderee/plugin-htmlparser2": "^0.11.0", - "deepmerge": "^4.3.1", - "dom-serializer": "^2.0.0", - "htmlparser2": "^8.0.2", - "selderee": "^0.11.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/htmlparser2": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" - } - }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -14292,15 +14169,6 @@ "node": ">=6" } }, - "node_modules/leac": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/leac/-/leac-0.6.0.tgz", - "integrity": "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==", - "license": "MIT", - "funding": { - "url": "https://ko-fi.com/killymxi" - } - }, "node_modules/less": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/less/-/less-4.2.2.tgz", @@ -15455,6 +15323,15 @@ "dev": true, "license": "MIT" }, + "node_modules/nodemailer": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.0.tgz", + "integrity": "sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -15933,19 +15810,6 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/parseley": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz", - "integrity": "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==", - "license": "MIT", - "dependencies": { - "leac": "^0.6.0", - "peberminta": "^0.9.0" - }, - "funding": { - "url": "https://ko-fi.com/killymxi" - } - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -16087,15 +15951,6 @@ "node": ">=0.12" } }, - "node_modules/peberminta": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz", - "integrity": "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==", - "license": "MIT", - "funding": { - "url": "https://ko-fi.com/killymxi" - } - }, "node_modules/peek-readable": { "version": "5.4.2", "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.4.2.tgz", @@ -17009,21 +16864,6 @@ "react-dom": "^16.8.0 || ^17 || ^18" } }, - "node_modules/react-promise-suspense": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/react-promise-suspense/-/react-promise-suspense-0.3.4.tgz", - "integrity": "sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^2.0.1" - } - }, - "node_modules/react-promise-suspense/node_modules/fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==", - "license": "MIT" - }, "node_modules/react-remove-scroll": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.3.tgz", @@ -17463,18 +17303,6 @@ "dev": true, "license": "MIT" }, - "node_modules/resend": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/resend/-/resend-4.2.0.tgz", - "integrity": "sha512-s6ogU+BBYH1H6Zl926cpddtLRAJWeFv5cIKcuHLWk1QYhFTbpFJlhqx31pnN2f0CB075KFSrc1Xf6HG690wzuw==", - "license": "MIT", - "dependencies": { - "@react-email/render": "1.0.5" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/reserved-words": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/reserved-words/-/reserved-words-0.1.2.tgz", @@ -17716,18 +17544,6 @@ "compute-scroll-into-view": "^3.0.2" } }, - "node_modules/selderee": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz", - "integrity": "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==", - "license": "MIT", - "dependencies": { - "parseley": "^0.12.0" - }, - "funding": { - "url": "https://ko-fi.com/killymxi" - } - }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", diff --git a/veterans/package.json b/veterans/package.json index bbe57dfe..06120be4 100644 --- a/veterans/package.json +++ b/veterans/package.json @@ -37,12 +37,12 @@ "https-browserify": "^1.0.0", "next": "^14.2.18", "next-auth": "^4.24.11", + "nodemailer": "^6.10.0", "os-browserify": "^0.3.0", "process": "^0.11.10", "react-error-boundary": "^5.0.0", "react-hook-form": "^7.54.2", "react-spinners": "^0.13.8", - "resend": "^4.2.0", "stream-browserify": "^3.0.0", "stream-http": "^3.2.0", "url": "^0.11.3", @@ -61,6 +61,7 @@ "@types/cors": "^2.8.17", "@types/jest": "^29.5.13", "@types/node": "^20.16.14", + "@types/nodemailer": "^6.4.17", "@typescript-eslint/eslint-plugin": "^8.12.2", "@typescript-eslint/parser": "^8.12.2", "dotenv-cli": "^7.4.1", From 805fe256e94dafabb728589badd370bb6bc3b8ce Mon Sep 17 00:00:00 2001 From: IhorMasechko Date: Tue, 8 Apr 2025 14:44:02 +0300 Subject: [PATCH 02/18] Added mutation for the isVerified field --- veterans/app/api/sendEmail/route.ts | 2 + .../EmailVerification/EmailVerification.tsx | 39 +++++++++++++++++-- .../common/authForm/RegisterForm.tsx | 7 +++- veterans/constants/graphql.ts | 2 +- veterans/helpers/handleSendEmail.ts | 2 - veterans/keystone/models/User.ts | 8 +--- 6 files changed, 45 insertions(+), 15 deletions(-) diff --git a/veterans/app/api/sendEmail/route.ts b/veterans/app/api/sendEmail/route.ts index e94de0c7..5294338d 100644 --- a/veterans/app/api/sendEmail/route.ts +++ b/veterans/app/api/sendEmail/route.ts @@ -14,6 +14,8 @@ export async function POST(req: Request) { try { const transporter = nodemailer.createTransport({ service: 'gmail', + port: 465, + secure: true, auth: { user: process.env.GMAIL_USER, pass: process.env.GMAIL_PASSWORD, diff --git a/veterans/components/common/authForm/EmailVerification/EmailVerification.tsx b/veterans/components/common/authForm/EmailVerification/EmailVerification.tsx index 7662dc02..a125577e 100644 --- a/veterans/components/common/authForm/EmailVerification/EmailVerification.tsx +++ b/veterans/components/common/authForm/EmailVerification/EmailVerification.tsx @@ -1,23 +1,54 @@ import React, { useState } from 'react'; +import { useMutation, useQuery } from '@apollo/client'; import { useRouter } from 'next/navigation'; import st from '@comComps/authForm/AuthForm.module.css'; +import { VERIFICATION_MUTATION, CHECK_USER_QUERY } from 'constants/graphql'; interface EmailVerificationProps { readonly verificationCode: string; + readonly email: string; } export default function EmailVerification({ verificationCode, + email, }: EmailVerificationProps) { const [confirmedCode, setConfirmedCode] = useState(''); + const [updateUserVerification] = useMutation(VERIFICATION_MUTATION); const router = useRouter(); + const { data: userData } = useQuery(CHECK_USER_QUERY, { + variables: { email }, + }); - const handleVerifyCode = () => { - if (confirmedCode === verificationCode) { - router.push('/login'); - } else { + const handleVerifyCode = async () => { + if (confirmedCode !== verificationCode) { // eslint-disable-next-line no-alert alert('Invalid verification code'); + return; + } + + const userId = userData.user.id; + + try { + const { data } = await updateUserVerification({ + variables: { + id: userId, + isVerified: true, + }, + }); + + if (data?.updateUser?.isVerified) { + router.push('/'); + } else { + // eslint-disable-next-line no-alert + alert('Verification failed'); + } + } catch (error: unknown) { + let message = 'Something went wrong during verification'; + + if (error instanceof Error) { + message = error.message; + } } }; diff --git a/veterans/components/common/authForm/RegisterForm.tsx b/veterans/components/common/authForm/RegisterForm.tsx index 185bab3e..09aa741f 100644 --- a/veterans/components/common/authForm/RegisterForm.tsx +++ b/veterans/components/common/authForm/RegisterForm.tsx @@ -37,6 +37,7 @@ export default function RegisterForm() { const [submitError, setSubmitError] = useState(null); const stepRef = useRef<'register' | 'verify'>('register'); const verificationCodeRef = useRef(''); + const emailRef = useRef(''); const { control, @@ -75,6 +76,7 @@ export default function RegisterForm() { const { data: responseData } = await register({ variables: { ...data } }); if (responseData?.createUser) { + emailRef.current = data.email; const { success, code } = await handleSendEmail(data.email); if (success && code) { verificationCodeRef.current = code; @@ -200,7 +202,10 @@ export default function RegisterForm() { ) : ( - + )} ); diff --git a/veterans/constants/graphql.ts b/veterans/constants/graphql.ts index 91c57bbb..96df829b 100644 --- a/veterans/constants/graphql.ts +++ b/veterans/constants/graphql.ts @@ -80,7 +80,7 @@ export const CHECK_USER_QUERY = gql` } `; -export const UPDATE_USER_VERIFICATION = gql` +export const VERIFICATION_MUTATION = gql` mutation UpdateUserVerification($id: ID!, $isVerified: Boolean!) { updateUser(where: { id: $id }, data: { isVerified: $isVerified }) { id diff --git a/veterans/helpers/handleSendEmail.ts b/veterans/helpers/handleSendEmail.ts index dc433910..f566b0f0 100644 --- a/veterans/helpers/handleSendEmail.ts +++ b/veterans/helpers/handleSendEmail.ts @@ -14,8 +14,6 @@ export const handleSendEmail = async ( body: JSON.stringify({ to: email, message }), }); - await response.json(); - if (response.ok) { // eslint-disable-next-line no-alert alert('Verification email sent! Check your inbox.'); diff --git a/veterans/keystone/models/User.ts b/veterans/keystone/models/User.ts index d6eeb5f5..7af3b444 100644 --- a/veterans/keystone/models/User.ts +++ b/veterans/keystone/models/User.ts @@ -189,13 +189,7 @@ export const User: ListConfig> = list({ defaultValue: false, access: { read: allowAll, - update: ({ - session, - item, - }: { - session?: Session; - item: CustomBaseItem; - }) => isSameUser({ session, item }), + update: allowAll, }, ui: { createView: { fieldMode: 'hidden' }, From 2f6e49a20076dfb3c690a3002151b537a47ff50d Mon Sep 17 00:00:00 2001 From: IhorMasechko Date: Wed, 9 Apr 2025 16:17:28 +0300 Subject: [PATCH 03/18] Added a toaster and the option to log in during registration --- veterans/app/layout.tsx | 2 + .../EmailVerification/EmailVerification.tsx | 8 +- .../components/common/authForm/LoginForm.tsx | 193 +++++++++++------- .../common/authForm/RegisterForm.tsx | 34 ++- veterans/constants/graphql.ts | 1 + veterans/helpers/handleSendEmail.ts | 4 +- veterans/keystone/models/User.ts | 8 +- veterans/package-lock.json | 27 +++ veterans/package.json | 1 + 9 files changed, 190 insertions(+), 88 deletions(-) diff --git a/veterans/app/layout.tsx b/veterans/app/layout.tsx index 60f370c2..fecb8958 100644 --- a/veterans/app/layout.tsx +++ b/veterans/app/layout.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { Toaster } from 'react-hot-toast'; import { Lato, Golos_Text } from 'next/font/google'; import 'styles/global.css'; @@ -51,6 +52,7 @@ export default function RootLayout({ +
{children}