diff --git a/backend/src/app.ts b/backend/src/app.ts index 6d8234e2..1b7b4800 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -28,6 +28,12 @@ void mongoose console.log(error); }); +server.app.use( + cors({ + origin: process.env.FRONTEND_ORIGIN, + }), +); + // Middleware server.app.use(json()); diff --git a/backend/src/controllers/student.ts b/backend/src/controllers/student.ts index 5892a84f..9e5085e1 100644 --- a/backend/src/controllers/student.ts +++ b/backend/src/controllers/student.ts @@ -1,7 +1,4 @@ /* eslint-disable @typescript-eslint/no-misused-promises */ -/** - * Functions that process task route requests. - */ import { RequestHandler } from "express"; import { validationResult } from "express-validator"; diff --git a/backend/src/validators/student.ts b/backend/src/validators/student.ts index 223dfa56..7e067470 100644 --- a/backend/src/validators/student.ts +++ b/backend/src/validators/student.ts @@ -111,12 +111,12 @@ const makeProg1Validator = () => .exists() .withMessage("Program 1 field required") .bail() - .isString() - .withMessage("Program 1 must be a string"); + .isArray() + .withMessage("Program 1 must be an array"); //prog2 const makeProg2Validator = () => - body("prog2").optional().isString().withMessage("Program 2 must be a string"); + body("prog2").optional().isArray().withMessage("Program 2 must be an array"); //dietary //validates entire array @@ -151,4 +151,6 @@ export const createStudent = [ makeDietaryArrayValidator(), makeDietaryItemsValidator(), makeDietaryOtherValidator(), + body("prog2.*").isString().withMessage("Programs must be strings"), + body("prog1.*").isString().withMessage("Programs must be strings"), ]; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index edee9ab2..9b0f0341 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -18,6 +18,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "date-fns": "^3.2.0", + "dotenv": "^16.4.2", "firebase": "^10.7.1", "lucide-react": "^0.311.0", "next": "14.0.4", @@ -4147,6 +4148,17 @@ "version": "0.5.16", "license": "MIT" }, + "node_modules/dotenv": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.2.tgz", + "integrity": "sha512-rZSSFxke7d9nYQ5NeMIwp5PP+f8wXgKNljpOb7KtH6SKW1cEqcXAz9VSJYVLKe7Jhup/gUYOkaeSVyK8GJ+nBg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "license": "MIT" diff --git a/frontend/package.json b/frontend/package.json index d95ffef2..e3be685e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -25,6 +25,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "date-fns": "^3.2.0", + "dotenv": "^16.4.2", "firebase": "^10.7.1", "lucide-react": "^0.311.0", "next": "14.0.4", diff --git a/frontend/src/api/requests.ts b/frontend/src/api/requests.ts index 01e0c7b4..4be97c64 100644 --- a/frontend/src/api/requests.ts +++ b/frontend/src/api/requests.ts @@ -7,6 +7,7 @@ * Custom type definition for the HTTP methods handled by this module. */ type Method = "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; +const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL; /** * Throws an error if the status code of the HTTP response indicates an error. If an HTTP error was @@ -68,7 +69,7 @@ async function fetchRequest( * @returns A `Response` object returned by `fetch()` */ export async function GET(url: string, headers: Record = {}): Promise { - return await fetchRequest("GET", url, undefined, headers); + return await fetchRequest("GET", API_BASE_URL + url, undefined, headers); } /** @@ -84,7 +85,7 @@ export async function POST( body: unknown, headers: Record = {}, ): Promise { - return await fetchRequest("POST", url, body, headers); + return await fetchRequest("POST", API_BASE_URL + url, body, headers); } /** @@ -100,7 +101,7 @@ export async function PUT( body: unknown, headers: Record = {}, ): Promise { - return await fetchRequest("PUT", url, body, headers); + return await fetchRequest("PUT", API_BASE_URL + url, body, headers); } /** @@ -116,7 +117,7 @@ export async function PATCH( body: unknown, headers: Record = {}, ): Promise { - return await fetchRequest("PATCH", url, body, headers); + return await fetchRequest("PATCH", API_BASE_URL + url, body, headers); } /** @@ -132,7 +133,7 @@ export async function DELETE( body: unknown, headers: Record = {}, ): Promise { - return await fetchRequest("DELETE", url, body, headers); + return await fetchRequest("DELETE", API_BASE_URL + url, body, headers); } /** diff --git a/frontend/src/api/students.ts b/frontend/src/api/students.ts new file mode 100644 index 00000000..489246ee --- /dev/null +++ b/frontend/src/api/students.ts @@ -0,0 +1,64 @@ +import { POST, handleAPIError } from "../api/requests"; +import { Contact, StudentData } from "../components/StudentForm/types"; + +import type { APIResult } from "../api/requests"; + +export type Student = { + _id: string; + student: Contact; + emergency: Contact; + serviceCoordinator: Contact; + location: string; + medication?: string; + birthday: Date; + intakeDate: Date; + tourDate: Date; + regular_programs: string[]; + varying_programs: string[]; + dietary: string[]; + otherString?: string; +}; + +type StudentJSON = { + _id: string; + student: Contact; + emergency: Contact; + serviceCoordinator: Contact; + location: string; + medication?: string; + birthday: string; + intakeDate: string; + tourDate: string; + regular_programs: string[]; + varying_programs: string[]; + dietary: string[]; + otherString?: string; +}; + +function parseStudent(studentJSON: StudentJSON): Student { + return { + _id: studentJSON._id, + student: studentJSON.student, + emergency: studentJSON.emergency, + serviceCoordinator: studentJSON.serviceCoordinator, + location: studentJSON.location, + medication: studentJSON.medication, + birthday: new Date(studentJSON.birthday), + intakeDate: new Date(studentJSON.intakeDate), + tourDate: new Date(studentJSON.tourDate), + regular_programs: studentJSON.regular_programs, + varying_programs: studentJSON.varying_programs, + dietary: studentJSON.dietary, + otherString: studentJSON.otherString, + } as Student; +} + +export async function createStudent(student: StudentData): Promise> { + try { + const response = await POST("/api/student", student); + const json = (await response.json()) as StudentJSON; + return { success: true, data: parseStudent(json) }; + } catch (error) { + return handleAPIError(error); + } +} diff --git a/frontend/src/components/StudentForm/StudentBackground.tsx b/frontend/src/components/StudentForm/StudentBackground.tsx index 59d1bcea..c3db586f 100644 --- a/frontend/src/components/StudentForm/StudentBackground.tsx +++ b/frontend/src/components/StudentForm/StudentBackground.tsx @@ -45,7 +45,7 @@ export default function StudentBackground({ />
-

