Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add additional validation to various forms #465

Merged
merged 9 commits into from
Feb 29, 2024
84 changes: 75 additions & 9 deletions csm_web/frontend/src/components/course/CreateSectionModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";

import { DAYS_OF_WEEK } from "../../utils/datetime";
import { useUserEmails } from "../../utils/queries/base";
Expand All @@ -7,6 +7,8 @@ import { Spacetime } from "../../utils/types";
import Modal from "../Modal";
import TimeInput from "../TimeInput";

import ExclamationCircle from "../../../static/frontend/img/exclamation-circle.svg";

const makeSpacetime = (): Spacetime => {
return { id: -1, duration: 0, dayOfWeek: 1, startTime: "", location: "" };
};
Expand Down Expand Up @@ -45,7 +47,21 @@ export const CreateSectionModal = ({ courseId, closeModal, reloadSections }: Cre
/**
* Capacity for the new section.
*/
const [capacity, setCapacity] = useState<string>("");
const [capacity, setCapacity] = useState<number>(0);

/**
* Validation text; if empty string, no validation text is displayed.
*/
const [validationText, setValidationText] = useState<string>("");

/**
* Automatically re-validate form if there was a previous validation error.
*/
useEffect(() => {
if (validationText !== "") {
validateSectionForm();
}
}, [mentorEmail, spacetimes, description, capacity]);

/**
* Create a new empty spacetime for the new section.
Expand All @@ -55,6 +71,44 @@ export const CreateSectionModal = ({ courseId, closeModal, reloadSections }: Cre
setSpacetimes(oldSpacetimes => [...oldSpacetimes, makeSpacetime()]);
};

/**
* Validate current spacetime values.
*/
const validateSectionForm = (): boolean => {
// all fields must be filled out
if (mentorEmail === null || mentorEmail === "") {
setValidationText("Mentor email must not be blank");
return false;
} else if (spacetimes.length === 0) {
setValidationText("Must have at least one section time");
return false;
} else if (isNaN(capacity)) {
setValidationText("Capacity must not be blank");
return false;
}

// validate spacetime fields
for (const spacetime of spacetimes) {
if (spacetime.location === null || spacetime.location === undefined || spacetime.location === "") {
setValidationText("All section locations must be specified");
return false;
} else if (spacetime.dayOfWeek <= 0) {
setValidationText("All section occurrences must have a specified day of week");
return false;
} else if (spacetime.startTime === "") {
setValidationText("All section occurrences must have a specified start time");
return false;
} else if (isNaN(spacetime.duration) || spacetime.duration <= 0) {
setValidationText("All section occurrences must have duration greater than 0");
return false;
}
}

// all valid
setValidationText("");
return true;
};

/**
* Handle the change of a form field.
*/
Expand All @@ -79,7 +133,7 @@ export const CreateSectionModal = ({ courseId, closeModal, reloadSections }: Cre
setDescription(value);
break;
case "capacity":
setCapacity(value);
setCapacity(parseInt(value));
break;
default:
console.error("Unknown input name: " + name);
Expand All @@ -93,6 +147,12 @@ export const CreateSectionModal = ({ courseId, closeModal, reloadSections }: Cre
*/
const handleSubmit = (event: React.MouseEvent<HTMLButtonElement>): void => {
event.preventDefault();

if (!validateSectionForm()) {
// don't do anything if invalid
return;
}

const data = {
mentorEmail,
spacetimes,
Expand Down Expand Up @@ -141,8 +201,8 @@ export const CreateSectionModal = ({ courseId, closeModal, reloadSections }: Cre
type="number"
min="0"
inputMode="numeric"
pattern="[0-9]*"
value={capacity}
pattern="[0-9]+"
value={isNaN(capacity) ? "" : capacity.toString()}
onChange={e => handleChange(-1, "capacity", e.target.value)}
/>
</label>
Expand Down Expand Up @@ -180,11 +240,11 @@ export const CreateSectionModal = ({ courseId, closeModal, reloadSections }: Cre
className="form-select"
onChange={e => handleChange(index, "dayOfWeek", e.target.value)}
name={`dayOfWeek|${index}`}
value={dayOfWeek}
value={dayOfWeek.toString()}
required
>
{[["---", ""], ...Array.from(DAYS_OF_WEEK)].map(([label, value]) => (
<option key={value} value={value} disabled={value === "---"}>
{[["---", -1], ...Array.from(DAYS_OF_WEEK)].map(([label, value]) => (
<option key={value} value={value} disabled={label === "---"}>
{label}
</option>
))}
Expand All @@ -206,7 +266,7 @@ export const CreateSectionModal = ({ courseId, closeModal, reloadSections }: Cre
className="form-input"
type="number"
name={`duration|${index}`}
value={duration}
value={isNaN(duration) ? "" : duration.toString()}
min={0}
onChange={e => handleChange(index, "duration", e.target.value)}
/>
Expand All @@ -217,6 +277,12 @@ export const CreateSectionModal = ({ courseId, closeModal, reloadSections }: Cre
</div>
</form>
<div className="create-section-submit-container">
{validationText !== "" && (
<div className="create-section-validation-text-container">
<ExclamationCircle className="icon outline" />
<span className="create-section-validation-text">{validationText}</span>
</div>
)}
<button className="secondary-btn" id="add-occurence-btn" onClick={appendSpacetime}>
Add another occurence
</button>
Expand Down
84 changes: 75 additions & 9 deletions csm_web/frontend/src/components/section/SpacetimeEditModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DateTime } from "luxon";
import React, { useState } from "react";
import React, { useEffect, useState } from "react";

import { DAYS_OF_WEEK } from "../../utils/datetime";
import { useSpacetimeModifyMutation, useSpacetimeOverrideMutation } from "../../utils/queries/spacetime";
Expand All @@ -8,6 +8,8 @@ import LoadingSpinner from "../LoadingSpinner";
import Modal from "../Modal";
import TimeInput from "../TimeInput";

import ExclamationCircle from "../../../static/frontend/img/exclamation-circle.svg";

import "../../css/spacetime-edit.scss";

interface SpacetimeEditModalProps {
Expand All @@ -30,26 +32,84 @@ const SpaceTimeEditModal = ({
const [date, setDate] = useState<string>("");
const [mode, setMode] = useState<string>(prevLocation && prevLocation.startsWith("http") ? "virtual" : "inperson");
const [showSaveSpinner, setShowSaveSpinner] = useState<boolean>(false);
const [validationText, setValidationText] = useState<string>("");

const spacetimeModifyMutation = useSpacetimeModifyMutation(sectionId, spacetimeId);
const spacetimeOverrideMutation = useSpacetimeOverrideMutation(sectionId, spacetimeId);

useEffect(() => {
if (validationText !== "") {
validateSpacetime();
}
}, [location, day, time, date, isPermanent]);

/**
* Validate current spacetime values.
*/
const validateSpacetime = (): boolean => {
// validate spacetime fields
if (location === null || location === undefined || location.length === 0) {
setValidationText("All section locations must be specified");
return false;
} else if (isPermanent && day <= 0) {
// only check this if it's for permanent changes
setValidationText("All section occurrences must have a specified day of week");
return false;
} else if (time === "") {
setValidationText("All section occurrences must have a specified start time");
return false;
}

if (!isPermanent && (date === null || date === "")) {
setValidationText("Section date to override must be specified");
return false;
}

// all valid
setValidationText("");
return true;
};

const handleSubmit = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
//TODO: Handle API failure

if (!validateSpacetime()) {
// don't do anythinng if invalid
return;
}

setShowSaveSpinner(true);
isPermanent
? spacetimeModifyMutation.mutate({
if (isPermanent) {
spacetimeModifyMutation.mutate(
{
dayOfWeek: day,
location: location,
startTime: time
})
: spacetimeOverrideMutation.mutate({
},
{
onSuccess: closeModal,
onError: () => {
setValidationText("Error occurred on save");
setShowSaveSpinner(false);
}
}
);
} else {
spacetimeOverrideMutation.mutate(
{
location: location,
startTime: time,
date: date
});
closeModal();
},
{
onSuccess: closeModal,
onError: () => {
setValidationText("Error occurred on save");
setShowSaveSpinner(false);
}
}
);
}
};

const today = DateTime.now().toISODate()!;
Expand Down Expand Up @@ -113,7 +173,7 @@ const SpaceTimeEditModal = ({
disabled={!isPermanent}
value={isPermanent ? day : "---"}
>
{[["---", ""], ...Array.from(DAYS_OF_WEEK)].map(([label, value]) => (
{[["---", -1], ...Array.from(DAYS_OF_WEEK)].map(([label, value]) => (
<option key={value} value={value} disabled={value === "---"}>
{label}
</option>
Expand Down Expand Up @@ -186,6 +246,12 @@ const SpaceTimeEditModal = ({
)}
</div>
<div className="form-actions">
{validationText !== "" && (
<div className="spacetime-edit-form-validation-container">
<ExclamationCircle className="icon" />
<span className="spacetime-edit-form-validation-text">{validationText}</span>
</div>
)}
{showSaveSpinner ? (
<LoadingSpinner />
) : (
Expand Down
12 changes: 10 additions & 2 deletions csm_web/frontend/src/css/course.scss
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,7 @@
grid-template-rows: repeat(auto-fit, 30px);
grid-template-columns: repeat(3, 1fr);
gap: 1%;
align-items: center;
justify-items: center;
place-items: center center;
width: 100%;
height: 50%;
}
Expand Down Expand Up @@ -276,3 +275,12 @@ $create-section-modal-height: 65vh;
#create-section-form .create-section-submit-container {
grid-area: submit;
}

.create-section-validation-text-container {
margin-bottom: 8px;
color: red;
}

.create-section-validation-text {
margin-left: 4px;
}
10 changes: 10 additions & 0 deletions csm_web/frontend/src/css/spacetime-edit.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,13 @@
align-items: flex-start;
justify-content: space-between;
}

.spacetime-edit-form-validation-container {
margin-bottom: 8px;
color: red;
}

.spacetime-edit-form-validation-text {
margin-left: 4px;
color: red;
}
2 changes: 1 addition & 1 deletion csm_web/frontend/src/utils/queries/sections.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@
progress?: Array<{
email: string;
status: string;
detail?: any;

Check warning on line 403 in csm_web/frontend/src/utils/queries/sections.tsx

View workflow job for this annotation

GitHub Actions / ESLint

csm_web/frontend/src/utils/queries/sections.tsx#L403

Unexpected any. Specify a different type (@typescript-eslint/no-explicit-any)
}>;
};
}
Expand Down Expand Up @@ -442,7 +442,7 @@
mentorEmail: string;
spacetimes: Spacetime[];
description: string;
capacity: string;
capacity: number;
courseId: number;
}

Expand Down
Loading
Loading