From 232a096371392f63256a8bca59c74dd85a77bbd8 Mon Sep 17 00:00:00 2001 From: PluckySquirrel Date: Mon, 4 Nov 2024 08:10:28 +0700 Subject: [PATCH] validation integration --- package-lock.json | 11 ++ package.json | 1 + src/controller/comment.controller.ts | 4 +- src/controller/course.controller.ts | 2 - src/controller/enrollment.controller.ts | 6 +- src/controller/lesson.controller.ts | 22 ++- src/controller/payment.controller.ts | 4 +- .../professor/professorCourse.controller.ts | 18 ++- .../professor/professorLesson.controller.ts | 20 ++- .../professor/professorSection.controller.ts | 13 +- src/controller/review.controller.ts | 10 +- src/controller/user.controller.ts | 138 ++++++++++++++---- src/entity/dto/lesson.dto.ts | 4 +- src/entity/dto/user.dto.ts | 55 ++++++- src/migration/1730713648023-migration.ts | 1 - src/public/js/editUserDetails.js | 51 +++++++ src/public/js/signup.js | 65 +++++++++ src/public/stylesheets/style.css | 9 ++ src/service/user.service.ts | 8 +- src/views/layout.pug | 2 + src/views/login.pug | 25 +++- src/views/signup.pug | 47 ------ src/views/user-details.pug | 103 +++++++------ 23 files changed, 445 insertions(+), 174 deletions(-) create mode 100644 src/public/js/editUserDetails.js create mode 100644 src/public/js/signup.js diff --git a/package-lock.json b/package-lock.json index 98ee6306..df063406 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "connect-mysql": "^4.0.0", "connect-mysql2": "^2.2.6", "cookie-parser": "^1.4.6", + "date-fns": "^4.1.0", "dotenv": "^16.4.5", "express": "^4.21.1", "express-async-errors": "^3.1.1", @@ -2004,6 +2005,16 @@ "node": ">= 8" } }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/dayjs": { "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", diff --git a/package.json b/package.json index a3a55602..12c593b2 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "connect-mysql": "^4.0.0", "connect-mysql2": "^2.2.6", "cookie-parser": "^1.4.6", + "date-fns": "^4.1.0", "dotenv": "^16.4.5", "express": "^4.21.1", "express-async-errors": "^3.1.1", diff --git a/src/controller/comment.controller.ts b/src/controller/comment.controller.ts index ea28ebdf..cc83d9ca 100644 --- a/src/controller/comment.controller.ts +++ b/src/controller/comment.controller.ts @@ -63,7 +63,7 @@ export const updateCommentPost = asyncHandler( } const commentData = new CommentDTO(); - commentData.comment_text = updatedText; + commentData.parent_id = undefined; // Update comment and save to database comment.comment_text = updatedText; @@ -92,7 +92,7 @@ export const createCommentPost = asyncHandler( commentData.review_id = review_id; commentData.user_id = user_id; commentData.comment_text = reply; - commentData.parent_id = parent_id; + commentData.parent_id = parent_id ? parent_id : undefined; commentData.course_id = course_id; try { diff --git a/src/controller/course.controller.ts b/src/controller/course.controller.ts index 29d55f91..0cf2a477 100644 --- a/src/controller/course.controller.ts +++ b/src/controller/course.controller.ts @@ -53,8 +53,6 @@ export const courseShowGet = asyncHandler( filterData.sortOrder = sortOrder; } - await validateOrReject(filterData); - try { await validateOrReject(filterData); const trans = { diff --git a/src/controller/enrollment.controller.ts b/src/controller/enrollment.controller.ts index af817efb..43ab83b7 100644 --- a/src/controller/enrollment.controller.ts +++ b/src/controller/enrollment.controller.ts @@ -3,11 +3,11 @@ import asyncHandler from 'express-async-handler'; import { updateEnrollmentProgress, enrollUserInCourse, getEnrollment, markLessonAsDone, hasUserPurchasedCourse, getEnrollmentWithCourseAndUser } from '../service/enrollment.service'; import { getProfessorAndCourseCountByCourseId, getSectionsWithLessons, countEnrolledUsersInCourse, getCourseById, getProfessorByCourse } from '../service/course.service'; import { getAllCommentsByCourseId } from '../service/comment.service'; -import { getUserById } from '@src/service/user.service'; -import { getEnrollmentLesson } from '@src/service/enrollmentlesson.service'; +import { getUserById } from 'src/service/user.service'; +import { getEnrollmentLesson } from 'src/service/enrollmentlesson.service'; import { validateOrReject } from 'class-validator'; import { plainToInstance } from 'class-transformer'; -import { GetUserCourseEnrollmentsDto, UpdateLessonProgressDto } from '@src/entity/dto/entrollment.dto'; +import { GetUserCourseEnrollmentsDto, UpdateLessonProgressDto } from 'src/entity/dto/entrollment.dto'; export const getUserCourseEnrollments = asyncHandler(async (req: Request, res: Response) => { diff --git a/src/controller/lesson.controller.ts b/src/controller/lesson.controller.ts index 14cf9666..6f0ded86 100644 --- a/src/controller/lesson.controller.ts +++ b/src/controller/lesson.controller.ts @@ -1,8 +1,7 @@ import { Request, Response } from 'express'; import asyncHandler from 'express-async-handler'; import { createLesson, deleteLesson, findLessonById, getAllLessons, saveLesson } from '../service/lession.service'; -import { plainToInstance } from 'class-transformer'; -import { LessonCreateDto, LessonUpdateDto } from '@src/entity/dto/lesson.dto'; +import { LessonCreateDto, LessonUpdateDto } from 'src/entity/dto/lesson.dto'; import { validate } from 'class-validator'; // Get the list of lessons @@ -16,7 +15,15 @@ export const lessonCreateGet = asyncHandler(async (req: Request, res: Response): }); export const lessonCreatePost = asyncHandler(async (req: Request, res: Response): Promise => { - const lessonData = plainToInstance(LessonCreateDto, req.body); + const lessonData = new LessonCreateDto() + lessonData.content = req.body.content + lessonData.description = req.body.description + lessonData.name = req.body.name + lessonData.progress = req.body.progress + lessonData.section_id = req.body.section_id + lessonData.time = req.body.time + lessonData.type = req.body.type + const errors = await validate(lessonData); if (errors.length > 0) { @@ -65,7 +72,14 @@ export const lessonUpdatePost = asyncHandler(async (req: Request, res: Response) return res.status(404).render('error', { message: req.t('course.lesson_not_found') }); } - const lessonData = plainToInstance(LessonUpdateDto, req.body); + const lessonData = new LessonUpdateDto() + lessonData.content = req.body.content ? req.body.content : undefined + lessonData.description = req.body.description ? req.body.description : undefined + lessonData.name = req.body.name ? req.body.name : undefined + lessonData.progress = req.body.progress ? req.body.progress : undefined + lessonData.time = req.body.time ? req.body.time : undefined + lessonData.type = req.body.type ? req.body.type : undefined + const errors = await validate(lessonData); if (errors.length > 0) { diff --git a/src/controller/payment.controller.ts b/src/controller/payment.controller.ts index 0969f16f..c5dc6343 100644 --- a/src/controller/payment.controller.ts +++ b/src/controller/payment.controller.ts @@ -7,8 +7,8 @@ import { } from "../service/payment.service"; import { getCourseById } from "../service/course.service"; import { hasUserPurchasedCourse } from "../service/enrollment.service"; -import { getItemByCourseId, removeFromCart } from "@src/service/cart.service"; -import { ProcessPaymentDto, SubmitPaymentDto } from "@src/entity/dto/payment.dto"; +import { getItemByCourseId, removeFromCart } from "src/service/cart.service"; +import { ProcessPaymentDto, SubmitPaymentDto } from "src/entity/dto/payment.dto"; import { validateOrReject } from "class-validator"; export const processPayment = asyncHandler( diff --git a/src/controller/professor/professorCourse.controller.ts b/src/controller/professor/professorCourse.controller.ts index d9bf22d8..50f04cfa 100644 --- a/src/controller/professor/professorCourse.controller.ts +++ b/src/controller/professor/professorCourse.controller.ts @@ -41,11 +41,12 @@ export const professorCreateCourse = async (req: Request, res: Response) => { throw new Error(req.t('course.invalid_professor_id')); } const courseData = new CreateCourseDto(); - courseData.average_rating = req.body.average_rating - courseData.category_id = req.body.category_id + courseData.average_rating = req.body.average_rating ? parseInt(req.body.average_rating) : 0 + courseData.category_id = req.body.category_id ? parseInt(req.body.category_id) : 0 courseData.description = req.body.description courseData.name = req.body.name - courseData.price = req.body.price + courseData.price = req.body.price ? parseInt(req.body.category_id) : 0 + console.log(courseData) await validateOrReject(courseData) @@ -62,6 +63,7 @@ export const professorCreateCourse = async (req: Request, res: Response) => { } catch (error) { if (Array.isArray(error) && error[0].constraints) { const validationErrors = error.map(err => Object.values(err.constraints)).flat(); + console.log(validationErrors) res.status(400).render('error', { message: req.t('course.update_error', { error: validationErrors.join(', ') }) }); } else { res.status(400).render("error", { error: error.message }); @@ -84,11 +86,11 @@ export const professorUpdateCourse = async (req: Request, res: Response) => { } const courseData = new CreateCourseDto(); - courseData.average_rating = req.body.average_rating - courseData.category_id = req.body.category_id - courseData.description = req.body.description - courseData.name = req.body.name - courseData.price = req.body.price + courseData.average_rating = req.body.average_rating ? parseInt(req.body.average_rating) : 0 + courseData.category_id = req.body.category_id ? parseInt(req.body.category_id) : 0 + courseData.description = req.body.description ? req.body.description : undefined + courseData.name = req.body.name ? req.body.name : undefined + courseData.price = req.body.price ? parseInt(req.body.price) : 0 await validateOrReject(courseData) diff --git a/src/controller/professor/professorLesson.controller.ts b/src/controller/professor/professorLesson.controller.ts index ddbff5f4..beba1e48 100644 --- a/src/controller/professor/professorLesson.controller.ts +++ b/src/controller/professor/professorLesson.controller.ts @@ -3,8 +3,7 @@ import asyncHandler from 'express-async-handler'; import { getLessonsBySectionIds, createLesson, updateLesson, deleteLesson } from 'src/service/lession.service'; import { getCoursesByUserId } from 'src/service/course.service'; import { getSectionsByCourseIds } from 'src/service/section.service'; -import { LessonCreateDto } from 'src/entity/dto/lesson.dto'; -import { plainToClass } from 'class-transformer'; +import { LessonCreateDto, LessonUpdateDto } from 'src/entity/dto/lesson.dto'; import { validateOrReject } from 'class-validator'; export const professorLesonShowGet = asyncHandler(async (req: Request, res: Response) => { @@ -41,7 +40,14 @@ export const professorLesonShowGet = asyncHandler(async (req: Request, res: Resp }); export const professorCreateLesson = async (req: Request, res: Response) => { - const lessonData = plainToClass(LessonCreateDto, req.body) + const lessonData = new LessonCreateDto() + lessonData.content = req.body.content + lessonData.description = req.body.description + lessonData.name = req.body.name + lessonData.progress = req.body.progress ? parseInt(req.body.progress) : 0 + lessonData.section_id = req.body.section_id ? parseInt(req.body.section_id) : 0 + lessonData.time = req.body.time ? parseInt(req.body.time) : 0 + lessonData.type = req.body.type try { await validateOrReject(lessonData); @@ -89,6 +95,14 @@ export const professorUpdateLesson = async (req: Request, res: Response) => { return; } + const lessonData = new LessonUpdateDto() + lessonData.content = req.body.content ? req.body.content : undefined + lessonData.description = req.body.description ? req.body.description : undefined + lessonData.name = req.body.name ? req.body.name : undefined + lessonData.progress = req.body.progress ? parseInt(req.body.progress) : 0 + lessonData.time = req.body.time ? parseInt(req.body.time) : 0 + lessonData.type = req.body.type ? req.body.type : undefined + const updatedLesson = await updateLesson(lessonId, req.body); if (!updatedLesson) { diff --git a/src/controller/professor/professorSection.controller.ts b/src/controller/professor/professorSection.controller.ts index 61943f3f..9f2450cd 100644 --- a/src/controller/professor/professorSection.controller.ts +++ b/src/controller/professor/professorSection.controller.ts @@ -25,9 +25,9 @@ export const professorCreateSection = async (req: Request, res: Response) => { const sectionData = new CreateSectionDto(); sectionData.name = names[i]; - sectionData.total_lesson = totalLessons[i]; + sectionData.total_lesson = totalLessons[i] ? totalLessons[i] : undefined; sectionData.course_id = courseId; - sectionData.total_time = totalTimes[i]; + sectionData.total_time = totalTimes[i] ? totalTimes[i] : undefined; const errors = await validate(sectionData); @@ -61,8 +61,13 @@ export const professorUpdateSection = async (req: Request, res: Response) => { return; } - const sectionDto = plainToInstance(UpdateSectionDto, req.body); - const errors = await validate(sectionDto); + const sectionData = new UpdateSectionDto() + sectionData.id = sectionId + sectionData.name = req.body.name ? req.body.name : undefined + sectionData.total_lesson = req.body.total_lesson ? req.body.total_lesson : undefined + sectionData.total_time = req.body.total_time ? req.body.total_time : undefined + + const errors = await validate(sectionData); if (errors.length > 0) { const messages = errors.map((err) => Object.values(err.constraints || {})).flat(); diff --git a/src/controller/review.controller.ts b/src/controller/review.controller.ts index 8570cfae..a14ba7e7 100644 --- a/src/controller/review.controller.ts +++ b/src/controller/review.controller.ts @@ -15,14 +15,14 @@ export const createReviewPost = asyncHandler( } const reviewDto = new CreateReviewDto(); - reviewDto.user_id = user_id; - reviewDto.course_id = course_id; - reviewDto.rating = rating; + reviewDto.user_id = parseInt(user_id); + reviewDto.course_id = parseInt(course_id); + reviewDto.rating = parseInt(rating); const commentDto = new CreateCommentDto(); commentDto.review_id = 0; - commentDto.user_id = user_id; - commentDto.parent_id = parent_id; + commentDto.user_id = parseInt(user_id); + commentDto.parent_id = parent_id ? parseInt(parent_id) : 0; commentDto.comment = comment; try { diff --git a/src/controller/user.controller.ts b/src/controller/user.controller.ts index b9f24eff..724e24af 100644 --- a/src/controller/user.controller.ts +++ b/src/controller/user.controller.ts @@ -1,8 +1,6 @@ import { Request, Response } from "express"; import asyncHandler from "express-async-handler"; -import { plainToClass } from 'class-transformer'; import { validateOrReject } from "class-validator"; -import { UserRoleType, UserGenderType } from "src/enum/user.enum"; import { userRegister, userLogin, @@ -11,6 +9,7 @@ import { saveUserDetails, getPurchasedCoursesWithDetails, updateUserPassword, + formatFieldName } from "../service/user.service"; import { getEnrollmentWithCourseAndUser } from "../service/enrollment.service"; import { @@ -18,44 +17,89 @@ import { countEnrolledUsersInCourse, getCoursesByUserId, } from "../service/course.service"; -import { UserRegisterDto, UserLoginDto } from 'src/entity/dto/user.dto'; +import { UserRegisterDto, UserLoginDto, UpdateUserDto } from 'src/entity/dto/user.dto'; +import { UserGenderType, UserRoleType } from "src/enum/user.enum"; export const register = asyncHandler(async (req: Request, res: Response) => { - const userRegisterData = plainToClass(UserRegisterDto, req.body); + const userData = new UserRegisterDto(); + userData.name = req.body.name + userData.email = req.body.email + userData.password = req.body.password + userData.role = req.body.role ? req.body.role : UserRoleType.USER + userData.phone_number = req.body.phone_number + userData.date_of_birth = req.body.date_of_birth + userData.gender = req.body.gender ? req.body.gender : UserGenderType.MALE + userData.address = req.body.address ? req.body.address : undefined + userData.identity_card = req.body.identity_card ? req.body.identity_card : undefined + userData.additional_info = req.body.additional_info ? req.body.additional_info : undefined + if (userData.role == UserRoleType.PROFESSOR) { + userData.department = req.body.department ? req.body.department : undefined + userData.years_of_experience = req.body.years_of_experience ? parseInt(req.body.years_of_experience) : 0 + } else { + userData.department = undefined + userData.years_of_experience = undefined + } + + const { + name, + email, + password, + role, + phone_number, + avatar, + date_of_birth, + gender, + address, + identity_card, + additional_info, + department, + years_of_experience, + } = req.body; try { - await validateOrReject(userRegisterData); + await validateOrReject(userData) + const user = await userRegister( - userRegisterData.name, - userRegisterData.email, - userRegisterData.password, - userRegisterData.role || UserRoleType.USER, - userRegisterData.phone_number, - userRegisterData.avatar || '', - userRegisterData.date_of_birth, - userRegisterData.gender || UserGenderType.MALE, - userRegisterData.address || '', - userRegisterData.identity_card || '', - userRegisterData.additional_info || '', - userRegisterData.department || '', - userRegisterData.years_of_experience || 0 + name, + email, + password, + role, + phone_number, + avatar, + date_of_birth, + gender, + address, + identity_card, + additional_info, + department, + years_of_experience ); res .status(201) .json({ status: 201, message: req.t("signup.signup-success"), user }); } catch (error) { - const message = - error.message === req.t("user.user_error") - ? error.message - : req.t("signup.signup-failure"); - res.status(400).json({ status: 400, message }); + if (Array.isArray(error) && error[0].constraints) { + const validationErrors = error.reduce((acc, err) => { + acc[err.property] = Object.values(err.constraints).join(", "); + return acc; + }, {}); + // console.log(validationErrors) + res.status(400).json({ status: 400, errors: validationErrors }); + } else { + const message = + error.message === req.t("user.user_error") + ? error.message + : req.t("signup.signup-failure"); + res.status(400).json({ status: 400, message }); + } } }); export const login = asyncHandler(async (req: Request, res: Response) => { - const userLoginData = plainToClass(UserLoginDto, req.body); - + const userLoginData = new UserLoginDto() + userLoginData.email = req.body.email + userLoginData.password = req.body.password try { await validateOrReject(userLoginData); const { token, user } = await userLogin(userLoginData.email, userLoginData.password); @@ -72,9 +116,20 @@ export const login = asyncHandler(async (req: Request, res: Response) => { user, }); } catch (error) { - res - .status(400) - .json({ status: 400, message: req.t("login.login-failure") }); + if (Array.isArray(error) && error[0].constraints) { + const validationErrors = error.reduce((acc, err) => { + acc[err.property] = Object.values(err.constraints).join(", "); + return acc; + }, {}); + // console.log(validationErrors) + res.status(400).json({ status: 400, errors: validationErrors }); + } else { + const message = + error.message === req.t("user.user_error") + ? error.message + : req.t("signup.signup-failure"); + res.status(400).json({ status: 400, message }); + } } }); @@ -232,6 +287,18 @@ export const updateUserDetails = asyncHandler( .status(404) .render("error", { message: req.t("user.user_authenticated") }); } + const userData = new UpdateUserDto() + userData.additional_info = req.body.additional_info ? req.body.additional_info : undefined + userData.address = req.body.address ? req.body.address : undefined + userData.avatar = req.body.avatar ? req.body.avatar : undefined + userData.date_of_birth = req.body.date_of_birth ? req.body.date_of_birth : undefined + userData.department = req.body.department ? req.body.department : undefined + userData.gender = req.body.gender ? req.body.gender : undefined + userData.identity_card = req.body.identity_card ? req.body.identity_card : undefined + userData.name = req.body.name ? req.body.name : undefined + userData.phone_number = req.body.phone_number ? req.body.phone_number : undefined + userData.years_of_experience = req.body.years_of_experience ? parseInt(req.body.years_of_experience) : 0 + const { name, phone_number, @@ -246,6 +313,7 @@ export const updateUserDetails = asyncHandler( } = req.body; try { + await validateOrReject(userData) const user = await getUserById(parseInt(userId)); if (user) { user.name = name; @@ -270,9 +338,17 @@ export const updateUserDetails = asyncHandler( .render("error", { message: req.t("user.user_not_found") }); } } catch (error) { - return res - .status(404) - .render("error", { message: req.t("user.update_user_error") }); + if (Array.isArray(error) && error[0].constraints) { + const validationErrors = error.reduce((acc, err) => { + acc[err.property] = Object.values(err.constraints).join(", "); + return acc; + }, {}); + res.status(400).json({ status: 400, errors: validationErrors }); + } else { + return res + .status(404) + .render("error", { message: req.t("user.update_user_error") }); + } } } ); diff --git a/src/entity/dto/lesson.dto.ts b/src/entity/dto/lesson.dto.ts index 6d90d6d4..bc136eea 100644 --- a/src/entity/dto/lesson.dto.ts +++ b/src/entity/dto/lesson.dto.ts @@ -11,7 +11,7 @@ export class LessonCreateDto { @IsInt() @Min(0) @Max(100) - progress!: number | null; + progress!: number; @IsNotEmpty() @IsEnum(LessonType) @@ -44,7 +44,7 @@ export class LessonUpdateDto { @IsInt() @Min(0) @Max(100) - progress?: number | null; + progress?: number; @IsOptional() @IsEnum(LessonType) diff --git a/src/entity/dto/user.dto.ts b/src/entity/dto/user.dto.ts index 7ec22cdd..c6a5ac8c 100644 --- a/src/entity/dto/user.dto.ts +++ b/src/entity/dto/user.dto.ts @@ -15,22 +15,18 @@ export class UserRegisterDto { @IsEnum(UserRoleType) @IsOptional() - role?: UserRoleType = UserRoleType.USER; + role?: UserRoleType; @IsString() @Length(10, 15) phone_number!: string; - @IsString() - @IsOptional() - avatar?: string; - @IsDateString() date_of_birth!: Date; @IsEnum(UserGenderType) @IsOptional() - gender?: UserGenderType = UserGenderType.MALE; + gender?: UserGenderType; @IsString() @IsOptional() @@ -61,3 +57,50 @@ export class UserLoginDto { @Length(8, 20) password!: string; } + +export class UpdateUserDto { + @IsOptional() + @IsString() + @Length(1, 50, { message: "Name must be between 1 and 50 characters" }) + name?: string; + + @IsOptional() + @IsString() + @Length(10, 15, { message: "Phone number must be between 10 and 15 characters" }) + phone_number?: string; + + @IsOptional() + @IsString() + avatar?: string; + + @IsOptional() + @IsDateString({}, { message: "Date of birth must be a valid date" }) + date_of_birth?: string; + + @IsOptional() + @IsEnum(UserGenderType, { message: `Gender must be either 'male', 'female', or 'other'` }) + gender?: UserGenderType; + + @IsOptional() + @IsString() + @Length(0, 100, { message: "Address cannot exceed 100 characters" }) + address?: string; + + @IsOptional() + @IsString() + @Length(0, 20, { message: "Identity card cannot exceed 20 characters" }) + identity_card?: string; + + @IsOptional() + @IsString() + @Length(0, 500, { message: "Additional information cannot exceed 500 characters" }) + additional_info?: string; + + @IsOptional() + @IsString() + department?: string; + + @IsOptional() + @IsInt({ message: "Years of experience must be a number" }) + years_of_experience?: number; +} diff --git a/src/migration/1730713648023-migration.ts b/src/migration/1730713648023-migration.ts index 6ce9fb1f..25fc7905 100644 --- a/src/migration/1730713648023-migration.ts +++ b/src/migration/1730713648023-migration.ts @@ -12,5 +12,4 @@ export class Migration1730713648023 implements MigrationInterface { await queryRunner.query(`ALTER TABLE \`comments\` DROP FOREIGN KEY \`FK_8510ab448f65396f69cc54c858c\``); await queryRunner.query(`ALTER TABLE \`comments\` DROP COLUMN \`course_id\``); } - } diff --git a/src/public/js/editUserDetails.js b/src/public/js/editUserDetails.js new file mode 100644 index 00000000..db4e89d3 --- /dev/null +++ b/src/public/js/editUserDetails.js @@ -0,0 +1,51 @@ +document.getElementById('update-form').addEventListener('submit', async (e) => { + e.preventDefault(); + const formData = new FormData(e.target); + +try { + const response = await $.ajax({ + url: "/account/edit", + method: "POST", + contentType: "application/json", + data: JSON.stringify(data), + }); + // const response = await fetch('/account/edit', { + // method: 'POST', + // body: formData + // }); + + const result = await response.json(); + + if (result.errors) { + // Display errors in the form fields + for (let field in result.errors) { + const input = document.querySelector(`#update-form [name="${field}"]`); + if (input) { + input.classList.add('is-invalid'); + const errorMsg = document.createElement('div'); + errorMsg.className = 'error-message text-danger'; + errorMsg.innerText = result.errors[field]; + input.parentElement.appendChild(errorMsg); + } + } + } else { + // Handle successful submission (e.g., close modal or show success message) + } + } catch (error) { + if (error.responseJSON && error.responseJSON.errors) { + // Display each validation error next to the input field + const errors = error.responseJSON.errors; + for (const [field, message] of Object.entries(errors)) { + const inputField = document.querySelector(`#${field}`); + if (inputField) { + const errorElement = document.createElement("div"); + errorElement.className = "error-message text-danger mt-1"; + errorElement.textContent = message; + inputField.parentNode.appendChild(errorElement); + } + } + } else { + console.error('Error submitting form:', error); + } + } +}); diff --git a/src/public/js/signup.js b/src/public/js/signup.js new file mode 100644 index 00000000..03490f9c --- /dev/null +++ b/src/public/js/signup.js @@ -0,0 +1,65 @@ +document.querySelector("#registerForm").addEventListener("submit", async function(event) { + event.preventDefault(); + const formData = new FormData(this); + const data = { + name: formData.get("name"), + email: formData.get("email"), + role: formData.get("role"), + password: formData.get("password"), + phone_number: formData.get("phone_number"), + date_of_birth: formData.get("date_of_birth"), + gender: formData.get("gender"), + address: formData.get("address"), + identity_card: formData.get("identity_card"), + additional_info: formData.get("additional_info"), + department: formData.get("department"), + years_of_experience: formData.get("years_of_experience"), + + }; + + // Clear any previous errors + document.querySelectorAll(".error-message").forEach(el => el.remove()); + + try { + const response = await $.ajax({ + url: "/register", + method: "POST", + contentType: "application/json", + data: JSON.stringify(data), + }); + + if (response.status === 201) { + const toastElement = document.getElementById("toastNoAutohideSuccess"); + toastElement.style.removeProperty("display"); + setTimeout(() => { + window.location.href = "/login"; + }, 2000); + } + } catch (error) { + if (error.responseJSON && error.responseJSON.errors) { + // Display each validation error next to the input field + const errors = error.responseJSON.errors; + for (const [field, message] of Object.entries(errors)) { + const inputField = document.querySelector(`#${field}`); + if (inputField) { + const errorElement = document.createElement("div"); + errorElement.className = "error-message text-danger mt-1"; + errorElement.textContent = message; + inputField.parentNode.appendChild(errorElement); + } + } + } else { + // Show a generic toast message if there's no specific error + const toastElement = document.getElementById("toastNoAutohideError"); + toastElement.style.removeProperty("display"); + setTimeout(() => { + toastElement.style.display = "none"; + }, 2000); + } + } +}); +document.querySelector("#role").addEventListener("change", function () { + const isProfessor = this.value === "professor"; + document.querySelector(".department-field").style.display = isProfessor ? "block" : "none"; + document.querySelector(".years-experience-field").style.display = isProfessor ? "block" : "none"; +}); \ No newline at end of file diff --git a/src/public/stylesheets/style.css b/src/public/stylesheets/style.css index 1bcc1b16..ffc3b23f 100644 --- a/src/public/stylesheets/style.css +++ b/src/public/stylesheets/style.css @@ -2027,6 +2027,15 @@ input[type="radio"] { height: fit-content; } +.error-message { + color: #dc3545; + font-size: 0.9em; +} + +.is-invalid { + border-color: #dc3545; +} + .status-overlay { position: absolute; top: 10px; diff --git a/src/service/user.service.ts b/src/service/user.service.ts index 9c240d45..51a584a3 100644 --- a/src/service/user.service.ts +++ b/src/service/user.service.ts @@ -156,4 +156,10 @@ export const updateUserPassword = async (userId: number, newPassword: string): P await userRepository.save(user); return true; -}; \ No newline at end of file +}; + +export function formatFieldName(fieldName: string): string { + return fieldName + .replace(/_/g, " ") + .replace(/\b\w/g, (char) => char.toUpperCase()); +} diff --git a/src/views/layout.pug b/src/views/layout.pug index 7322a31a..954c39c7 100644 --- a/src/views/layout.pug +++ b/src/views/layout.pug @@ -40,5 +40,7 @@ html script(scr='/js/cart.js') script(src='/js/adminCourses.js') script(src='/js/markdone.js') + script(src='/js/signup.js') + script(src='/js/editUserDetails.js') body block content diff --git a/src/views/login.pug b/src/views/login.pug index de904908..fd4af4d2 100644 --- a/src/views/login.pug +++ b/src/views/login.pug @@ -78,10 +78,25 @@ block content }, 2000); } } catch (error) { - const toastElement = document.getElementById("toastNoAutohideError"); - toastElement.style.removeProperty("display"); - setTimeout(() => { - toastElement.style.display = "none"; - }, 2000); + if (error.responseJSON && error.responseJSON.errors) { + // Display each validation error next to the input field + const errors = error.responseJSON.errors; + for (const [field, message] of Object.entries(errors)) { + const inputField = document.querySelector(`#${field}`); + if (inputField) { + const errorElement = document.createElement("div"); + errorElement.className = "error-message text-danger mt-1"; + errorElement.textContent = message; + inputField.parentNode.appendChild(errorElement); + } + } + } else { + // Show a generic toast message if there's no specific error + const toastElement = document.getElementById("toastNoAutohideError"); + toastElement.style.removeProperty("display"); + setTimeout(() => { + toastElement.style.display = "none"; + }, 2000); + } } }); diff --git a/src/views/signup.pug b/src/views/signup.pug index a8029792..800603b4 100644 --- a/src/views/signup.pug +++ b/src/views/signup.pug @@ -89,51 +89,4 @@ block content include partial/footer - script. - document.querySelector("#registerForm").addEventListener("submit", async function(event) { - event.preventDefault(); - const formData = new FormData(this); - const data = { - name: formData.get("name"), - email: formData.get("email"), - role: formData.get("role"), - password: formData.get("password"), - phone_number: formData.get("phone_number"), - date_of_birth: formData.get("date_of_birth"), - gender: formData.get("gender"), - address: formData.get("address"), - identity_card: formData.get("identity_card"), - additional_info: formData.get("additional_info"), - department: formData.get("department"), - years_of_experience: formData.get("years_of_experience"), - }; - - try { - const response = await $.ajax({ - url: "/register", - method: "POST", - contentType: "application/json", - data: JSON.stringify(data), - }); - - if (response.status === 201) { - const toastElement = document.getElementById("toastNoAutohideSuccess"); - toastElement.style.removeProperty("display"); - setTimeout(() => { - window.location.href = "/login"; - }, 2000); - } - } catch (error) { - const toastElement = document.getElementById("toastNoAutohideError"); - toastElement.style.removeProperty("display"); - setTimeout(() => { - toastElement.style.display = "none"; - }, 2000); - } - }); - document.querySelector("#role").addEventListener("change", function () { - const isProfessor = this.value === "professor"; - document.querySelector(".department-field").style.display = isProfessor ? "block" : "none"; - document.querySelector(".years-experience-field").style.display = isProfessor ? "block" : "none"; - }); diff --git a/src/views/user-details.pug b/src/views/user-details.pug index a2797159..9fb3fd32 100644 --- a/src/views/user-details.pug +++ b/src/views/user-details.pug @@ -146,58 +146,65 @@ block content else p.text-center #{t('home.no_course_available')} #editModal.hidden - .modal-content-admin - h3 #{t('admin-user.updateInfo')} - form(action="/account/edit" method="POST") - input(type="hidden" name="id" id="update-id" value=user.id) - .form-group - label(for="name") #{t('admin-user.name')} - input(type="text" name="name" id="update-name" value=user.name required) - .form-group - label(for="phone_number") #{t('admin-user.phone_number')} - input(type="text" name="phone_number" id="update-phone_number" value=user.phone_number required) - .form-group - label(for="date_of_birth") #{t('admin-user.date_of_birth')} - input(type="date" name="date_of_birth" id="update-date_of_birth" value=user.date_of_birth) - .form-group - label(for="gender") #{t('admin-user.gender')} - select(name="gender" id="update-gender" required) - option(value="male" selected=user.gender == 'male') #{t('admin-user.male')} - option(value="female" selected=user.gender == 'female') #{t('admin-user.female')} - .form-group - label(for="address") #{t('admin-user.address')} - input(type="text" name="address" id="update-address" value=user.address) - .form-group - label(for="identity_card") #{t('admin-user.identity_card')} - input(type="text" name="identity_card" id="update-identity_card" value=user.identity_card) - .form-group - label(for="additional_info") #{t('admin-user.additional_info')} - input(type="text" name="additional_info" id="update-additional_info" value=user.additional_info) - if user.role === 'professor' + .modal-content-admin + h3 #{t('admin-user.updateInfo')} + form(action="/account/edit" method="POST" id="update-form") + input(type="hidden" name="id" id="update-id" value=user.id) .form-group - label(for="department") #{t('admin-user.department')} - input(type="text" name="department" id="update-department" value=user.department) + label(for="name") #{t('admin-user.name')} + input(type="text" name="name" id="update-name" value=user.name required class=(errors && errors.name ? 'is-invalid' : '')) + if errors && errors.name + .error-message.text-danger #{errors.name} .form-group - label(for="years_of_experience") #{t('admin-user.years_of_experience')} - input(type="number" name="years_of_experience" id="update-years_of_experience" value=user.years_of_experience) - - .btn-group-admin - .save-btn: button(type="submit") #{t('admin.save')} - .cancel-btn: button(type="button" onclick="hideUpdateForm()") #{t('admin.cancel')} - #resetPasswordModal.hidden - .modal-content-admin - h3 #{t('admin-user.reset_password')} - form(action=`/account/reset-password/${userId}` method="POST" id="reset-password-form") - input(type="hidden" name="userId" value=user.id) + label(for="phone_number") #{t('admin-user.phone_number')} + input(type="text" name="phone_number" id="update-phone_number" value=user.phone_number required class=(errors && errors.phone_number ? 'is-invalid' : '')) + if errors && errors.phone_number + .error-message.text-danger #{errors.phone_number} .form-group - label(for="currentPassword") #{t('admin-user.current_password')} - input(type="password" name="currentPassword" required) + label(for="date_of_birth") #{t('admin-user.date_of_birth')} + input(type="date" name="date_of_birth" id="update-date_of_birth" value=user.date_of_birth class=(errors && errors.date_of_birth ? 'is-invalid' : '')) + if errors && errors.date_of_birth + .error-message.text-danger #{errors.date_of_birth} .form-group - label(for="newPassword") #{t('admin-user.new_password')} - input(type="password" name="newPassword" required) + label(for="gender") #{t('admin-user.gender')} + select(name="gender" id="update-gender" required class=(errors && errors.gender ? 'is-invalid' : '')) + option(value="male" selected=user.gender == 'male') #{t('admin-user.male')} + option(value="female" selected=user.gender == 'female') #{t('admin-user.female')} + if errors && errors.gender + .error-message.text-danger #{errors.gender} .form-group - label(for="confirmPassword") #{t('admin-user.confirm_password')} - input(type="password" name="confirmPassword" required) + label(for="address") #{t('admin-user.address')} + input(type="text" name="address" id="update-address" value=user.address class=(errors && errors.address ? 'is-invalid' : '')) + if errors && errors.address + .error-message.text-danger #{errors.address} + .form-group + label(for="identity_card") #{t('admin-user.identity_card')} + input(type="text" name="identity_card" id="update-identity_card" value=user.identity_card class=(errors && errors.identity_card ? 'is-invalid' : '')) + if errors && errors.identity_card + .error-message.text-danger #{errors.identity_card} + .form-group + label(for="additional_info") #{t('admin-user.additional_info')} + input(type="text" name="additional_info" id="update-additional_info" value=user.additional_info class=(errors && errors.additional_info ? 'is-invalid' : '')) + if errors && errors.additional_info + .error-message.text-danger #{errors.additional_info} .btn-group-admin .save-btn: button(type="submit") #{t('admin.save')} - .cancel-btn: button(type="button" onclick="hideResetPasswordPopup()") #{t('admin.cancel')} + .cancel-btn: button(type="button" onclick="hideUpdateForm()") #{t('admin.cancel')} + + #resetPasswordModal.hidden + .modal-content-admin + h3 #{t('admin-user.reset_password')} + form(action="/account/reset-password" method="POST" id="reset-password-form") + input(type="hidden" name="userId" value=user.id) + .form-group + label(for="currentPassword") #{t('admin-user.current_password')} + input(type="password" name="currentPassword" required) + .form-group + label(for="newPassword") #{t('admin-user.new_password')} + input(type="password" name="newPassword" required) + .form-group + label(for="confirmPassword") #{t('admin-user.confirm_password')} + input(type="password" name="confirmPassword" required) + .btn-group-admin + .save-btn: button(type="submit") #{t('admin.save')} + .cancel-btn: button(type="button" onclick="hideResetPasswordPopup()") #{t('admin.cancel')}