diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 7a73a41b..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,2 +0,0 @@ -{ -} \ No newline at end of file diff --git a/caeq-backend/controllers/architect.user.controller.js b/caeq-backend/controllers/architect.user.controller.js index 0971d03c..944b7486 100644 --- a/caeq-backend/controllers/architect.user.controller.js +++ b/caeq-backend/controllers/architect.user.controller.js @@ -1,8 +1,16 @@ const factory = require('./handlerFactory.controller'); const ArchitectUser = require('../models/architect.user.model'); +const RegisterRequest = require('../models/regiesterRequests.model'); const APIFeatures = require(`../utils/apiFeatures`); +const catchAsync = require('../utils/catchAsync'); +const AppError = require('../utils/appError'); +const Email = require('../utils/email'); exports.getAllArchitectUsers = factory.getAll(ArchitectUser, 'specialties'); +exports.getAllRegistrationRequests = factory.getAll(RegisterRequest, [ + 'newInfo', + 'overwrites', +]); exports.getArchitectUser = factory.getOne(ArchitectUser, 'specialties'); exports.createArchitectUser = factory.createOne(ArchitectUser); exports.updateArchitectUser = factory.updateOne(ArchitectUser); @@ -11,7 +19,7 @@ exports.deleteArchitectUser = factory.deleteOne(ArchitectUser); /** * This is a function that gets all architects with authorizationToShareInfo set to true. */ -exports.getAllPublicArchitectUsers = async (req, res) => { +exports.getAllPublicArchitectUsers = catchAsync(async (req, res) => { let query = ArchitectUser.find({ authorizationToShareInfo: true, }) @@ -30,4 +38,108 @@ exports.getAllPublicArchitectUsers = async (req, res) => { results: documents.length, data: { documents }, }); -}; +}); + +/** + * Accepts an architect user's request to become a member. + * + * @function + * @async + * @param {Object} req - The request object. + * @param {Object} res - The response object. + * @param {function} next - The next middleware function. + * @returns {Promise} A Promise that resolves when the operation is complete. + * @throws {AppError} If the request cannot be processed. + * + * @example + * // Example usage: + * acceptArchitectUser(req, res, next); + */ +exports.acceptArchitectUser = catchAsync(async (req, res, next) => { + const registrationRequestId = req.params.id; + + const registerRequest = await RegisterRequest.findById( + registrationRequestId + ).populate({ + path: 'newInfo', + select: '-_id -email -isLegacy -overwritten -collegiateNumber -isRequest +password', + }); + + if (!registerRequest) { + return next(new AppError('La petición de registro ya no existe.', 400)); + } + + const newArchitectInfo = registerRequest.newInfo; + + await ArchitectUser.findByIdAndUpdate(registerRequest.overwrites, { + email: newArchitectInfo.newEmail, + isLegacy: true, + overwritten: true, + collegiateNumber: registerRequest.architectNumber, + isRequest: false, + ...newArchitectInfo._doc, + }); + + const registerRequestNewInfo = await RegisterRequest.findById(registrationRequestId); + await ArchitectUser.findByIdAndDelete(registerRequestNewInfo.newInfo); + + await RegisterRequest.findByIdAndDelete(registrationRequestId); + + // Uncomment after emails are payed + try { + newArchitectInfo.email = newArchitectInfo.newEmail; + await new Email(newArchitectInfo).sendArchitectAccepted(); + } catch (error) { + // Production logging + console.log(error); + } + + res.status(200).json({ + status: 'success', + message: 'Arquitecto verificado. El usuario ahora cuenta con acceso al portal.', + }); +}); + +/** + * Rejects a Architect user's request to become a member and deletes the request. + * + * @function + * @async + * @param {Object} req - The request object. + * @param {Object} res - The response object. + * @param {function} next - The next middleware function. + * @returns {Promise} A Promise that resolves when the operation is complete. + * + * @example + * // Example usage: + * rejectArchitectUser(req, res, next); + */ +exports.rejectArchitectUser = catchAsync(async (req, res, next) => { + const registrationRequestId = req.params.id; + + const registerRequest = await RegisterRequest.findById( + registrationRequestId + ).populate('newInfo'); + + if (!registerRequest) { + return next(new AppError('La petición de registro ya no existe.', 400)); + } + + await ArchitectUser.findByIdAndDelete(registerRequest.newInfo._id); + + await RegisterRequest.findByIdAndDelete(registrationRequestId); + + // Uncomment after emails are payed + try { + registerRequest.newInfo.email = registerRequest.newInfo.newEmail; + await new Email(registerRequest.newInfo).sendArchitectRejected(); + } catch (error) { + // Production logging + console.log(error); + } + + res.status(200).json({ + status: 'success', + message: 'Solicitud eliminada. El usuario no fue aceptado en el portal.', + }); +}); diff --git a/caeq-backend/controllers/auth.controller.js b/caeq-backend/controllers/auth.controller.js index 58da9112..79789e17 100644 --- a/caeq-backend/controllers/auth.controller.js +++ b/caeq-backend/controllers/auth.controller.js @@ -1,5 +1,6 @@ const CaeqUser = require('../models/caeq.user.model'); const ArchitectUser = require('../models/architect.user.model'); +const RegisterRequest = require('../models/regiesterRequests.model'); const jwt = require('jsonwebtoken'); const catchAsync = require('./../utils/catchAsync'); const AppError = require('./../utils/appError'); @@ -69,13 +70,12 @@ exports.signUpCaeqUser = catchAsync(async (req, res, next) => { }); // Uncomment after emails after payed - // try { - // await new Email(newUser).sendWelcomeAdmin(); - // } catch (error) { - // return next( - // new AppError('Hemos tenido problemas enviando un correo de bienvenida.', 500) - // ); - // } + try { + await new Email(newUser).sendWelcomeAdmin(); + } catch (error) { + // Prod logging + console.log(error); + } // After signup a verified admin must approve the new admin res.status(200).json({ @@ -85,6 +85,50 @@ exports.signUpCaeqUser = catchAsync(async (req, res, next) => { }); }); +/** + * Asynchronously creates a registration request for an architect. + * + * @param {Object} req - The request object containing the registration information. + * @param {Object} existingUser - The existing user object, if any. + * @param {Object} res - The response object used to send the registration status. + * @returns {Promise} - A Promise that resolves when the registration process is complete. + */ +async function createRegistrationRequest(req, existingUser, res) { + const email = req.body.email; + delete req.body.email; + const updatedArchitect = await ArchitectUser.create({ + ...req.body, + email: `${Date.now()}${email}`, + newEmail: email, + isRequest: true, + }); + + await RegisterRequest.create({ + overwrites: existingUser, + newInfo: updatedArchitect, + architectNumber: updatedArchitect.collegiateNumber, + }); + + updatedArchitect.email = email; + // Send welcome email + try { + await new Email( + updatedArchitect, + process.env.LANDING_URL + ).sendWelcomeUserRegistrationRequested(); + } catch (error) { + // Prod logging + console.log(error); + } + + res.status(200).json({ + status: 'success', + message: `Te has registrado con éxito, espera a que un administrador verifique que eres el arquitecto con el número de colegiado ${updatedArchitect.collegiateNumber} y te de acceso al portal.`, + }); + + return; +} + /** * Creates a new architect user. * @@ -94,88 +138,51 @@ exports.signUpCaeqUser = catchAsync(async (req, res, next) => { */ exports.signUpArchitectUser = catchAsync(async (req, res, next) => { const { collegiateNumber } = req.body; - let newUser; + const password = req.body.password; + const passwordConfirm = req.body.passwordConfirm; + + if (password !== passwordConfirm) { + return next(new AppError('Tus contraseñas deben coincidir.', 400)); + } // Check if user already exists - const existingUser = await ArchitectUser.findOne({ collegiateNumber }); + const existingUser = await ArchitectUser.findOne({ + collegiateNumber, + }); if (existingUser) { - if (existingUser.isLegacy === true && existingUser.isOverwritten === false) { - const password = req.body.password; - const passwordConfirm = req.body.passwordConfirm; - - if (password !== passwordConfirm) { - return next(new AppError('Tus contraseñas deben coincidir.')); - } - - delete req.body.password; - delete req.body.passwordConfirm; - - // Update existing user - newUser = await ArchitectUser.findOneAndUpdate( - { _id: existingUser._id }, - { $set: req.body }, - { - new: true, - runValidators: true, - useFindAndModify: true, - } - ); - - // Update password - newUser = await ArchitectUser.findOneAndUpdate( - { _id: existingUser._id }, - { - $set: { - password: await bcrypt.hash(password, 12), - isOverwritten: true, - }, - }, - { - new: true, - runValidators: false, - useFindAndModify: true, - } - ); - } else if ( - existingUser.isLegacy === true && - existingUser.isOverwritten === true - ) { - return next( - new AppError( - 'Una persona ya se ha inscrito en el portal con estos datos. Crea una nueva cuenta o si crees que es un error contacta a gerencia.' - ) - ); + if (existingUser.isLegacy === true) { + return await createRegistrationRequest(req, existingUser, res); } else if ( existingUser.isLegacy === false && existingUser.isOverwritten === true ) { return next( new AppError( - 'El colegiado que intentas sobreescribir se inscribió recientemente y no forma parte del viejo sistema.' + 'El colegiado que intentas sobreescribir se inscribió recientemente y no forma parte del viejo sistema.', + 400 ) ); } else { return next( new AppError( - 'Algo salió muy mal.No hemos podido sobreescribir los datos.' + 'Algo salió muy mal.No hemos podido sobreescribir los datos.', + 400 ) ); } - } else { - // Create new user - newUser = await ArchitectUser.create(req.body); } - // Uncomment after emails after payed - // // Send welcome email - // try { - // await new Email(newUser, process.env.LANDING_URL).sendWelcomeUser(); - // } catch (error) { - // return next( - // new AppError('Hemos tenido problemas enviando un correo de bienvenida.', 500) - // ); - // } + let newUser; + newUser = await ArchitectUser.create(req.body); + + // Send welcome email + try { + await new Email(newUser, process.env.LANDING_URL).sendWelcomeUser(); + } catch (error) { + // Prod logging + console.log(error); + } // Send JWT token return createSendToken(newUser, 'architect', 201, req, res); @@ -218,13 +225,16 @@ exports.loginArchitectUser = catchAsync(async (req, res, next) => { if (!user) { return next( new AppError( - 'Email incorrecto. No hay un usuario registrado con este correo.', + 'Email incorrecto. No hay un usuario registrado con este correo. Si se registró recientemente, por favor espere a que un administrador verifique su perfil.', 401 ) ); } else if (!(await user.correctPassword(password, user.password))) { return next( - new AppError('Contraseña incorrecta. Intente de nuevo por favor.', 401) + new AppError( + 'Contraseña incorrecta. Intente de nuevo por favor. Si te registraste recientemente, por favor espere a que un administrador verifique su perfil.', + 401 + ) ); } diff --git a/caeq-backend/models/architect.user.model.js b/caeq-backend/models/architect.user.model.js index 274f4b0c..9f6ac737 100644 --- a/caeq-backend/models/architect.user.model.js +++ b/caeq-backend/models/architect.user.model.js @@ -6,7 +6,6 @@ const crypto = require('crypto'); // UPDATE TEST DATA AFTER UPDATING ARCHITECT MODEL const ArchitectUserSchema = new mongoose.Schema({ collegiateNumber: { - unique: true, type: Number, required: [true, 'Por favor dinos tu número de colegiado!'], }, @@ -167,6 +166,9 @@ const ArchitectUserSchema = new mongoose.Schema({ trim: true, validate: [validator.isEmail, 'Necesitas un correo válido.'], }, + newEmail: { + type: String, + }, password: { type: String, required: [true, 'Por favor provee una contraseña.'], @@ -196,6 +198,10 @@ const ArchitectUserSchema = new mongoose.Schema({ type: Boolean, default: true, }, + isRequest: { + type: Boolean, + default: false, + }, }); // Indexing admin properties for optimized search diff --git a/caeq-backend/models/data/architect.user.js b/caeq-backend/models/data/architect.user.js index 60ed652f..52f16976 100644 --- a/caeq-backend/models/data/architect.user.js +++ b/caeq-backend/models/data/architect.user.js @@ -128,7 +128,7 @@ const architectUserTestData = [ passwordConfirm: 'passwordabc', }, { - collegiateNumber: 24680, + collegiateNumber: 7838, fullName: 'Javier López', memberType: 'Miembro Adherente', classification: 'Docente', @@ -155,12 +155,15 @@ const architectUserTestData = [ positionsInCouncil: 'Secretario', capacitationHours: 80, annuity: false, - email: 'javier@example.com', + email: '97et9et7e90rt7javier@example.com', + newEmail: 'javier@example.com', password: 'password123', passwordConfirm: 'password123', + isLegacy: true, + isOverwritten: false, }, { - collegiateNumber: 13579, + collegiateNumber: 1329, fullName: 'Isabel Torres', memberType: 'Miembro Pasante', classification: 'Convenio', @@ -187,7 +190,8 @@ const architectUserTestData = [ positionsInCouncil: 'Vocal', capacitationHours: 95, annuity: true, - email: 'pablocesarjimenezvilleda@gmail.com', + email: '2654874682754723isabel@example.com', + newEmail: 'isabel@example.com', password: 'password456', passwordConfirm: 'password456', }, diff --git a/caeq-backend/models/regiesterRequests.model.js b/caeq-backend/models/regiesterRequests.model.js new file mode 100644 index 00000000..6fdfa8b4 --- /dev/null +++ b/caeq-backend/models/regiesterRequests.model.js @@ -0,0 +1,31 @@ +const mongoose = require('mongoose'); + +const registerRequest = new mongoose.Schema( + { + overwrites: { + type: mongoose.Schema.ObjectId, + ref: 'architect.user', + required: [true, 'Se necesita un colegiado al que sobreescribir.'], + }, + newInfo: { + type: mongoose.Schema.ObjectId, + ref: 'architect.user', + required: [ + true, + 'Se necesita nueva información para sobreescribir a un colegiado.', + ], + }, + architectNumber: { + type: Number, + required: [ + true, + 'Una petición de registro necesita de un número de colegiado.', + ], + }, + }, + { timestamps: true } +); + +const RegisterRequest = mongoose.model('RegisterRequest', registerRequest); + +module.exports = RegisterRequest; diff --git a/caeq-backend/models/testdata.setup.js b/caeq-backend/models/testdata.setup.js index d6ba364e..5625d04c 100644 --- a/caeq-backend/models/testdata.setup.js +++ b/caeq-backend/models/testdata.setup.js @@ -17,6 +17,7 @@ const GatheringData = require('./data/gathering.js'); const Inscription = require('./inscription.model'); const Services = require('./roomOffer.model.js'); const ServicesData = require('./data/services.js'); +const RegisterRequests = require('./regiesterRequests.model'); /** * Set up 'CaeqUser' data by populating the database with the provided test data. @@ -77,7 +78,7 @@ const setUpSessionData = catchAsync(async () => { const setUpAttendeesData = catchAsync(async () => { const gatherings = await Gathering.find(); - const architect = await ArchitectUser.find() + const architect = await ArchitectUser.find(); AttendeesData[0].idGathering = gatherings[0]._id; AttendeesData[1].idGathering = gatherings[1]._id; AttendeesData[2].idGathering = gatherings[2]._id; @@ -108,6 +109,36 @@ const setUpCourseData = catchAsync(async () => { await populateDb(Course, CourseData); }); +/** + * Set up 'RegisterRequest' data by populating the database with the provided test data. + * + * This function is wrapped in 'catchAsync' to handle any asynchronous errors that may occur during execution. + */ +const setUpregisterRequests = catchAsync(async () => { + const user1 = await ArchitectUser.findOne({ email: 'jcastr@tec.mx' }); + const user2 = await ArchitectUser.findOne({ + email: '97et9et7e90rt7javier@example.com', + }); + const user3 = await ArchitectUser.findOne({ + email: '2654874682754723isabel@example.com', + }); + + const registerRequestData = [ + { + overwrites: user1._id, + newInfo: user2._id, + architectNumber: 45672, + }, + { + overwrites: user1._id, + newInfo: user3._id, + architectNumber: 45672, + }, + ]; + + await populateDb(RegisterRequests, registerRequestData); +}); + /** * Set up 'Specialty' data by populating the database with the provided test data. * @@ -186,6 +217,7 @@ exports.setUpDbWithMuckData = catchAsync(async () => { await setUpAttendeesData(); await setUpInsciptionData(); await setUpServicesData(); + await setUpregisterRequests(); console.log('Test data uploaded to DB'); }); diff --git a/caeq-backend/routes/architect.user.route.js b/caeq-backend/routes/architect.user.route.js index bd1dc31b..37ccfb39 100644 --- a/caeq-backend/routes/architect.user.route.js +++ b/caeq-backend/routes/architect.user.route.js @@ -7,6 +7,9 @@ const { updateArchitectUser, deleteArchitectUser, getAllPublicArchitectUsers, + getAllRegistrationRequests, + acceptArchitectUser, + rejectArchitectUser, } = require(`${__dirname}/../controllers/architect.user.controller.js`); const { @@ -32,8 +35,15 @@ router.post( router.post('/auth/login', loginArchitectUser); router.post('/forgot-password', forgotPasswordArchitectUser); router.patch('/reset-password/:token', resetPasswordArchitectUser); -router.route('/').get(getAllArchitectUsers).post(createArchitectUser); +router.route('/accept-architect/:id').patch(acceptArchitectUser); +router.route('/reject-architect/:id').patch(rejectArchitectUser); + +router + .route('/registration-requests') + .get(protect, restrictTo('caeq'), getAllRegistrationRequests); + +router.route('/').get(getAllArchitectUsers).post(createArchitectUser); router .route('/:id') .get(protect, restrictTo('caeq', 'self'), getArchitectUser) diff --git a/caeq-backend/tests/architectUsers/architect.user.auth.test.js b/caeq-backend/tests/architectUsers/architect.user.auth.test.js index b5bfb27b..7847aac3 100644 --- a/caeq-backend/tests/architectUsers/architect.user.auth.test.js +++ b/caeq-backend/tests/architectUsers/architect.user.auth.test.js @@ -22,19 +22,20 @@ const testArchitectLogin = async () => { expect(resTest2.statusCode).toEqual(401); expect(resTest2.body.message).toEqual( - 'Contraseña incorrecta. Intente de nuevo por favor.' + 'Contraseña incorrecta. Intente de nuevo por favor. Si te registraste recientemente, por favor espere a que un administrador verifique su perfil.' ); }; const testArchitectSignUp = async () => { const password = 'password789'; - const resTest1 = await agent.post('/architectusers/auth/signup') + const resTest1 = await agent + .post('/architectusers/auth/signup') .type('multipart/form-data') .field('fullName', 'Pablo Jimenez') .field('email', 'cesar@example.com') .field('password', password) .field('passwordConfirm', password) - .field('collegiateNumber', 45672) + .field('collegiateNumber', 90801) .field('memberType', 'Miembro de número') .field('classification', 'Docente') .field('DRONumber', 'DRO98765') @@ -61,7 +62,6 @@ const testArchitectSignUp = async () => { expect(resTest1.statusCode).toEqual(201); expect(resTest1.body).toBeTruthy(); - expect(resTest1.body.data.user.email).toEqual('cesar@example.com'); const resLoginTest = await agent.post('/architectusers/auth/login').send({ email: 'cesar@example.com', @@ -70,9 +70,9 @@ const testArchitectSignUp = async () => { expect(resLoginTest.statusCode).toEqual(201); expect(resLoginTest.body).toBeTruthy(); - expect(resLoginTest.body.data.user.email).toEqual('cesar@example.com'); - const resTest2 = await agent.post('/architectusers/auth/signup') + const resTest2 = await agent + .post('/architectusers/auth/signup') .type('multipart/form-data') .field('fullName', 'Pablo Jimenez') .field('email', 'cesar@example.com') @@ -103,18 +103,18 @@ const testArchitectSignUp = async () => { .field('positionsInCouncil', 'Vocal') .field('capacitationHours', 90); - expect(resTest2.statusCode).toEqual(500); + expect(resTest2.statusCode).toEqual(200); expect(resTest2.body.message).toEqual( - 'Una persona ya se ha inscrito en el portal con estos datos. ' + - 'Crea una nueva cuenta o si crees que es un error contacta a gerencia.' + 'Te has registrado con éxito, espera a que un administrador verifique que eres el arquitecto con el número de colegiado 45672 y te de acceso al portal.' ); - const resTest3 = await agent.post('/architectusers/auth/signup') + const resTest3 = await agent + .post('/architectusers/auth/signup') .type('multipart/form-data') .field('fullName', 'Pablo Jimenez') .field('email', 'cesar2@example.com') .field('password', password) - .field('passwordConfirm', password+'wrong') + .field('passwordConfirm', password + 'wrong') .field('collegiateNumber', 456723) .field('memberType', 'Miembro de número') .field('classification', 'Docente') @@ -141,14 +141,13 @@ const testArchitectSignUp = async () => { .field('capacitationHours', 90); expect(resTest3.statusCode).toEqual(400); - expect(resTest3.body.message).toEqual( - 'Datos inválidos: Por favor ingresa la misma contraseña.' - ); + expect(resTest3.body.message).toEqual('Tus contraseñas deben coincidir.'); }; const testArchitectSignUpNew = async () => { const password = 'password789'; - const resTest1 = await agent.post('/architectusers/auth/signup') + const resTest1 = await agent + .post('/architectusers/auth/signup') .type('multipart/form-data') .field('fullName', 'Pablo Jimenez') .field('email', 'sesar@example.com') @@ -193,6 +192,58 @@ const testArchitectSignUpNew = async () => { expect(resLoginTest.body.data.user.email).toEqual('sesar@example.com'); }; +const testRegistrationCreation = async () => { + const password = 'password789'; + const resTest1 = await agent + .post('/architectusers/auth/signup') + .type('multipart/form-data') + .field('fullName', 'Pablo Jimenez') + .field('email', 'pablito@example.com') + .field('password', password) + .field('passwordConfirm', password) + .field('collegiateNumber', 45672) + .field('memberType', 'Miembro de número') + .field('classification', 'Docente') + .field('DRONumber', 'DRO98765') + .field('authorizationToShareInfo', true) + .field('lifeInsurance', false) + .field('lifeInsureID', '9937557b') + .field('age', 40) + .field('gender', 'Hombre') + .field('cellphone', 5551112222) + .field('homePhone', 5553334444) + .field('officePhone', 5555556666) + .field('emergencyContact', 'Ana García 5557778888') + .field('mainProfessionalActivity', 'Ingeniero Civil') + .field('dateOfAdmission', 2002) + .field('dateOfBirth', new Date('1983-07-20').toISOString()) + .field('municipalityOfLabor', 'Querétaro') + .field('university', 'Universidad Autónoma de Querétaro') + .field('professionalLicense', 'P98765') + .field('workAddress', '123 Avenida Principal, Querétaro') + .field('homeAddress', '456 Calle Secundaria, Querétaro') + .field('specialty', 'Corresponsable en seguridad estructural') + .field('positionsInCouncil', 'Vocal') + .field('capacitationHours', 90); + + expect(resTest1.statusCode).toEqual(200); + expect(resTest1.body).toBeTruthy(); + expect(resTest1.body.message).toEqual( + 'Te has registrado con éxito, espera a que un administrador verifique que eres el arquitecto con el número de colegiado 45672 y te de acceso al portal.' + ); + + const resLoginTest = await agent.post('/architectusers/auth/login').send({ + email: 'pablito@example.com', + password: password, + }); + + expect(resLoginTest.statusCode).toEqual(401); + expect(resLoginTest.body).toBeTruthy(); + expect(resLoginTest.body.message).toEqual( + 'Email incorrecto. No hay un usuario registrado con este correo. Si se registró recientemente, por favor espere a que un administrador verifique su perfil.' + ); +}; + beforeAll(async () => { await connectDB(); await setUpDbWithMuckData(); @@ -202,4 +253,5 @@ describe('Architect login successful', () => { test('successful', () => testArchitectLogin()); test('successful', () => testArchitectSignUp()); test('successful', () => testArchitectSignUpNew()); + test('successful', () => testRegistrationCreation()); }); diff --git a/caeq-backend/tests/architectUsers/architect.user.register.test.js b/caeq-backend/tests/architectUsers/architect.user.register.test.js new file mode 100644 index 00000000..3fbab330 --- /dev/null +++ b/caeq-backend/tests/architectUsers/architect.user.register.test.js @@ -0,0 +1,170 @@ +const request = require('supertest'); +const app = require('../../app'); +const RegistrationRequest = require('../../models/regiesterRequests.model'); +const { connectDB } = require('../config/databaseTest'); +const { setUpDbWithMuckData } = require('../../models/testdata.setup'); +const { loginAdmin } = require('../config/authSetUp'); + +const agent = request.agent(app); +let registrationRequests; + +const testAcceptNewUser = async () => { + const endpoint = `/architectusers/accept-architect/${registrationRequests[0]._id}`; + const res = await agent.patch(endpoint).send(); + + expect(res.statusCode).toEqual(200); + expect(res.body.message).toEqual( + 'Arquitecto verificado. El usuario ahora cuenta con acceso al portal.' + ); + + const res2 = await agent.patch(endpoint).send(); + + expect(res2.statusCode).toEqual(400); + expect(res2.body.message).toEqual('La petición de registro ya no existe.'); +}; + +const testRejectNewUser = async () => { + const endpoint = `/architectusers/reject-architect/${registrationRequests[1]._id}`; + const res = await agent.patch(endpoint).send(); + + expect(res.statusCode).toEqual(200); + expect(res.body.message).toEqual( + 'Solicitud eliminada. El usuario no fue aceptado en el portal.' + ); + + const res2 = await agent.patch(endpoint).send(); + + expect(res2.statusCode).toEqual(400); + expect(res2.body.message).toEqual('La petición de registro ya no existe.'); +}; + +const testPendingProfileNoLogin = async () => { + const password = 'password789'; + const resTest1 = await agent + .post('/architectusers/auth/signup') + .type('multipart/form-data') + .field('fullName', 'Jorge Castro') + .field('email', 'pablin@tec.mx') + .field('password', password) + .field('passwordConfirm', password) + .field('collegiateNumber', 45672) + .field('memberType', 'Miembro de número') + .field('classification', 'Docente') + .field('DRONumber', 'DRO98765') + .field('authorizationToShareInfo', true) + .field('lifeInsurance', false) + .field('lifeInsureID', '9937557b') + .field('age', 40) + .field('gender', 'Hombre') + .field('cellphone', 5551112222) + .field('homePhone', 5553334444) + .field('officePhone', 5555556666) + .field('emergencyContact', 'Ana García 5557778888') + .field('mainProfessionalActivity', 'Ingeniero Civil') + .field('dateOfAdmission', 2002) + .field('dateOfBirth', new Date('1983-07-20').toISOString()) + .field('municipalityOfLabor', 'Querétaro') + .field('linkCV', 'https://example.com/luisgarcia-cv') + .field('university', 'Universidad Autónoma de Querétaro') + .field('professionalLicense', 'P98765') + .field('workAddress', '123 Avenida Principal, Querétaro') + .field('homeAddress', '456 Calle Secundaria, Querétaro') + .field('specialty', 'Corresponsable en seguridad estructural') + .field('positionsInCouncil', 'Vocal') + .field('capacitationHours', 90); + + expect(resTest1.statusCode).toEqual(200); + expect(resTest1.body).toBeTruthy(); + expect(resTest1.body.message).toEqual( + 'Te has registrado con éxito, espera a que un administrador verifique que eres el arquitecto con el número de colegiado 45672 y te de acceso al portal.' + ); + + const resLoginTest = await agent.post('/architectusers/auth/login').send({ + email: 'pablito@example.com', + password: password, + }); + + expect(resLoginTest.statusCode).toEqual(401); + expect(resLoginTest.body).toBeTruthy(); + expect(resLoginTest.body.message).toEqual( + 'Email incorrecto. No hay un usuario registrado con este correo. Si se registró recientemente, por favor espere a que un administrador verifique su perfil.' + ); +}; + +const testAcceptedProfileLogin = async () => { + const password = 'password789'; + const resTest1 = await agent + .post('/architectusers/auth/signup') + .type('multipart/form-data') + .field('fullName', 'Pablo Jimenez') + .field('email', 'correoaceptado@example.com') + .field('password', password) + .field('passwordConfirm', password) + .field('collegiateNumber', 45672) + .field('memberType', 'Miembro de número') + .field('classification', 'Docente') + .field('DRONumber', 'DRO98765') + .field('authorizationToShareInfo', true) + .field('lifeInsurance', false) + .field('lifeInsureID', '9937557b') + .field('age', 40) + .field('gender', 'Hombre') + .field('cellphone', 5551112222) + .field('homePhone', 5553334444) + .field('officePhone', 5555556666) + .field('emergencyContact', 'Ana García 5557778888') + .field('mainProfessionalActivity', 'Ingeniero Civil') + .field('dateOfAdmission', 2002) + .field('dateOfBirth', new Date('1983-07-20').toISOString()) + .field('municipalityOfLabor', 'Querétaro') + .field('linkCV', 'https://example.com/luisgarcia-cv') + .field('university', 'Universidad Autónoma de Querétaro') + .field('professionalLicense', 'P98765') + .field('workAddress', '123 Avenida Principal, Querétaro') + .field('homeAddress', '456 Calle Secundaria, Querétaro') + .field('specialty', 'Corresponsable en seguridad estructural') + .field('positionsInCouncil', 'Vocal') + .field('capacitationHours', 90); + + expect(resTest1.statusCode).toEqual(200); + expect(resTest1.body).toBeTruthy(); + expect(resTest1.body.message).toEqual( + 'Te has registrado con éxito, espera a que un administrador verifique que eres el arquitecto con el número de colegiado 45672 y te de acceso al portal.' + ); + + await loginAdmin(agent, 'john@example.com', 'password123'); + const registrationRequest = await RegistrationRequest.find().populate('newInfo'); + const registrationId = registrationRequest.filter( + (registration) => registration.newInfo.newEmail === 'correoaceptado@example.com' + )[0]._id; + + const endpoint = `/architectusers/accept-architect/${registrationId}`; + const resApprove = await agent.patch(endpoint).send(); + + expect(resApprove.statusCode).toEqual(200); + expect(resApprove.body.message).toEqual( + 'Arquitecto verificado. El usuario ahora cuenta con acceso al portal.' + ); + + const resLoginTest = await agent.post('/architectusers/auth/login').send({ + email: 'correoaceptado@example.com', + password: password, + }); + + expect(resLoginTest.statusCode).toEqual(201); + expect(resLoginTest.body).toBeTruthy(); +}; + +beforeAll(async () => { + await connectDB(); + await setUpDbWithMuckData(); + await loginAdmin(agent, 'john@example.com', 'password123'); + registrationRequests = await RegistrationRequest.find(); +}); + +describe('Architect login successful', () => { + test('successful', () => testAcceptedProfileLogin()); + test('successful', () => testPendingProfileNoLogin()); + test('successful', () => testAcceptNewUser()); + test('successful', () => testRejectNewUser()); +}); diff --git a/caeq-backend/utils/email.js b/caeq-backend/utils/email.js index 21b0ef91..08b1c4fb 100644 --- a/caeq-backend/utils/email.js +++ b/caeq-backend/utils/email.js @@ -119,11 +119,20 @@ module.exports = class Email { await this.send('welcomeUser', 'Bienvenido a la familia CAEQ!'); } + /** + * Send a welcome email to a user trying to access the system. + */ + async sendWelcomeUserRegistrationRequested() { + await this.send( + 'welcomeUserRegistrationPending', + 'Bienvenido a la familia CAEQ! Pronto verificaremos su perfil.' + ); + } + /** * Send a welcome email to an administrator. */ async sendWelcomeAdmin() { - // esto va a ser una pug template await this.send( 'welcomeAdmin', 'Bienvenido a la familia CAEQ! Un administrador revisará tu perfil.' @@ -134,7 +143,6 @@ module.exports = class Email { * Send an email to notify that an administrator's request is accepted. */ async sendAdminAccepted() { - // esto va a ser una pug template await this.send( 'adminAccepted', 'Hemos verificado tu perfil! Bienvenido a la familia CAEQ!' @@ -145,15 +153,30 @@ module.exports = class Email { * Send an email to notify that an administrator's request is rejected. */ async sendAdminRejected() { - // esto va a ser una pug template - await this.send('adminRejected', 'Hemos rechazado tu perfil de acceso.'); + await this.send('adminRejected', 'Hemos rechazado tu solicitud de acceso.'); + } + + /** + * Send an email to notify that an architect's request is accepted. + */ + async sendArchitectAccepted() { + await this.send( + 'architectAccepted', + 'Hemos verificado tu perfil! Bienvenido a la familia CAEQ!' + ); + } + + /** + * Send an email to notify that an architect's request is rejected. + */ + async sendArchitectRejected() { + await this.send('architectRejected', 'Hemos rechazado tu perfil de acceso.'); } /* * Send a password reset email to the user. * Note: This method is commented out in the original code. */ - async sendPasswordReset() { await this.send( 'passwordReset', diff --git a/caeq-backend/views/emails/architectAccepted.pug b/caeq-backend/views/emails/architectAccepted.pug new file mode 100644 index 00000000..0ebd616d --- /dev/null +++ b/caeq-backend/views/emails/architectAccepted.pug @@ -0,0 +1,7 @@ +extends baseEmail + +block content + p Hola Arq. #{firstName}, + p Bienvenido al portal del Colegio de Arquitectos del Estado de Querétaro (CAEQ), nos alegra que nos acompañes 🎉🙏 + p Hemos verificado los datos que usted ingresó. Ahora cuenta con acceso a la plataforma. + p - El equipo de CAEQ. \ No newline at end of file diff --git a/caeq-backend/views/emails/architectRejected.pug b/caeq-backend/views/emails/architectRejected.pug new file mode 100644 index 00000000..dedf8d33 --- /dev/null +++ b/caeq-backend/views/emails/architectRejected.pug @@ -0,0 +1,7 @@ +extends baseEmail + +block content + p Hola Arq. #{firstName}, + p Lo sentimos, los datos que ingresó han sido verificados por los administradores. Los datos no coinciden con nuestro registro del número de colegiado que eligió. + p La solicitud de acceso ha sido rechazada. Si usted cree que esto es un error, contáctanos para recibir más información. + p - El equipo de CAEQ. \ No newline at end of file diff --git a/caeq-backend/views/emails/welcomeUser.pug b/caeq-backend/views/emails/welcomeUser.pug index 7f4dc043..15473fca 100644 --- a/caeq-backend/views/emails/welcomeUser.pug +++ b/caeq-backend/views/emails/welcomeUser.pug @@ -1,8 +1,8 @@ extends baseEmail block content - h2 Hola Arq. #{firstName}, - p Bienvenido a la familia del Colegio de Arquitectos del Estado de Querétaro (CAEQ), nos alegra que nos acompañes 🎉🙏 + p Hola Arq. #{firstName}, + p Bienvenido al portal del Colegio de Arquitectos del Estado de Querétaro (CAEQ), nos alegra que nos acompañes 🎉🙏 p Somos una gran familia con muchos cursos y oportunidades de aprendizaje que ofrecer. Empieza revisando nuestra pagina oficial! p Si necesitas ayuda con alguno de nuestros cursos o servicios no dudes en contactarme! p - El equipo de CAEQ. \ No newline at end of file diff --git a/caeq-backend/views/emails/welcomeUserRegistrationPending.pug b/caeq-backend/views/emails/welcomeUserRegistrationPending.pug new file mode 100644 index 00000000..45a45ea5 --- /dev/null +++ b/caeq-backend/views/emails/welcomeUserRegistrationPending.pug @@ -0,0 +1,10 @@ +extends baseEmail + +block content + p Hola Arq. #{firstName}, + P Un miembro del equipo de CAEQ ha recibido su solicitud de registro. + p Cuando hizo su registro usó un número de colegiado ya registrado. Verificaremos la información que ingresó y le contactaremos para darle acceso al sistema. + p Bienvenido al portal del Colegio de Arquitectos del Estado de Querétaro (CAEQ), nos alegra que nos acompañes 🎉🙏 + p Somos una gran familia con muchos cursos y oportunidades de aprendizaje que ofrecer. Empieza revisando nuestra página oficial! + p ¡Si necesita ayuda con alguno de nuestros cursos o servicios no dude en contactarnos! + p - El equipo de CAEQ. \ No newline at end of file diff --git a/caeq-web-portal/src/client/ArchitectUser/ArchitecUser.PATCH.js b/caeq-web-portal/src/client/ArchitectUser/ArchitecUser.PATCH.js index 1800a810..ad8f3256 100644 --- a/caeq-web-portal/src/client/ArchitectUser/ArchitecUser.PATCH.js +++ b/caeq-web-portal/src/client/ArchitectUser/ArchitecUser.PATCH.js @@ -1,7 +1,6 @@ -import axios from "axios"; -import baseApiEndpoint from "../backendConfig"; -import { getToken } from "../../utils/auth"; - +import axios from 'axios'; +import baseApiEndpoint from '../backendConfig'; +import { getToken } from '../../utils/auth'; /** * Sends a PATCH request to the '/architectusers/resetpassword/:token' endpoint to reset the user's password. @@ -26,7 +25,6 @@ export async function patchResetPasswordArchitec(token, newPassword, passwordCon return response.data; } - /** * Updates an architect user by ID. * @@ -45,8 +43,36 @@ export async function updateArchitectUserByID(id, data) { const response = await axios.patch(endpoint, data, { headers: { 'Content-Type': 'multipart/form-data', - 'Authorization': `Bearer ${getToken()}`, + Authorization: `Bearer ${getToken()}`, }, }); return response.data; } + +/** + * Accepts an architect user's request to become a member. + * @async + * @function getArchitectUsers + * @returns {Promise} A promise that resolves to an array of architect user documents. + */ +export async function patchAcceptRegistration(id) { + let endpoint = `${baseApiEndpoint}/architectusers/accept-architect/${id}`; + + const response = await axios.patch(endpoint); + + return response.data; +} + +/** + * Rejects an architect user's request to become a member. + * @async + * @function getArchitectUsers + * @returns {Promise} A promise that resolves to an array of architect user documents. + */ +export async function patchRejectRegistration(id) { + let endpoint = `${baseApiEndpoint}/architectusers/reject-architect/${id}`; + + const response = await axios.patch(endpoint); + + return response.data; +} diff --git a/caeq-web-portal/src/client/ArchitectUser/ArchitectUser.GET.js b/caeq-web-portal/src/client/ArchitectUser/ArchitectUser.GET.js index 62480619..200102fb 100644 --- a/caeq-web-portal/src/client/ArchitectUser/ArchitectUser.GET.js +++ b/caeq-web-portal/src/client/ArchitectUser/ArchitectUser.GET.js @@ -11,14 +11,14 @@ export async function getAllArchitectUsers( filtersParams = '', pageLimit = 100 ) { - let endpoint = `${baseApiEndpoint}/architectusers?page=${page}&limit=${pageLimit}&${filtersParams}`; + let endpoint = `${baseApiEndpoint}/architectusers?page=${page}&limit=${pageLimit}&isRequest=false&${filtersParams}`; const response = await axios.get(endpoint); return response.data.data.documents; } export async function getAllPublicArchitectUsers(page = 1, filtersParams = '') { - let endpoint = `${baseApiEndpoint}/architectusers/public?page=${page}&limit=${paginationPageLimit}&${filtersParams}`; + let endpoint = `${baseApiEndpoint}/architectusers/public?page=${page}&limit=${paginationPageLimit}&isRequest=false&${filtersParams}`; const response = await axios.get(endpoint); return response.data.data.documents; @@ -62,3 +62,17 @@ export async function getArchitectUsers() { return response.data.data.documents; } + +/** + * Retrieves a list of architect user registration requests from the server. + * @async + * @function getArchitectUsers + * @returns {Promise} A promise that resolves to an array of architect user documents. + */ +export async function getArchitectRegistrationRequest() { + let endpoint = `${baseApiEndpoint}/architectusers/registration-requests`; + + const response = await axios.get(endpoint); + + return response.data.data.documents; +} diff --git a/caeq-web-portal/src/components/attendeesButton/AttendeesButton.jsx b/caeq-web-portal/src/components/attendeesButton/AttendeesButton.jsx index 85294227..9d2a4a1b 100644 --- a/caeq-web-portal/src/components/attendeesButton/AttendeesButton.jsx +++ b/caeq-web-portal/src/components/attendeesButton/AttendeesButton.jsx @@ -20,42 +20,45 @@ function AttendancesComponent({ attendances }) { return (

Asistencias a Asambleas

-
- {uniqueYears.map((year) => ( -
- handleYearClick(year)} - > - {year} - - {selectedYear === year && ( -
- {attendances - .filter( - (asistencia) => - asistencia.idGathering.year === year && - asistencia.attended - ) - .map((asistencia) => { - const date = new Date( - asistencia.idGathering.date - ); +
+ {uniqueYears.length > 0 ? ( + uniqueYears.map((year) => ( +
+ handleYearClick(year)}> + {year} + + {selectedYear === year && ( +
+ {attendances + .filter( + (asistencia) => + asistencia.idGathering.year === year && + asistencia.attended + ) + .map((asistencia) => { + const date = new Date( + asistencia.idGathering.date + ); - date.setDate(date.getDate() + 1); + date.setDate(date.getDate() + 1); - return ( -

- {date.toLocaleDateString('en-GB')} - - Modalidad: {asistencia.modality} -

- ); - })} -
- )} -
- ))} + return ( +

+ {date.toLocaleDateString('en-GB')} - + Modalidad: {asistencia.modality} +

+ ); + })} +
+ )} +
+ )) + ) : ( +

No hay asistencias registradas

+ )}
); diff --git a/caeq-web-portal/src/components/cards/RegistrationCard.jsx b/caeq-web-portal/src/components/cards/RegistrationCard.jsx new file mode 100644 index 00000000..969f22cd --- /dev/null +++ b/caeq-web-portal/src/components/cards/RegistrationCard.jsx @@ -0,0 +1,73 @@ +import './AdminCard.scss'; +import './RegistrationCard.scss'; +import AcceptIcon from '../icons/AcceptIcon.png'; +import RejectIcon from '../icons/RejectIcon.png'; + +/** + * RegistrationCard component for displaying registration details and actions. + * + * @component + * @param {Object} props - The component props. + * @param {string} props.id - The unique identifier for the registration card. + * @param {Function} props.acceptRegistration - Callback function for accepting the registration. + * @param {Function} props.rejectRegistration - Callback function for rejecting the registration. + * @param {string} props.collegiateNumber - The collegiate number of the architect. + * @param {Object} props.overwrites - Information before the update. + * @param {Object} props.newInfo - Information after the update. + * @returns {JSX.Element} JSX representation of the RegistrationCard component. + */ +const RegistrationCard = ({ + id, + acceptRegistration, + rejectRegistration, + collegiateNumber, + overwrites, + newInfo, +}) => { + return ( +
+

Arquitecto número {collegiateNumber}

+
+
+

Nuevo

+

Nombre: {newInfo.fullName}

+

Correo: {newInfo.newEmail}

+

+ Ingreso al colegio:{' '} + {newInfo.dateOfAdmission || 'Sin fecha de admisión'} +

+

DRO: {newInfo.DRONumber || 'Sin número'}

+

Celular: {newInfo.cellphone || 'Sin número celular'}

+

+ INE: Descargar +

+
+
+

Antes

+

Nombre: {overwrites.fullName}

+

Correo: {overwrites.email}

+

+ Ingreso: {overwrites.dateOfAdmission || 'Sin fecha de admisión'} +

+

DRO: {overwrites.DRONumber || 'Sin número'}

+

Celular: {overwrites.cellphone || 'Sin número celular'}

+
+
+ +
+ acceptRegistration(id)} + src={AcceptIcon} + alt={`Accept Icon`} + /> + rejectRegistration(id)} + src={RejectIcon} + alt={`Reject Icon`} + /> +
+
+ ); +}; + +export default RegistrationCard; diff --git a/caeq-web-portal/src/components/cards/RegistrationCard.scss b/caeq-web-portal/src/components/cards/RegistrationCard.scss new file mode 100644 index 00000000..46843982 --- /dev/null +++ b/caeq-web-portal/src/components/cards/RegistrationCard.scss @@ -0,0 +1,48 @@ +.registration-card { + padding: 10px; + display: flex; + flex-direction: column; + min-width: 300px; + width: 45%; + max-height: 600px; + overflow: auto; + border-radius: 20px; + background: #fff; + box-shadow: 4px 7px 15px 0px rgba(0, 0, 0, 0.35); + + .registration-card-info { + display: flex; + flex-direction: row; + width: 100%; + + @media screen and (max-width: 768px) { + flex-direction: column; + } + + .registration-card-details { + display: flex; + flex-direction: column; + width: 50%; + overflow: auto; + + @media screen and (max-width: 768px) { + width: 100%; + } + + p { + align-self: flex-start; + margin-left: 15px; + } + } + } + + .admin-card-buttons { + align-self: end; + height: 57px; + } + + img { + margin-left: 5px; + cursor: pointer; + } +} diff --git a/caeq-web-portal/src/routes.js b/caeq-web-portal/src/routes.js index 6cd079c8..47639db3 100644 --- a/caeq-web-portal/src/routes.js +++ b/caeq-web-portal/src/routes.js @@ -154,8 +154,8 @@ const routes = [ roles: ['caeq'], }, { - path: '/Admins', - name: 'Admins', + path: '/Usuarios', + name: 'Usuarios', icon: AdminIcon, iconWhite: AdminIconWhite, Component: AcceptAdmin, diff --git a/caeq-web-portal/src/screens/AcceptAdmin/AcceptAdmin.js b/caeq-web-portal/src/screens/AcceptAdmin/AcceptAdmin.js index b1bb7cb3..3778d995 100644 --- a/caeq-web-portal/src/screens/AcceptAdmin/AcceptAdmin.js +++ b/caeq-web-portal/src/screens/AcceptAdmin/AcceptAdmin.js @@ -3,6 +3,12 @@ import './AcceptAdmin.scss'; import AcceptIcon from '../../components/icons/AcceptIcon.png'; import RejectIcon from '../../components/icons/RejectIcon.png'; import AdminCard from '../../components/cards/AdminCard'; +import RegistrationCard from '../../components/cards/RegistrationCard'; +import { getArchitectRegistrationRequest } from '../../client/ArchitectUser/ArchitectUser.GET'; +import { + patchAcceptRegistration, + patchRejectRegistration, +} from '../../client/ArchitectUser/ArchitecUser.PATCH'; import { getCaeqUsers } from '../../client/CaeqUser/CaeqUser.GET'; import { patchAcceptAdmin, patchRejectAdmin } from '../../client/CaeqUser/CaeqUser.PATCH'; import { @@ -25,6 +31,7 @@ import { useNavigate } from 'react-router-dom'; */ const AcceptAdmin = () => { const [admins, setAdmins] = useState([]); + const [registrations, setRegistrations] = useState([]); const navigate = useNavigate(); useEffect(() => { @@ -33,6 +40,10 @@ const AcceptAdmin = () => { const admins = await getCaeqUsers(false); setAdmins(admins); + + const registrations = await getArchitectRegistrationRequest(); + + setRegistrations(registrations); } catch (error) { FireError(error.response.data.message); } @@ -43,7 +54,7 @@ const AcceptAdmin = () => { * Handles the approval of an administrator. * @param {string} id - The ID of the administrator to be approved. */ - const handleAccept = async (id) => { + const handleAcceptAdmin = async (id) => { try { const confirmation = await FireQuestion( '¿Está seguro que desea aprobar al administrador?', @@ -76,7 +87,7 @@ const AcceptAdmin = () => { * Handles the rejection of an administrator. * @param {string} id - The ID of the administrator to be rejected. */ - const handleReject = async (id) => { + const handleRejectAdmin = async (id) => { try { const confirmation = await FireQuestion( '¿Está seguro que desea rechazar al administrador?', @@ -105,37 +116,181 @@ const AcceptAdmin = () => { } }; + /** + * Handles the approval of a registration. + * @param {string} id - The ID of the registration to be approved. + */ + const handleAcceptRegistration = async (id) => { + try { + const registration = registrations.filter( + (registration) => registration._id === id + )[0]; + + if (registration.overwrites.overwritten) { + const confirmation = await FireQuestion( + '¿Está seguro que desea aprobar al nuevo usuario?', + 'Este usuario ya ha sido sobreescrito anteriormente. Aprobar esta solicitud podría dejar sin acceso al portal a un usuario existente.' + ); + + if (!confirmation.isConfirmed) { + return; + } + } else { + const confirmation = await FireQuestion( + '¿Está seguro que desea aprobar al nuevo usuario?', + 'Esta acción no se puede deshacer. El usuario tendrá acceso a la aplicación.' + ); + + if (!confirmation.isConfirmed) { + return; + } + } + + const swal = FireLoading('Aceptando usuario...'); + const response = await patchAcceptRegistration(id); + + const newUser = registration.newInfo; + const oldUser = registration.overwrites; + + setRegistrations( + registrations + .filter((registration) => registration._id !== id) + .map((registration) => { + if (registration.overwrites._id === oldUser._id) { + registration.overwrites = newUser; + registration.overwrites.email = newUser.newEmail; + } + console.log(registration); + + return registration; + }) + ); + + swal.close(); + FireSucess(response.message); + } catch (error) { + console.log(error); + FireError(error.response.data.message); + if ( + error.response.data.message === + 'Hemos tenido problemas enviando un correo de verificación. El usuario ha sido verificado.' + ) { + setRegistrations( + registrations.filter((registration) => registration._id !== id) + ); + } + } + }; + + /** + * Handles the rejection of a registration. + * @param {string} id - The ID of the registration to be rejected. + */ + const handleRejectRegistration = async (id) => { + try { + const confirmation = await FireQuestion( + '¿Está seguro que desea rechazar al administrador?', + 'Esta acción no se puede deshacer. El usuario que intenta acceder será eliminado.' + ); + + if (!confirmation.isConfirmed) { + return; + } + + const swal = FireLoading('Rechazando usuario...'); + const response = await patchRejectRegistration(id); + + setRegistrations( + registrations.filter((registration) => registration._id !== id) + ); + + swal.close(); + FireSucess(response.message); + } catch (error) { + FireError(error.response.data.message); + if ( + error.response.data.message === + 'Hemos tenido problemas enviando un correo de verificación. El usuario ha sido eliminado.' + ) { + setRegistrations( + registrations.filter((registration) => registration._id !== id) + ); + } + } + }; + return ( -
-

Administradores por registrar

-

- El propósito de esta sección es conceder autorizaciones a otras cuentas - para permitirles el acceso al portal de administración. -

-
- {`Accept -

- Da click a para aceptar la petición de otorgar a una cuenta acceso al - portal de administración. -

-
-
- {`Reject -

- Da click para rechazar la petición de otorgar a una cuenta acceso al - portal de administración. -

+
+
+

Administradores por registrar

+

+ El propósito de esta sección es conceder autorizaciones a otras + cuentas para permitirles el acceso al portal de administración. +

+
+ {`Accept +

+ Da click a para aceptar la petición de otorgar a una cuenta acceso + al portal de administración. +

+
+
+ {`Reject +

+ Da click para rechazar la petición de otorgar a una cuenta acceso + al portal de administración. +

+
+
+ {admins.map((admin) => ( + + ))} +
-
- {admins.map((admin) => ( - - ))} +
+

Colegiados por registrar

+

+ El propósito de esta sección es conceder autorizaciones a otras + cuentas para permitirles el acceso al portal de arquitectos. Las + peticiones de acceso son creadas cuando un arquitecto se registra con + un número de colegiado que ya existe en la base de datos. +

+
+ {`Accept +

+ Da click a para aceptar la petición de sobreescribir la + información anterior del arquitecto. +

+
+
+ {`Reject +

+ Da click a para rechazar la petición de sobreescribir la + información anterior del arquitecto. +

+
+
+ {registrations.length > 0 ? ( + registrations.map((registration) => ( + + )) + ) : ( +

No hay usuarios por aceptar

+ )} +
); diff --git a/caeq-web-portal/src/screens/AcceptAdmin/AcceptAdmin.scss b/caeq-web-portal/src/screens/AcceptAdmin/AcceptAdmin.scss index 17b64f32..8e92ab4f 100644 --- a/caeq-web-portal/src/screens/AcceptAdmin/AcceptAdmin.scss +++ b/caeq-web-portal/src/screens/AcceptAdmin/AcceptAdmin.scss @@ -42,4 +42,13 @@ flex-wrap: wrap; justify-content: space-around; } + + .registration-cards { + margin-top: 2rem; + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: space-around; + gap: 30px; + } } diff --git a/caeq-web-portal/src/screens/Profile/Profile.js b/caeq-web-portal/src/screens/Profile/Profile.js index d28905af..5b3bcea7 100644 --- a/caeq-web-portal/src/screens/Profile/Profile.js +++ b/caeq-web-portal/src/screens/Profile/Profile.js @@ -253,7 +253,7 @@ const Profile = (props) => {

Municipio: - list' {profile.municipalityOfLabor} + {profile.municipalityOfLabor}

Currículum Vitae (CV): diff --git a/caeq-web-portal/src/screens/SignupArchitect/SignupArchitect.js b/caeq-web-portal/src/screens/SignupArchitect/SignupArchitect.js index 73c7ca5d..a3605e36 100644 --- a/caeq-web-portal/src/screens/SignupArchitect/SignupArchitect.js +++ b/caeq-web-portal/src/screens/SignupArchitect/SignupArchitect.js @@ -183,10 +183,10 @@ const Signup = () => { } if (user) { const continueSignUp = await FireQuestion( - 'Arquitecto ya existente', + 'Número de colegiado ya registrado.', `El arquitecto con número de colegiado ${collegiateNumber} ya existe. - ¿Es usted ${user.fullName}? - ¿Desea continuar y actualizar con la información proporcionada?` + ¿Es usted ${user.fullName}? Si no es usted, por favor verifique que el número de colegiado que ingresó es correcto. Presione cancelar. + ¿Desea continuar y actualizar con la información proporcionada? Presione aceptar. Un administrador revisará sus datos y le dará acceso a la plataforma en breve.` ); if (!continueSignUp.isConfirmed) return; } @@ -194,12 +194,21 @@ const Signup = () => { // Post user try { const response = await postSignupArchitectUsers(form); - if (response.status === 'success') { + + if (response.status === 'success' && response.statusCode === 201) { const token = response.token; user = response.data.user; setUserType(token); setToken(token); setArchitectUserSaved(response.data.user); + + swal.close(); + FireSucess('Te has registrado con éxito'); + navigate('/Principal'); + } else { + swal.close(); + FireSucess(response.message); + navigate('/'); } } catch (error) { const message = error.response.data.message ||