Mediciation

+

Medication

Varying Programs diff --git a/frontend/src/components/StudentForm/types.ts b/frontend/src/components/StudentForm/types.ts index ec93a05b..ed015d4f 100644 --- a/frontend/src/components/StudentForm/types.ts +++ b/frontend/src/components/StudentForm/types.ts @@ -41,4 +41,5 @@ export type StudentFormData = { intake_date: string; tour_date: string; regular_programs: string[]; + varying_programs: string[]; }; diff --git a/frontend/src/components/StudentFormButton.tsx b/frontend/src/components/StudentFormButton.tsx index bd19010b..e35fd4c3 100644 --- a/frontend/src/components/StudentFormButton.tsx +++ b/frontend/src/components/StudentFormButton.tsx @@ -1,6 +1,7 @@ import { useState } from "react"; import { SubmitHandler, useForm } from "react-hook-form"; +import { createStudent } from "../api/students"; import { cn } from "../lib/utils"; import { Button } from "./Button"; @@ -55,15 +56,33 @@ export default function StudentFormButton({ }, location: formData.address, medication: formData.medication, - birthday: formData.birthdate, - intakeDate: formData.intake_date, - tourDate: formData.tour_date, - prog1: formData.regular_programs, - prog2: formData.regular_programs, + // Syntax is to prevent runtime errors when attempting to make dates with invalid date strings + birthday: Date.parse(formData.birthdate) ? new Date(formData.birthdate).toISOString() : "", + intakeDate: Date.parse(formData.intake_date) + ? new Date(formData.intake_date).toISOString() + : "", + tourDate: Date.parse(formData.tour_date) ? new Date(formData.tour_date).toISOString() : "", + prog1: formData.regular_programs ? formData.regular_programs : ([] as string[]), + prog2: formData.varying_programs ? formData.varying_programs : ([] as string[]), dietary: formData.dietary, otherString: formData.other, }; - reset(); //Clear form + if (type === "add") { + createStudent(transformedData).then( + (result) => { + if (result.success) { + reset(); // only clear form on success + } else { + console.log(result.error); + alert("Unable to create student: " + result.error); + } + }, + (error) => { + console.log(error); + }, + ); + } + //uncomment for testing console.log(`${type} student data:`, transformedData); }; diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index 42d544c3..c05a97a6 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -1,5 +1,9 @@ -import CreateUser from "./create_user"; +import StudentFormButton from "../components/StudentFormButton"; export default function Home() { - return ; + return ( +
+ +
+ ); }