Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export default function TeacherSchedule({ teacher }: TeacherScheduleProps) {
return getDescriptionValidation(description);
}, [description]);

const { wordCount, isValid: isDescriptionValid } = descriptionValidation;
const { characterCount, isValid: isDescriptionValid } = descriptionValidation;

const handleDateSelection = (date: Date): void => {
setSelectedDate(date);
Expand Down Expand Up @@ -325,10 +325,14 @@ export default function TeacherSchedule({ teacher }: TeacherScheduleProps) {

{selectedSubject && selectedLevel && (
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
<label
htmlFor="appointment-description"
className="block text-sm font-medium text-gray-300 mb-2"
>
Description (optional)
</label>
<textarea
id="appointment-description"
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Tell the teacher about your goals, current level, or any specific topics you'd like to focus on..."
Expand All @@ -344,12 +348,13 @@ export default function TeacherSchedule({ teacher }: TeacherScheduleProps) {
<span
className={`text-xs ${isDescriptionValid ? "text-gray-400" : "text-red-400"}`}
>
{wordCount}/{descriptionValidation.maxWords} words
{characterCount}/{descriptionValidation.maxCharacters}{" "}
characters
</span>
{!isDescriptionValid && (
<span className="text-xs text-red-400">
Maximum {descriptionValidation.maxWords} words
allowed
Maximum {descriptionValidation.maxCharacters}{" "}
characters allowed
</span>
)}
</div>
Expand All @@ -373,7 +378,7 @@ export default function TeacherSchedule({ teacher }: TeacherScheduleProps) {
: isTeacher
? "Teachers cannot book lessons"
: !isDescriptionValid
? `Please limit description to ${descriptionValidation.maxWords} words`
? `Please limit description to ${descriptionValidation.maxCharacters} characters`
: "Book Now"}
</Button>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { removeRegularStudentApi } from "../../../api/appointments/regularStudents.api";
import { queryKeys } from "../../queryKeys";
import { useNotificationStore } from "../../../store/notification.store";

export const useRemoveRegularStudentMutation = () => {
const queryClient = useQueryClient();
const success = useNotificationStore((s) => s.success);

return useMutation({
mutationFn: (appointmentId: string) =>
Expand All @@ -21,6 +23,7 @@ export const useRemoveRegularStudentMutation = () => {
queryClient.invalidateQueries({
queryKey: ["appointments", "teacher"],
});
success("Student removed from regular list successfully!");
},
onError: (error: Error) => {
console.error("Failed to remove regular student:", error);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { setRegularStudentApi } from "../../../api/appointments/regularStudents.api";
import { queryKeys } from "../../queryKeys";
import { useNotificationStore } from "../../../store/notification.store";

export const useSetRegularStudentMutation = () => {
const queryClient = useQueryClient();
const success = useNotificationStore((s) => s.success);

return useMutation({
mutationFn: (appointmentId: string) => setRegularStudentApi(appointmentId),
Expand All @@ -20,6 +22,7 @@ export const useSetRegularStudentMutation = () => {
queryClient.invalidateQueries({
queryKey: ["appointments", "teacher"],
});
success("Student added to regular list successfully!");
},
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
import { updateWeeklyScheduleApi } from "../../../api/appointments/regularStudents.api";
import { queryKeys } from "../../queryKeys";
import { WeeklyScheduleSlot } from "../../../types/appointments.types";
import { useNotificationStore } from "../../../store/notification.store";

export const useUpdateWeeklyScheduleMutation = () => {
const queryClient = useQueryClient();
const success = useNotificationStore((s) => s.success);

return useMutation({
mutationFn: ({
Expand All @@ -27,6 +29,7 @@ export const useUpdateWeeklyScheduleMutation = () => {
queryClient.invalidateQueries({
queryKey: ["appointments", "teacher"],
});
success("Weekly schedule updated successfully!");
},
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
import { updateMyStudentProfile } from "../../../api/student/student.api";
import { UpdateStudentProfile } from "../../../api/student/student.type";
import { queryKeys } from "../../queryKeys";
import { useNotificationStore } from "../../../store/notification.store";

export const useUpdateMyStudentProfileMutation = () => {
const queryClient = useQueryClient();
const success = useNotificationStore((s) => s.success);

return useMutation({
mutationFn: (data: UpdateStudentProfile) => updateMyStudentProfile(data),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: queryKeys.students.myProfile(),
});
success("Profile updated successfully!");
},
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
import { updateMyProfileApi } from "../../../api/teacher/teacher.api";
import { UpdateTeacherProfileInput } from "../../../api/teacher/teacher.type";
import { queryKeys } from "../../queryKeys";
import { useNotificationStore } from "../../../store/notification.store";

export const useUpdateMyProfileMutation = () => {
const queryClient = useQueryClient();
const success = useNotificationStore((s) => s.success);

return useMutation({
mutationFn: (data: UpdateTeacherProfileInput) => updateMyProfileApi(data),
Expand All @@ -16,6 +18,7 @@ export const useUpdateMyProfileMutation = () => {
queryClient.invalidateQueries({
queryKey: queryKeys.teacher(updatedTeacher.id),
});
success("Profile updated successfully!");
},
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,6 @@ export const TeacherAppointments = () => {

setIsRegularStudentModalOpen(false);
setSelectedStudent(null);

openModal("alert", {
title: "Success",
message: "Schedule updated successfully",
});
} catch (error) {
const axiosError = error as {
response?: { data?: { errorsMessages?: Array<{ message: string }> } };
Expand Down
20 changes: 6 additions & 14 deletions client/src/utils/appointmentDescription.validation.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,16 @@
export const MAX_DESCRIPTION_WORDS = 50;

export const countWords = (text: string): number => {
return text
.trim()
.split(/\s+/)
.filter((word) => word.length > 0).length;
};
export const MAX_DESCRIPTION_CHARACTERS = 500;

export const isDescriptionValid = (description: string): boolean => {
const wordCount = countWords(description);
return wordCount <= MAX_DESCRIPTION_WORDS;
return description.trim().length <= MAX_DESCRIPTION_CHARACTERS;
};

export const getDescriptionValidation = (description: string) => {
const wordCount = countWords(description);
const isValid = wordCount <= MAX_DESCRIPTION_WORDS;
const characterCount = description.trim().length;
const isValid = characterCount <= MAX_DESCRIPTION_CHARACTERS;

return {
wordCount,
characterCount,
isValid,
maxWords: MAX_DESCRIPTION_WORDS,
maxCharacters: MAX_DESCRIPTION_CHARACTERS,
};
};
1 change: 1 addition & 0 deletions server/src/constants/validation.constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const MAX_DESCRIPTION_CHARACTERS = 500;
126 changes: 26 additions & 100 deletions server/src/controllers/appointment.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import {
UpdateAppointmentStatusType,
UpdateWeeklyScheduleType,
} from "../types/appointment/appointment.types.js";
import {
validatePaginationParams,
validateAuthorization,
} from "../utils/validation/requestValidation.util.js";

@injectable()
export class AppointmentController {
Expand Down Expand Up @@ -64,23 +68,10 @@ export class AppointmentController {
next: NextFunction,
) {
try {
let page: number | undefined;
let limit: number | undefined;

if (req.query.page) {
page = parseInt(req.query.page, 10);
if (isNaN(page)) {
return res.status(400).json({ message: "Invalid page parameter" });
}
}

if (req.query.limit) {
limit = parseInt(req.query.limit, 10);
if (isNaN(limit)) {
return res.status(400).json({ message: "Invalid limit parameter" });
}
}

const { page, limit } = validatePaginationParams(
req.query.page,
req.query.limit,
);
const result = await this.appointmentService.getAppointmentsByStudent(
req.params.studentId,
page,
Expand All @@ -99,23 +90,10 @@ export class AppointmentController {
next: NextFunction,
) {
try {
let page: number | undefined;
let limit: number | undefined;

if (req.query.page) {
page = parseInt(req.query.page, 10);
if (isNaN(page)) {
return res.status(400).json({ message: "Invalid page parameter" });
}
}

if (req.query.limit) {
limit = parseInt(req.query.limit, 10);
if (isNaN(limit)) {
return res.status(400).json({ message: "Invalid limit parameter" });
}
}

const { page, limit } = validatePaginationParams(
req.query.page,
req.query.limit,
);
const result = await this.appointmentService.getAppointmentsByTeacher(
req.params.teacherId,
page,
Expand Down Expand Up @@ -171,11 +149,7 @@ export class AppointmentController {
next: NextFunction,
) {
try {
const userId = req.auth?.userId;
if (!userId) {
return res.status(401).json({ message: "Unauthorized" });
}

const userId = validateAuthorization(req.auth?.userId);
await this.appointmentService.deleteAppointment(req.params.id, userId);
return res.status(204).send();
} catch (error) {
Expand All @@ -189,11 +163,7 @@ export class AppointmentController {
next: NextFunction,
) {
try {
const teacherId = req.auth?.userId;
if (!teacherId) {
return res.status(401).json({ message: "Unauthorized" });
}

const teacherId = validateAuthorization(req.auth?.userId);
const appointment = await this.appointmentService.setRegularStudent(
req.params.id,
teacherId,
Expand All @@ -215,11 +185,7 @@ export class AppointmentController {
next: NextFunction,
) {
try {
const teacherId = req.auth?.userId;
if (!teacherId) {
return res.status(401).json({ message: "Unauthorized" });
}

const teacherId = validateAuthorization(req.auth?.userId);
const appointment = await this.appointmentService.removeRegularStudent(
req.params.id,
teacherId,
Expand All @@ -242,11 +208,7 @@ export class AppointmentController {
next: NextFunction,
) {
try {
const teacherId = req.auth?.userId;
if (!teacherId) {
return res.status(401).json({ message: "Unauthorized" });
}

const teacherId = validateAuthorization(req.auth?.userId);
const appointment = await this.appointmentService.updateWeeklySchedule(
req.params.id,
teacherId,
Expand All @@ -269,34 +231,16 @@ export class AppointmentController {
next: NextFunction,
) {
try {
const teacherId = req.auth?.userId;
if (!teacherId) {
return res.status(401).json({ message: "Unauthorized" });
}

let page: number | undefined;
let limit: number | undefined;

if (req.query.page) {
page = parseInt(req.query.page, 10);
if (isNaN(page)) {
return res.status(400).json({ message: "Invalid page parameter" });
}
}

if (req.query.limit) {
limit = parseInt(req.query.limit, 10);
if (isNaN(limit)) {
return res.status(400).json({ message: "Invalid limit parameter" });
}
}

const teacherId = validateAuthorization(req.auth?.userId);
const { page, limit } = validatePaginationParams(
req.query.page,
req.query.limit,
);
const result = await this.appointmentService.getRegularStudentsByTeacher(
teacherId,
page,
limit,
);

res.json(result);
} catch (error) {
return next(error);
Expand All @@ -309,34 +253,16 @@ export class AppointmentController {
next: NextFunction,
) {
try {
const studentId = req.auth?.userId;
if (!studentId) {
return res.status(401).json({ message: "Unauthorized" });
}

let page: number | undefined;
let limit: number | undefined;

if (req.query.page) {
page = parseInt(req.query.page, 10);
if (isNaN(page)) {
return res.status(400).json({ message: "Invalid page parameter" });
}
}

if (req.query.limit) {
limit = parseInt(req.query.limit, 10);
if (isNaN(limit)) {
return res.status(400).json({ message: "Invalid limit parameter" });
}
}

const studentId = validateAuthorization(req.auth?.userId);
const { page, limit } = validatePaginationParams(
req.query.page,
req.query.limit,
);
const result = await this.appointmentService.getRegularTeachersByStudent(
studentId,
page,
limit,
);

res.json(result);
} catch (error) {
return next(error);
Expand Down
Loading
Loading