diff --git a/src/api/index.ts b/src/api/index.ts index 4992582..8006e31 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,3 +1,6 @@ +/** + * API client for making HTTP requests. + */ import Axios from "axios"; export const apiClient = Axios.create({ diff --git a/src/api/meetings.ts b/src/api/meetings.ts index db8e6c0..c70a7ce 100644 --- a/src/api/meetings.ts +++ b/src/api/meetings.ts @@ -1,6 +1,11 @@ + import { CreateMeetingInput, Meeting } from "@/types/Meeting"; import { apiClient } from "."; +/** + * Retrieves future meetings from the server. + * @returns An array of Meeting objects representing future meetings. + */ export async function getFutureMeetings(): Promise { const response = await apiClient.get<{ meetings: Meeting[] }>("/meetings"); @@ -11,6 +16,11 @@ export async function getFutureMeetings(): Promise { })); } +/** + * Retrieves previous meetings from the server that occurred before the specified date. + * @param before The date before which the meetings should have occurred. + * @returns An array of Meeting objects representing previous meetings. + */ export async function getPreviousMeetings(before: Date): Promise { const response = await apiClient.get<{ meetings: Meeting[] }>( `/meetings?before=${before.toISOString()}` @@ -22,6 +32,11 @@ export async function getPreviousMeetings(before: Date): Promise { })); } +/** + * Creates a new meeting on the server. + * @param data The input data for creating the meeting. + * @returns The created Meeting object. + */ export async function createMeeting( data: CreateMeetingInput ): Promise { @@ -29,10 +44,21 @@ export async function createMeeting( return response.data; } +/** + * Deletes a meeting from the server. + * @param id The ID of the meeting to delete. + * @returns A promise that resolves when the meeting is successfully deleted. + */ export async function deleteMeeting(id: number): Promise { await apiClient.delete(`/meetings/${id}`); } +/** + * Retrieves a specific meeting from the server. + * @param id The ID of the meeting to retrieve. + * @returns The Meeting object representing the retrieved meeting. + * @throws An error if the retrieved data is invalid. + */ export async function getMeeting(id: number): Promise { const response = await apiClient.get(`/meetings/${id}`); @@ -44,6 +70,12 @@ export async function getMeeting(id: number): Promise { return response.data; } +/** + * Updates a meeting on the server. + * @param id The ID of the meeting to update. + * @param data The partial input data for updating the meeting. + * @returns The updated Meeting object. + */ export async function updateMeeting( id: number, data: Partial diff --git a/src/components/base/button/button.tsx b/src/components/base/button/button.tsx index c97537d..ebd99ec 100644 --- a/src/components/base/button/button.tsx +++ b/src/components/base/button/button.tsx @@ -1,5 +1,4 @@ import classNames from "classnames"; -import { FC, ReactElement, forwardRef } from "react"; import { type LucideIcon } from "lucide-react"; import { Link, LinkProps, To } from "react-router-dom"; @@ -23,6 +22,7 @@ type ButtonPropsAsLink = BaseButtonProps & type ButtonProps = ButtonPropsAsLink | (ButtonPropsAsButton & { to?: never }); + export function Button({ className, children, @@ -55,6 +55,7 @@ export function Button({ className ); + // If the button is a link, use the Link component from react-router-dom if (props.to) { const propsAsLink = props as ButtonPropsAsLink; diff --git a/src/components/base/title/title.tsx b/src/components/base/title/title.tsx index 76399ef..23cc5df 100644 --- a/src/components/base/title/title.tsx +++ b/src/components/base/title/title.tsx @@ -16,6 +16,7 @@ export const Title = forwardRef( "text-blue-600 text-2xl font-semibold inline-grid grid-flow-col gap-2 items-center" )} > + {/* If the button has an icon props, render it */} {Icon && ( ( ({ id, label, error, className, ...props }, ref) => { return (
+ {/* If the button has a label props, render it */} {label && ( )}
+ {/* The input element is wrapped in a div to allow the icon to be placed on the left */} ( )} /> + {/* If the button has an icon props, render it */}
( {props.icon}
+ {/* If the button has an error props, render it */} {error && ( {error} diff --git a/src/components/form/inputTags/inputTags.tsx b/src/components/form/inputTags/inputTags.tsx index 45b60d2..f6b011b 100644 --- a/src/components/form/inputTags/inputTags.tsx +++ b/src/components/form/inputTags/inputTags.tsx @@ -27,6 +27,7 @@ export const InputTags = forwardRef( ): JSX.Element => { return (
+ {/* If the input tags has a label props, render it */} {label && ( )}
+ {/* We're using the react-multi-email library to handle the mails input */} ( onChange={props.onChange} emails={props.value} getLabel={(email, index, removeEmail) => ( + // To be able to customize the input, we're using our own TagElement component ( /> )} /> + {/* If the input tags has an icon props, render it */}
( {props.icon}
+ {/* If the input tags has an error props, render it */} {error && ( {error} diff --git a/src/components/form/select/select.tsx b/src/components/form/select/select.tsx index 2c6ae16..ba61410 100644 --- a/src/components/form/select/select.tsx +++ b/src/components/form/select/select.tsx @@ -12,6 +12,7 @@ export type SelectProps = { export const Select = forwardRef(({ id, label, error, ...props }, ref) => { return (
+ {/* If the select has a label props, render it */} {label && (
+ {/* If the select has an error props, render it */} {error && {error}}
); diff --git a/src/components/form/textarea/textarea.tsx b/src/components/form/textarea/textarea.tsx index 03e34e5..c3ff1fc 100644 --- a/src/components/form/textarea/textarea.tsx +++ b/src/components/form/textarea/textarea.tsx @@ -13,6 +13,7 @@ export const Textarea = forwardRef( ({ id, label, error, ...props }, ref) => { return (
+ {/* If the textarea has a label props, render it */} {label && (
+ {/* If the textarea has an error props, render it */} {error && {error}} ); diff --git a/src/components/meeting/MeetingCard.tsx b/src/components/meeting/MeetingCard.tsx index 5e26984..14a5123 100644 --- a/src/components/meeting/MeetingCard.tsx +++ b/src/components/meeting/MeetingCard.tsx @@ -56,6 +56,7 @@ export function MeetingCard({ + {/* Is the meeting isn't a previous meeting, then show the actions buttons */} {!isPrevious && (
diff --git a/src/components/meeting/MeetingInfoModal.tsx b/src/components/meeting/MeetingInfoModal.tsx index 7becafa..0fd4c19 100644 --- a/src/components/meeting/MeetingInfoModal.tsx +++ b/src/components/meeting/MeetingInfoModal.tsx @@ -67,8 +67,6 @@ export const MeetingInfoModal = forwardRef<
- - diff --git a/src/hooks/useFetchMeetings.tsx b/src/hooks/useFetchMeetings.tsx index 7485d28..bd87f0c 100644 --- a/src/hooks/useFetchMeetings.tsx +++ b/src/hooks/useFetchMeetings.tsx @@ -3,6 +3,10 @@ import { Meeting } from "@/types/Meeting"; import { sortMeetings } from "@/utils/date"; import { useCallback, useEffect, useState } from "react"; +/** + * Custom hook for fetching meetings data. + * @returns An object containing functions and state variables related to fetching meetings. + */ export default function useFetchMeetings() { const [loading, setLoading] = useState(false); const [data, setData] = useState(undefined); @@ -12,6 +16,10 @@ export default function useFetchMeetings() { undefined ); + /** + * Fetches future meetings from the server and updates the component state. + * @returns {Promise} A promise that resolves when the fetch is complete. + */ const fetchFutureMeetings = useCallback(async () => { console.debug("Fetching future meetings"); setLoading(true); @@ -32,6 +40,12 @@ export default function useFetchMeetings() { } }, [setData, setError]); + /** + * Fetches previous meetings and updates the state with the fetched data. + * If there are no previous meetings, it fetches meetings until the current date. + * + * @returns {void} + */ const fetchPreviousMeetings = useCallback(async () => { console.debug("Fetching previous meetings"); setLoading(true); @@ -64,10 +78,15 @@ export default function useFetchMeetings() { } }, [setPreviousData, setError]); + // Fetch future meetings on component mount useEffect(() => { fetchFutureMeetings(); }, []); + + /** + * Refreshes the meeting list by resetting the data and error states, and then fetching future meetings. + */ const refreshMeetingList = useCallback(async () => { setData(undefined); setError(undefined); diff --git a/src/hooks/useMeetingDeleteModal.tsx b/src/hooks/useMeetingDeleteModal.tsx index acd4d34..b94d0a4 100644 --- a/src/hooks/useMeetingDeleteModal.tsx +++ b/src/hooks/useMeetingDeleteModal.tsx @@ -3,6 +3,11 @@ import { MeetingDeleteModal } from "@/components/meeting/MeetingDeleteModal"; import { Meeting } from "@/types/Meeting"; import { useCallback, useRef, useState } from "react"; +/** + * Custom hook for managing the meeting delete modal. + * @param deleteCallback - Optional callback function to be executed after a meeting is deleted. + * @returns An object containing the function to show the meeting delete modal and the JSX element for the modal. + */ export default function useMeetingDeleteModal({ deleteCallback, }: { @@ -14,6 +19,11 @@ export default function useMeetingDeleteModal({ const deleteMeetingModalRef = useRef(null); + /** + * Show the deletion modal of the meeting. + * + * @param meeting The meeting to delete. + */ const showMeetingDeleteModal = useCallback( async (meeting: Meeting) => { const meetingData = await getMeeting(meeting.id); diff --git a/src/hooks/useMeetingInfoModal.tsx b/src/hooks/useMeetingInfoModal.tsx index 2101e87..586e4c1 100644 --- a/src/hooks/useMeetingInfoModal.tsx +++ b/src/hooks/useMeetingInfoModal.tsx @@ -3,17 +3,28 @@ import { MeetingInfoModal } from "@/components/meeting/MeetingInfoModal"; import { Meeting } from "@/types/Meeting"; import { useCallback, useRef, useState } from "react"; +/** + * Custom hook for managing the meeting info modal. + * This hook provides functions and state variables to show and manage the meeting info modal. + * + * @returns An object containing the following properties: + * - showMeetingInfoModal: A function to show the meeting info modal for a specific meeting. + * - meetingInfoModal: The JSX element representing the meeting info modal. + */ export default function useMeetingInfoModal() { + // State variable to store the currently selected meeting const [modalMeeting, setModalMeeting] = useState( undefined ); + // Ref to the dialog element used to show the meeting info modal const showMeetingModalRef = useRef(null); + // Function to show the meeting info modal for a specific meeting const showMeetingInfoModal = useCallback( async (meeting: Meeting) => { - const meetingData = await getMeeting(meeting.id); // Fetch meeting data + const meetingData = await getMeeting(meeting.id); console.debug("Show meeting %O", meetingData); setModalMeeting(meetingData); showMeetingModalRef.current?.showModal(); @@ -21,6 +32,7 @@ export default function useMeetingInfoModal() { [setModalMeeting, showMeetingModalRef] ); + // JSX element representing the meeting info modal const meetingInfoModal = ( ); diff --git a/src/hooks/useMeetingUpdateModal.tsx b/src/hooks/useMeetingUpdateModal.tsx index d53444c..18073d3 100644 --- a/src/hooks/useMeetingUpdateModal.tsx +++ b/src/hooks/useMeetingUpdateModal.tsx @@ -3,6 +3,12 @@ import { MeetingUpdateModal } from "@/components/meeting/MeetingUpdateModal"; import { Meeting } from "@/types/Meeting"; import { useCallback, useRef, useState } from "react"; +/** + * Custom hook for managing the meeting update modal. + * @param options - The options for the hook. + * @param options.updateCallback - The callback function to be called after a meeting is updated. + * @returns An object containing the function to show the meeting update modal and the JSX element for the modal. + */ export default function useMeetingUpdateModal({ updateCallback, }: { diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx deleted file mode 100644 index 8a85d70..0000000 --- a/src/pages/Login.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { Link } from "react-router-dom"; - -export default function LoginPage() { - return
-

It's a prank man ;)

- Back -
-} \ No newline at end of file diff --git a/src/pages/meetings/CreateForm.tsx b/src/pages/meetings/CreateForm.tsx index cbc6d4c..ebf1bd8 100644 --- a/src/pages/meetings/CreateForm.tsx +++ b/src/pages/meetings/CreateForm.tsx @@ -56,6 +56,12 @@ export function MeetingCreateForm({ const endDate = watch("end_date", "Invalid Date"); const now = new Date(); + /** + * Handles the form submission for creating a meeting. + * + * @param data - The form inputs. + * @returns A Promise that resolves when the meeting is created. + */ const onSubmit: SubmitHandler = async (data) => { setLoading(true); @@ -74,6 +80,13 @@ export function MeetingCreateForm({ setLoading(false); }; + /** + * Updates the "end_date" value based on the start date and end date. + * If the start date or end date is invalid, the function returns early. + * If the minimal end date is after the current end date, the end date is updated. + * @param startDate The start date of the meeting. + * @param endDate The end date of the meeting. + */ useEffect(() => { if (startDate === "Invalid Date" || endDate === "Invalid Date") return; diff --git a/src/pages/meetings/MeetingPage.tsx b/src/pages/meetings/MeetingPage.tsx index dd5c386..09b3943 100644 --- a/src/pages/meetings/MeetingPage.tsx +++ b/src/pages/meetings/MeetingPage.tsx @@ -41,7 +41,7 @@ export function MeetingPage({}): JSX.Element { } else if (error instanceof Error) { setError(error.message); } else { - setError("An unknown error occured"); + setError("An unknown error occurred"); } } }, []); @@ -54,23 +54,36 @@ export function MeetingPage({}): JSX.Element { const eventSource = new EventSource(`/api/meetings/${meetingId}/events`); eventSource.onmessage = (event) => { + /** + * Handles the event received from the server. + * @param event - The event received from the server. + */ console.log("Event received", event); const data = JSON.parse(event.data) as MeetingEvent; console.log("Event data", data); switch (data.type) { - case 'cancelled': - if (data.id !== meetingId) return; - setError("Meeting cancelled"); - break; - case 'started': - console.log("Meeting started", data.id, meetingId); - if (data.id !== meetingId) return; - window.location.href = data.url - break; - case 'updated': - if (data.id !== meetingId) return; - setMeeting(JSON.parse(event.data)); - break; + /** + * Handles the case when the meeting is cancelled. + */ + case 'cancelled': + if (data.id !== meetingId) return; + setError("Meeting cancelled"); + break; + /** + * Handles the case when the meeting is started. + */ + case 'started': + console.log("Meeting started", data.id, meetingId); + if (data.id !== meetingId) return; + window.location.href = data.url + break; + /** + * Handles the case when the meeting is updated. + */ + case 'updated': + if (data.id !== meetingId) return; + setMeeting(JSON.parse(event.data)); + break; } }; @@ -80,6 +93,10 @@ export function MeetingPage({}): JSX.Element { } }, [router]); + /** + * Executes the fetchMeeting and listenEvents functions when the component mounts. + * @returns {void} + */ useEffect(() => { fetchMeeting(); listenEvents(); diff --git a/src/utils/date.ts b/src/utils/date.ts index 5855ca7..ea81306 100644 --- a/src/utils/date.ts +++ b/src/utils/date.ts @@ -1,12 +1,26 @@ import { Meeting } from "@/types/Meeting"; import { isToday, isTomorrow, format, isBefore } from "date-fns"; +/** + * Sets the time of the given date object to the specified hours and minutes. + * @param date - The date object to set the time on. + * @param hours - The hours to set. + * @param minutes - The minutes to set. + * @returns The modified date object with the updated time. + */ export function setTime(date: Date, hours: number, minutes: number): Date { date.setHours(hours); date.setMinutes(minutes); return date; } +/** + * Formats the start and end dates into a string representation. + * + * @param start - The start date. + * @param end - The end date. + * @returns The formatted string representation of the dates. + */ export function formatDate(start: Date, end: Date): string { let startFormat = ""; let endFormat = ""; @@ -34,10 +48,21 @@ export function formatDate(start: Date, end: Date): string { return startFormat; } +/** + * Formats a Date object to a string representation suitable for an input value. + * The format is "yyyy-MM-dd'T'HH:mm". + * + * @param date - The Date object to format. + * @returns The formatted string representation of the date. + */ export function formatDatetimeToInputValue(date: Date): string { return format(date, "yyyy-MM-dd'T'HH:mm"); } +/** + * Sorts an array of meetings in ascending order based on their start and end dates. + * @param meetings - The array of meetings to be sorted. + */ export function sortMeetings(meetings: Meeting[]) { meetings.sort((m1, m2) => { // If m1 start date is before m2 start date, return -1