diff --git a/package-lock.json b/package-lock.json index 60a729b..4633cd1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "@fortawesome/react-fontawesome": "^0.2.0", "@hookform/resolvers": "^3.3.4", "@tanstack/react-query": "^5.28.6", - "axios": "^1.6.7", + "axios": "^1.6.8", "dotenv": "^16.3.1", "moon-loader": "^0.1.0", "react": "^18.2.0", @@ -1736,20 +1736,20 @@ } }, "node_modules/@tanstack/query-core": { - "version": "5.28.6", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.28.6.tgz", - "integrity": "sha512-hnhotV+DnQtvtR3jPvbQMPNMW4KEK0J4k7c609zJ8muiNknm+yoDyMHmxTWM5ZnlZpsz0zOxYFr+mzRJNHWJsA==", + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.29.0.tgz", + "integrity": "sha512-WgPTRs58hm9CMzEr5jpISe8HXa3qKQ8CxewdYZeVnA54JrPY9B1CZiwsCoLpLkf0dGRZq+LcX5OiJb0bEsOFww==", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" } }, "node_modules/@tanstack/react-query": { - "version": "5.28.6", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.28.6.tgz", - "integrity": "sha512-/DdYuDBSsA21Qbcder1R8Cr/3Nx0ZnA2lgtqKsLMvov8wL4+g0HBz/gWYZPlIsof7iyfQafyhg4wUVUsS3vWZw==", + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.29.0.tgz", + "integrity": "sha512-yxlhHB73jaBla6h5B6zPaGmQjokkzAhMHN4veotkPNiQ3Ac/mCxgABRZPsJJrgCTvhpcncBZcDBFxaR2B37vug==", "dependencies": { - "@tanstack/query-core": "5.28.6" + "@tanstack/query-core": "5.29.0" }, "funding": { "type": "github", @@ -9439,9 +9439,9 @@ } }, "node_modules/vite": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz", - "integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==", + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz", + "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==", "dev": true, "dependencies": { "esbuild": "^0.18.10", diff --git a/package.json b/package.json index f809ef3..2657ec5 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "@fortawesome/free-solid-svg-icons": "^6.5.1", "@fortawesome/react-fontawesome": "^0.2.0", "@hookform/resolvers": "^3.3.4", - "axios": "^1.6.7", + "axios": "^1.6.8", "@tanstack/react-query": "^5.28.6", "dotenv": "^16.3.1", "moon-loader": "^0.1.0", diff --git a/src/api/queries.ts b/src/api/queries.ts index 2bcdc8c..74c92d0 100644 --- a/src/api/queries.ts +++ b/src/api/queries.ts @@ -42,6 +42,44 @@ export const createUser = ( }) .then((res) => res.data); +export const createPassenger = ( + passenger: { + fields: { + "First Name": string; + "Last Name": string; + Relationship: + | "Mother" + | "Father" + | "Step-mother" + | "Step-father" + | "Legal Guardian" + | "Spouse" + | "Family Member" + | "Other Caregiver"; + "Date of Birth": Date | string; + Diagnoses?: (string | undefined)[]; + Gender: "Male" | "Female" | "Other"; + Street: string; + City: string; + State: string; + Zip: string; + Country: string; + "Cell Phone": string; + Email: string; + Waiver: boolean; + }; + }, + userId: string, + token?: string | null, +): Promise => + axios + .post(`${process.env.VITE_HOST}/passenger/${userId}`, passenger, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + .then((res) => res.data); + export const linkUser = ( airtableRecordId: string, token?: string | null, diff --git a/src/pages/PassengersPage/PassengersPage.module.css b/src/pages/PassengersPage/PassengersPage.module.css index 973e715..49f585f 100644 --- a/src/pages/PassengersPage/PassengersPage.module.css +++ b/src/pages/PassengersPage/PassengersPage.module.css @@ -29,6 +29,8 @@ font-weight: 700; color: var(--miracle-color-dark); margin-top: 2rem; + display: flex; + align-items: center; } .description { @@ -43,8 +45,14 @@ justify-content: space-evenly; justify-content: flex-start; margin-top: 2rem; + overflow-x: auto; + overflow-y: hidden; } .passengerCard { margin-right: 3rem; } + +.plusButton { + margin-left: 0.5rem; +} diff --git a/src/pages/PassengersPage/PassengersPage.tsx b/src/pages/PassengersPage/PassengersPage.tsx index f0dfae4..f35dd25 100644 --- a/src/pages/PassengersPage/PassengersPage.tsx +++ b/src/pages/PassengersPage/PassengersPage.tsx @@ -1,19 +1,22 @@ import styles from "./PassengersPage.module.css"; import PatientCard from "./components/PatientCard/PatientCard"; import PassengerCard from "./components/PassengerCard/PassengerCard"; +import PlusButton from "./components/PlusButton/PlusButton"; +import AddPassengerModal from "./components/AddPassengerModal/AddPassengerModal"; import { getAccompanyingPassengers, getPassengers } from "../../api/queries"; import { useUserContext } from "../../context/User.context"; import { useNavigationContext } from "../../context/Navigation.context"; import { Tabs } from "../../layout/SideBar/SideBar.definitions"; import { useQuery } from "@tanstack/react-query"; import { useAuth } from "@clerk/clerk-react"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import type { PassengerData } from "../../interfaces/passenger.interface"; const PassengersPage = () => { const { getToken } = useAuth(); const { currentUser } = useUserContext(); const { setCurrentTab } = useNavigationContext(); + const [isModalOpen, setIsModalOpen] = useState(false); // ping the getUserByID endpoint to get the user's data const { data: passengerData, isLoading: passengerLoading } = @@ -55,6 +58,9 @@ const PassengersPage = () => { return ( <> + {isModalOpen && ( + setIsModalOpen(false)} /> + )}

Patients and Companions

@@ -64,7 +70,14 @@ const PassengersPage = () => {
-

Companion Information

+
+

+ Companion Information +
+ +
{" "} +

+
Companions of {passengerData["First Name"]}
diff --git a/src/pages/PassengersPage/components/AddPassengerModal/AddPassengerModal.definitions.ts b/src/pages/PassengersPage/components/AddPassengerModal/AddPassengerModal.definitions.ts new file mode 100644 index 0000000..78bb3e0 --- /dev/null +++ b/src/pages/PassengersPage/components/AddPassengerModal/AddPassengerModal.definitions.ts @@ -0,0 +1,28 @@ +export type FormData = { + "First Name": string; + "Last Name": string; + Relationship: + | "Mother" + | "Father" + | "Step-mother" + | "Step-father" + | "Legal Guardian" + | "Spouse" + | "Family Member" + | "Other Caregiver"; + "Date of Birth": Date | string; + Diagnoses?: (string | undefined)[]; + Gender: "Male" | "Female" | "Other"; + Street: string; + City: string; + State: string; + Zip: string; + Country: string; + "Cell Phone": string; + Email: string; + Waiver: "Yes" | "No"; +}; + +export type AddPassengerModalProps = { + onClose: () => void; +}; diff --git a/src/pages/PassengersPage/components/AddPassengerModal/AddPassengerModal.module.css b/src/pages/PassengersPage/components/AddPassengerModal/AddPassengerModal.module.css new file mode 100644 index 0000000..7cc6efa --- /dev/null +++ b/src/pages/PassengersPage/components/AddPassengerModal/AddPassengerModal.module.css @@ -0,0 +1,13 @@ +.inputList { + display: flex; + flex-direction: column; + gap: 0.5rem; + width: 100%; +} + +.errorMessage { + color: red; + margin-top: 0.2rem; + text-align: center; + font-size: medium; +} diff --git a/src/pages/PassengersPage/components/AddPassengerModal/AddPassengerModal.tsx b/src/pages/PassengersPage/components/AddPassengerModal/AddPassengerModal.tsx new file mode 100644 index 0000000..ea4e46f --- /dev/null +++ b/src/pages/PassengersPage/components/AddPassengerModal/AddPassengerModal.tsx @@ -0,0 +1,329 @@ +import passengerSchema from "./passengerSchema"; +import styles from "./AddPassengerModal.module.css"; +import Button from "../../../../components/Button/Button"; +import { ButtonColor } from "../../../../components/Button/Button.definitions"; +import Input from "../../../../components/Input/Input"; +import Modal from "../../../../components/Modal/Modal"; +import Select from "../../../../components/Select/Select"; +import { useUserContext } from "../../../../context/User.context"; +import { createPassenger } from "../../../../api/queries"; +// eslint-disable-next-line import/no-duplicates +import React from "react"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { useAuth } from "@clerk/clerk-react"; +// eslint-disable-next-line import/no-duplicates +import { useMutation } from "@tanstack/react-query"; +// eslint-disable-next-line import/no-duplicates +import { useQueryClient } from "@tanstack/react-query"; +// eslint-disable-next-line import/no-duplicates +import { useState } from "react"; +import type { + FormData, + AddPassengerModalProps, +} from "./AddPassengerModal.definitions"; + +const AddPassengerModal: React.FC = ({ onClose }) => { + const { + register, + handleSubmit, + formState: { errors, isValid }, + } = useForm({ + resolver: yupResolver(passengerSchema), + mode: "all", + }); + + const { currentUser } = useUserContext(); + const { getToken } = useAuth(); + const queryClient = useQueryClient(); + const [errorMessage, setErrorMessage] = useState(""); + + const processDate = (date: string) => { + const newDate = + date.substring(5, 7) + + "/" + + date.substring(8, 10) + + "/" + + date.substring(0, 4); + return newDate; + }; + + const formatPhoneNumber = (phoneNumber: string) => { + const areaCode = phoneNumber.slice(0, 3); + const middleThree = phoneNumber.slice(3, 6); + const lastThree = phoneNumber.slice(6); + return `(${areaCode}) ${middleThree}-${lastThree}`; + }; + + // mutation to create a new user + const { mutate } = useMutation({ + mutationFn: async (data: FormData) => { + console.log( + JSON.stringify( + { + fields: { + ...data, + "Date of Birth": (data["Date of Birth"] as Date).toISOString(), + Waiver: data.Waiver === "Yes" ? true : false, + }, + }, + null, + 2, + ), + ); + return createPassenger( + { + fields: { + ...data, + "Date of Birth": processDate( + (data["Date of Birth"] as Date).toISOString(), + ), + Waiver: data.Waiver === "Yes" ? true : false, + "Cell Phone": formatPhoneNumber(data["Cell Phone"]), + }, + }, + currentUser?.["AirTable Record ID"] || "", + await getToken(), + ); + }, + onSuccess: () => { + console.log("passenger created successfully"); + setErrorMessage(""); + onClose(); + queryClient.invalidateQueries({ + queryKey: ["accompanyingPassengers"], + }); + }, + onError: () => { + console.log("passenger creation failed"); + setErrorMessage("Failed to add passenger. Please try again."); + }, + }); + + const onSubmit = (data: FormData) => { + mutate(data); + }; + + return ( + onClose()} + header="Add Passenger" + body={ +
+ + + + + + + + + +