From e21c0843272f2015dc849783dd829696659489df Mon Sep 17 00:00:00 2001 From: Alan Khalili Date: Thu, 9 Jan 2025 17:18:41 -0800 Subject: [PATCH 1/8] feat: Add Healthy Habits Tracking Form List component to display and manage tracking forms, need to migrate to grid2 --- .../HealthyHabitsTrackingFormList.tsx | 205 ++++++++++++++++++ .../HealthyHabitsHistory/index.tsx | 2 + 2 files changed, 207 insertions(+) create mode 100644 src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList.tsx diff --git a/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList.tsx b/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList.tsx new file mode 100644 index 0000000..978ecb7 --- /dev/null +++ b/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList.tsx @@ -0,0 +1,205 @@ +import React, { useState } from "react"; +import { + Box, + Card, + CardContent, + Typography, + Button, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Grid, +} from "@mui/material"; +import { HealthyHabitsTrackingForm } from "@/types"; +import dayjsUtil from "@/utils/dayjsUtil"; + +type HealthyHabitsTrackingFormListProps = { + trackingForms: HealthyHabitsTrackingForm[]; +}; + +const HealthyHabitsTrackingFormList = ({ + trackingForms, +}: HealthyHabitsTrackingFormListProps) => { + const [selectedForm, setSelectedForm] = + useState(null); + const [detailsOpen, setDetailsOpen] = useState(false); + + const handleOpenDetails = (form: HealthyHabitsTrackingForm) => { + setSelectedForm(form); + setDetailsOpen(true); + }; + + const handleCloseDetails = () => { + setDetailsOpen(false); + setSelectedForm(null); + }; + + const handleDelete = (formId: string) => { + // TODO: Implement delete functionality with server action + console.log("Delete form with ID:", formId); + }; + + const groupFormsByWeek = (forms: HealthyHabitsTrackingForm[]) => { + const sortedForms = [...forms].sort((a, b) => + dayjsUtil(b.submittedDate, "MM/DD/YYYY").diff( + dayjsUtil(a.submittedDate, "MM/DD/YYYY"), + ), + ); + + return sortedForms; + }; + + const sortedForms = groupFormsByWeek(trackingForms); + + return ( + + {sortedForms.map((form) => ( + + + + + + Week of{" "} + {dayjsUtil(form.submittedDate, "MM/DD/YYYY").format( + "MMM D, YYYY", + )} + + + + + + + + + + + + ))} + + + {selectedForm && ( + <> + + Health Tracking Details -{" "} + {dayjsUtil(selectedForm.submittedDate, "MM/DD/YYYY").format( + "MMM D, YYYY", + )} + + + + + + Health Conditions: + {" "} + {selectedForm.healthConditions} + + + + + Measurements + + + + Weight: {selectedForm.weight} lbs + + + + Blood Pressure: {selectedForm.systolicBloodPressure}/ + {selectedForm.diastolicBloodPressure} + + + + + Blood Glucose: {selectedForm.bloodGlucose} + + + + + + + + Daily Activity + + + Movement Minutes: {selectedForm.movementMinutes} + + + + + + Wellness Rankings + + + + + Sleep: {selectedForm.sleepRanking}/5 + + + + + Energy: {selectedForm.energyRanking}/5 + + + + + Emotional Health: {selectedForm.emotionalHealthRanking} + /5 + + + + + + + + Goals + + {selectedForm.qualitativeGoals} + + + + + + + + )} + + + ); +}; + +export default HealthyHabitsTrackingFormList; \ No newline at end of file diff --git a/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/index.tsx b/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/index.tsx index 5456852..53ee286 100644 --- a/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/index.tsx +++ b/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/index.tsx @@ -4,6 +4,7 @@ import { Box } from "@mui/material"; import ModularBarChart from "@/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/ModularBarChart"; import ModularLineChart from "@/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/ModularLineChart"; +import HealthyHabitsTrackingFormList from "@/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList"; import { HealthyHabitsTrackingForm } from "@/types"; type HealthyHabitsHistoryProps = { @@ -91,6 +92,7 @@ export default function HealthyHabitsHistory({ dataKey="emotionalHealthRanking" title="Emotional Health" /> + ); } From 5a5cfe16bcad7060620a7463ef3aeb61f64cfef5 Mon Sep 17 00:00:00 2001 From: Alan Khalili Date: Thu, 9 Jan 2025 19:39:10 -0800 Subject: [PATCH 2/8] feat: Migrate Healthy Habits Tracking Form List to Grid2 for improved layout and styling --- .../HealthyHabitsTrackingFormList.tsx | 242 +++++++++--------- 1 file changed, 125 insertions(+), 117 deletions(-) diff --git a/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList.tsx b/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList.tsx index 978ecb7..9452793 100644 --- a/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList.tsx +++ b/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList.tsx @@ -9,11 +9,18 @@ import { DialogTitle, DialogContent, DialogActions, - Grid, } from "@mui/material"; +import Grid from "@mui/material/Grid2"; +import { styled } from "@mui/material/styles"; import { HealthyHabitsTrackingForm } from "@/types"; import dayjsUtil from "@/utils/dayjsUtil"; +const Item = styled('div')(({ theme }) => ({ + height: "100%", + display: "flex", + alignItems: "center", +})); + type HealthyHabitsTrackingFormListProps = { trackingForms: HealthyHabitsTrackingForm[]; }; @@ -36,73 +43,60 @@ const HealthyHabitsTrackingFormList = ({ }; const handleDelete = (formId: string) => { - // TODO: Implement delete functionality with server action console.log("Delete form with ID:", formId); }; const groupFormsByWeek = (forms: HealthyHabitsTrackingForm[]) => { - const sortedForms = [...forms].sort((a, b) => + return [...forms].sort((a, b) => dayjsUtil(b.submittedDate, "MM/DD/YYYY").diff( dayjsUtil(a.submittedDate, "MM/DD/YYYY"), ), ); - - return sortedForms; }; const sortedForms = groupFormsByWeek(trackingForms); return ( - - {sortedForms.map((form) => ( - - - - - - Week of{" "} - {dayjsUtil(form.submittedDate, "MM/DD/YYYY").format( - "MMM D, YYYY", - )} - - - - - - - - - - - - ))} + + + {sortedForms.map((form) => ( + + + + + + + + Week of{" "} + {dayjsUtil(form.submittedDate, "MM/DD/YYYY").format( + "MMM D, YYYY", + )} + + + + + + + + + + + + + + ))} + - - - - Health Conditions: - {" "} - {selectedForm.healthConditions} - - - - - Measurements - - - - Weight: {selectedForm.weight} lbs - - + + + + - Blood Pressure: {selectedForm.systolicBloodPressure}/ - {selectedForm.diastolicBloodPressure} + + Health Conditions: + {" "} + {selectedForm.healthConditions} - - - - Blood Glucose: {selectedForm.bloodGlucose} - - + - - - - Daily Activity - - - Movement Minutes: {selectedForm.movementMinutes} - - - - - - Wellness Rankings - - - - - Sleep: {selectedForm.sleepRanking}/5 - - - - - Energy: {selectedForm.energyRanking}/5 - + + Measurements + + + + + Weight: {selectedForm.weight} lbs + + + + + + + Blood Pressure: {selectedForm.systolicBloodPressure} + /{selectedForm.diastolicBloodPressure} + + + + + + + Blood Glucose: {selectedForm.bloodGlucose} + + + - + + + + Daily Activity + - Emotional Health: {selectedForm.emotionalHealthRanking} - /5 + Movement Minutes: {selectedForm.movementMinutes} + + + + + Wellness Rankings + + + + + Sleep: {selectedForm.sleepRanking}/5 + + + + + + + Energy: {selectedForm.energyRanking}/5 + + + + + + + Emotional Health:{" "} + {selectedForm.emotionalHealthRanking}/5 + + + - - - - Goals - - {selectedForm.qualitativeGoals} - + + Goals + + {selectedForm.qualitativeGoals} + + + @@ -202,4 +210,4 @@ const HealthyHabitsTrackingFormList = ({ ); }; -export default HealthyHabitsTrackingFormList; \ No newline at end of file +export default HealthyHabitsTrackingFormList; From 1383e1e6439bc018f45483fac239a34980226109 Mon Sep 17 00:00:00 2001 From: Alan Khalili Date: Thu, 9 Jan 2025 19:59:47 -0800 Subject: [PATCH 3/8] feat: Implement delete functionality for Healthy Habits Tracking Forms with confirmation modal --- .../HealthyHabitsTrackingFormList.tsx | 82 +++++++++++++++++-- .../healthy-habits-tracking-forms/queries.ts | 23 ++++++ 2 files changed, 99 insertions(+), 6 deletions(-) diff --git a/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList.tsx b/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList.tsx index 9452793..db1d1d3 100644 --- a/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList.tsx +++ b/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList.tsx @@ -9,13 +9,17 @@ import { DialogTitle, DialogContent, DialogActions, + Modal, } from "@mui/material"; import Grid from "@mui/material/Grid2"; import { styled } from "@mui/material/styles"; import { HealthyHabitsTrackingForm } from "@/types"; import dayjsUtil from "@/utils/dayjsUtil"; +import { deleteHealthyHabitsTrackingForm } from "@/server/api/healthy-habits-tracking-forms/queries"; -const Item = styled('div')(({ theme }) => ({ +const Item = styled("div")(({ theme }) => ({ + padding: theme.spacing(2), + color: theme.palette.text.primary, height: "100%", display: "flex", alignItems: "center", @@ -31,6 +35,9 @@ const HealthyHabitsTrackingFormList = ({ const [selectedForm, setSelectedForm] = useState(null); const [detailsOpen, setDetailsOpen] = useState(false); + const [deleteModalOpen, setDeleteModalOpen] = useState(false); + const [formToDelete, setFormToDelete] = useState(null); + const [loading, setLoading] = useState(false); const handleOpenDetails = (form: HealthyHabitsTrackingForm) => { setSelectedForm(form); @@ -42,8 +49,31 @@ const HealthyHabitsTrackingFormList = ({ setSelectedForm(null); }; - const handleDelete = (formId: string) => { - console.log("Delete form with ID:", formId); + const handleOpenDeleteModal = (formId: string) => { + setFormToDelete(formId); + setDeleteModalOpen(true); + }; + + const handleCloseDeleteModal = () => { + setDeleteModalOpen(false); + setFormToDelete(null); + }; + + const handleConfirmDelete = async () => { + if (!formToDelete) return; + + setLoading(true); + try { + const response = await deleteHealthyHabitsTrackingForm(formToDelete); + if (response[1] !== null) { + console.error("Error deleting form:", response[1]); + return; + } + console.log("Delete form with ID:", formToDelete); + } finally { + setLoading(false); + handleCloseDeleteModal(); + } }; const groupFormsByWeek = (forms: HealthyHabitsTrackingForm[]) => { @@ -77,15 +107,15 @@ const HealthyHabitsTrackingFormList = ({ @@ -98,6 +128,7 @@ const HealthyHabitsTrackingFormList = ({ ))} + {/* Details Dialog */} )} + + {/* Delete Confirmation Modal */} + + + Confirm Deletion + + Are you sure you want to delete this health tracking record? This + action cannot be undone. + + + + + + + ); }; diff --git a/src/server/api/healthy-habits-tracking-forms/queries.ts b/src/server/api/healthy-habits-tracking-forms/queries.ts index 562ae95..0d5dbcf 100644 --- a/src/server/api/healthy-habits-tracking-forms/queries.ts +++ b/src/server/api/healthy-habits-tracking-forms/queries.ts @@ -43,3 +43,26 @@ export async function getHealthyHabitsTrackingForm( return [null, handleMongooseError(error)]; } } + +export async function deleteHealthyHabitsTrackingForm( + id: string, +): Promise> { + await dbConnect(); + + try { + const healthyHabitsTrackingForm = + await HealthyHabitsTrackingFormModel.findByIdAndDelete(id).exec(); + + if (!healthyHabitsTrackingForm) { + return [ + null, + apiErrors.healthyHabitsTrackingForm.healthyHabitsTrackingFormNotFound, + ]; + } + + return [serializeMongooseObject(healthyHabitsTrackingForm), null]; + } catch (error) { + console.error(error); + return [null, handleMongooseError(error)]; + } +} From 736f9e953ca62724c39f4aef67c2f0f95dcfc44e Mon Sep 17 00:00:00 2001 From: Alan Khalili Date: Thu, 9 Jan 2025 20:17:32 -0800 Subject: [PATCH 4/8] feat: Refactor Healthy Habits Tracking Form List layout and styling for improved responsiveness --- .../HealthyHabitsTrackingFormList.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList.tsx b/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList.tsx index db1d1d3..d48651a 100644 --- a/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList.tsx +++ b/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList.tsx @@ -18,7 +18,7 @@ import dayjsUtil from "@/utils/dayjsUtil"; import { deleteHealthyHabitsTrackingForm } from "@/server/api/healthy-habits-tracking-forms/queries"; const Item = styled("div")(({ theme }) => ({ - padding: theme.spacing(2), + padding: theme.spacing(), color: theme.palette.text.primary, height: "100%", display: "flex", @@ -59,7 +59,7 @@ const HealthyHabitsTrackingFormList = ({ setFormToDelete(null); }; - const handleConfirmDelete = async () => { + const handleConfirsmelete = async () => { if (!formToDelete) return; setLoading(true); @@ -94,17 +94,17 @@ const HealthyHabitsTrackingFormList = ({ - + Week of{" "} {dayjsUtil(form.submittedDate, "MM/DD/YYYY").format( - "MMM D, YYYY", + "MM/D/YYYY", )} - + - - + <> + setSnackbarOpen(false)} + message={snackbarMessage} + /> + + + {sortedForms.map((form) => ( + + + + + + + + Week of{" "} + {dayjsUtil(form.submittedDate, "MM/DD/YYYY").format( + "MM/D/YYYY", + )} + + + + + + + + + - - - - - ))} - + + + + ))} + - {/* Details Dialog */} - - {selectedForm && ( - <> - - Health Tracking Details -{" "} - {dayjsUtil(selectedForm.submittedDate, "MM/DD/YYYY").format( - "MMM D, YYYY", - )} - - - - - - - - - Health Conditions: - {" "} - {selectedForm.healthConditions} - - - + {/* Details Dialog */} + + {selectedForm && ( + <> + + Health Tracking Details -{" "} + {dayjsUtil(selectedForm.submittedDate, "MM/DD/YYYY").format( + "MMM D, YYYY", + )} + + + + + + + + + Health Conditions: + {" "} + {selectedForm.healthConditions} + + + - - Measurements - - - - - Weight: {selectedForm.weight} lbs - - - - - - - Blood Pressure: {selectedForm.systolicBloodPressure} - /{selectedForm.diastolicBloodPressure} - - - - - - - Blood Glucose: {selectedForm.bloodGlucose} - - + + Measurements + + + + + Weight: {selectedForm.weight} lbs + + + + + + + Blood Pressure:{" "} + {selectedForm.systolicBloodPressure}/ + {selectedForm.diastolicBloodPressure} + + + + + + + Blood Glucose: {selectedForm.bloodGlucose} + + + - - - Daily Activity - - - Movement Minutes: {selectedForm.movementMinutes} - - - + + Daily Activity + + + Movement Minutes: {selectedForm.movementMinutes} + + + - - Wellness Rankings - - - - - Sleep: {selectedForm.sleepRanking}/5 - - - - - - - Energy: {selectedForm.energyRanking}/5 - - - - - - - Emotional Health:{" "} - {selectedForm.emotionalHealthRanking}/5 - - + + Wellness Rankings + + + + + Sleep: {selectedForm.sleepRanking}/5 + + + + + + + Energy: {selectedForm.energyRanking}/5 + + + + + + + Emotional Health:{" "} + {selectedForm.emotionalHealthRanking}/5 + + + - - - Goals - - {selectedForm.qualitativeGoals} - + + Goals + + {selectedForm.qualitativeGoals} + + - - - - - - - - )} - + + + + + + + )} + - {/* Delete Confirmation Modal */} - - - Confirm Deletion - - Are you sure you want to delete this health tracking record? This - action cannot be undone. - + {/* Delete Confirmation Modal */} + - - + + + - - - + + + ); } diff --git a/src/server/api/healthy-habits-tracking-forms/private-mutations.ts b/src/server/api/healthy-habits-tracking-forms/private-mutations.ts index 582d1d1..f9c4157 100644 --- a/src/server/api/healthy-habits-tracking-forms/private-mutations.ts +++ b/src/server/api/healthy-habits-tracking-forms/private-mutations.ts @@ -43,3 +43,26 @@ export async function createHealthyHabitsTrackingForm( return [null, handleMongooseError(error)]; } } + +export async function deleteHealthyHabitsTrackingForm( + id: string, +): Promise> { + await dbConnect(); + + try { + const healthyHabitsTrackingForm = + await HealthyHabitsTrackingFormModel.findByIdAndDelete(id).exec(); + + if (!healthyHabitsTrackingForm) { + return [ + null, + apiErrors.healthyHabitsTrackingForm.healthyHabitsTrackingFormNotFound, + ]; + } + + return [serializeMongooseObject(healthyHabitsTrackingForm), null]; + } catch (error) { + console.error(error); + return [null, handleMongooseError(error)]; + } +} diff --git a/src/server/api/healthy-habits-tracking-forms/public-mutations.ts b/src/server/api/healthy-habits-tracking-forms/public-mutations.ts index face44c..34c09ff 100644 --- a/src/server/api/healthy-habits-tracking-forms/public-mutations.ts +++ b/src/server/api/healthy-habits-tracking-forms/public-mutations.ts @@ -1,6 +1,9 @@ "use server"; -import { createHealthyHabitsTrackingForm } from "@/server/api/healthy-habits-tracking-forms/private-mutations"; +import { + createHealthyHabitsTrackingForm, + deleteHealthyHabitsTrackingForm, +} from "@/server/api/healthy-habits-tracking-forms/private-mutations"; import { ApiResponse, HealthyHabitsTrackingForm } from "@/types"; export async function handleHealthyHabitsTrackingFormSubmission( @@ -16,3 +19,15 @@ export async function handleHealthyHabitsTrackingFormSubmission( return [null, null]; } + +export async function handleHealthyHabitsTrackingFormDeletion( + id: string, +): Promise> { + const [, error] = await deleteHealthyHabitsTrackingForm(id); + + if (error !== null) { + return [null, error]; + } + + return [null, null]; +} diff --git a/src/server/api/healthy-habits-tracking-forms/queries.ts b/src/server/api/healthy-habits-tracking-forms/queries.ts index 0d5dbcf..562ae95 100644 --- a/src/server/api/healthy-habits-tracking-forms/queries.ts +++ b/src/server/api/healthy-habits-tracking-forms/queries.ts @@ -43,26 +43,3 @@ export async function getHealthyHabitsTrackingForm( return [null, handleMongooseError(error)]; } } - -export async function deleteHealthyHabitsTrackingForm( - id: string, -): Promise> { - await dbConnect(); - - try { - const healthyHabitsTrackingForm = - await HealthyHabitsTrackingFormModel.findByIdAndDelete(id).exec(); - - if (!healthyHabitsTrackingForm) { - return [ - null, - apiErrors.healthyHabitsTrackingForm.healthyHabitsTrackingFormNotFound, - ]; - } - - return [serializeMongooseObject(healthyHabitsTrackingForm), null]; - } catch (error) { - console.error(error); - return [null, handleMongooseError(error)]; - } -} From 1b065b485fd3015d78c487e6e9454f9b4b7b88a7 Mon Sep 17 00:00:00 2001 From: Alan Khalili Date: Thu, 9 Jan 2025 21:01:47 -0800 Subject: [PATCH 7/8] feat: Add device translation and display available devices in Healthy Habits Tracking Form List --- .../HealthyHabitsTrackingFormList.tsx | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList.tsx b/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList.tsx index 78f0e2d..4735999 100644 --- a/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList.tsx +++ b/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList.tsx @@ -34,6 +34,18 @@ type HealthyHabitsTrackingFormListProps = { function HealthyHabitsTrackingFormList({ trackingForms: initialForms, }: HealthyHabitsTrackingFormListProps) { + const deviceTranslation: Record = { + hasScale: "Scale", + hasBloodPressureCuff: "Blood Pressure Cuff", + hasGlucoseMonitor: "Glucose Monitor", + hasA1cHomeTest: "A1c Home Test", + hasFitnessTracker: "Fitness Tracker", + hasBodyTapeMeasure: "Body Tape Measure", + hasResistanceBands: "Resistance Bands", + hasOtherExerciseEquipment: "Other Exercise Equipment", + noneOfTheAbove: "No Devices", + }; + const [trackingForms, setTrackingForms] = useState(initialForms); const [selectedForm, setSelectedForm] = @@ -171,7 +183,7 @@ function HealthyHabitsTrackingFormList({ - + @@ -211,7 +223,17 @@ function HealthyHabitsTrackingFormList({ - + + Devices + + + {Object.entries(selectedForm.devices) + .filter(([_, isAvailable]) => isAvailable) // Only include available devices + .map(([deviceKey]) => deviceTranslation[deviceKey]) // Translate device keys + .join(", ")} + + + Daily Activity From aca87932caa459bbf6743d2743bda6048f2dc95d Mon Sep 17 00:00:00 2001 From: Rudra Patel Date: Fri, 10 Jan 2025 19:28:25 -0500 Subject: [PATCH 8/8] feat: Refactor list and imports --- .eslintrc.cjs | 12 - src/app/api/auth/[...nextauth]/route.ts | 2 +- .../FormResponse.tsx | 2 +- .../HealthyHabitsTrackingFormList.tsx | 338 ------------------ .../HealthyHabitsTrackingFormListItem.tsx | 57 +++ .../HealthyHabitsTrackingFormModal.tsx | 168 +++++++++ .../HealthyHabitsTrackingFormList/index.tsx | 70 ++++ .../HealthyHabitsHistory/ModularBarChart.tsx | 6 +- .../HealthyHabitsHistory/ModularLineChart.tsx | 8 +- .../HealthyHabitsHistory/index.tsx | 37 +- .../HealthyHabits/HealthyHabitsTracking.tsx | 1 + .../ClientDashboard/HealthyHabits/index.tsx | 3 +- .../private-mutations.ts | 3 +- .../public-mutations.ts | 7 +- src/server/api/emails/private-mutations.tsx | 3 +- .../api/enrollment-forms/private-mutations.ts | 3 +- .../api/enrollment-forms/public-mutations.ts | 9 +- .../private-mutations.ts | 15 +- .../public-mutations.ts | 47 ++- .../healthy-habits-tracking-forms/queries.ts | 3 +- .../private-mutations.ts | 3 +- .../password-reset-tokens/public-mutations.ts | 7 +- .../program-enrollments/private-mutations.ts | 3 +- .../program-enrollments/public-mutations.ts | 9 +- src/server/api/users/private-mutations.ts | 3 +- src/server/api/users/public-mutations.ts | 19 +- src/server/api/users/queries.ts | 5 +- src/server/models/index.ts | 12 +- src/types/EnrollmentForm/EnrollmentForm.ts | 10 +- src/types/EnrollmentForm/index.ts | 10 +- src/types/HealthyHabitsTrackingForm.ts | 2 +- src/types/ProgramEnrollment.ts | 2 +- src/types/User.ts | 2 +- src/types/index.ts | 18 +- src/utils/authenticateServerFunction.ts | 5 +- src/utils/calculateAge.ts | 2 +- src/utils/getClosestPastSunday.ts | 2 +- src/utils/handleMongooseError.ts | 2 +- 38 files changed, 467 insertions(+), 443 deletions(-) delete mode 100644 src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList.tsx create mode 100644 src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList/HealthyHabitsTrackingFormListItem.tsx create mode 100644 src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList/HealthyHabitsTrackingFormModal.tsx create mode 100644 src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList/index.tsx diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 4accb66..dce8b99 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -49,17 +49,5 @@ module.exports = { "import/first": "error", "import/newline-after-import": "error", "import/no-duplicates": "error", - "no-restricted-imports": [ - "error", - { - patterns: [ - { - group: [".*"], - message: - 'Please do not use relative imports. Use "@/..." instead. If this breaks something, disable the rule for that file only.', - }, - ], - }, - ], }, }; diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts index c446deb..9a73823 100644 --- a/src/app/api/auth/[...nextauth]/route.ts +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -1,6 +1,6 @@ import NextAuth from "next-auth"; -import authOptions from "@/app/api/auth/[...nextauth]/options"; +import authOptions from "./options"; const handler = NextAuth(authOptions); diff --git a/src/components/AdminDashboard/PendingApplicationDashboard/PendingApplicationInfoModal/FormResponse.tsx b/src/components/AdminDashboard/PendingApplicationDashboard/PendingApplicationInfoModal/FormResponse.tsx index 825632b..3b77398 100644 --- a/src/components/AdminDashboard/PendingApplicationDashboard/PendingApplicationInfoModal/FormResponse.tsx +++ b/src/components/AdminDashboard/PendingApplicationDashboard/PendingApplicationInfoModal/FormResponse.tsx @@ -2,7 +2,7 @@ import { Typography } from "@mui/material"; type FormResponse = { label: string; - value: string; + value: string | number; isListItem?: boolean; }; diff --git a/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList.tsx b/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList.tsx deleted file mode 100644 index 4735999..0000000 --- a/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList.tsx +++ /dev/null @@ -1,338 +0,0 @@ -import { - Box, - Button, - Card, - CardContent, - Dialog, - DialogActions, - DialogContent, - DialogTitle, - Modal, - Snackbar, - Typography, -} from "@mui/material"; -import Grid from "@mui/material/Grid2"; -import { styled } from "@mui/material/styles"; -import React, { useState } from "react"; - -import { handleHealthyHabitsTrackingFormDeletion } from "@/server/api/healthy-habits-tracking-forms/public-mutations"; -import { HealthyHabitsTrackingForm } from "@/types"; -import dayjsUtil from "@/utils/dayjsUtil"; - -const Item = styled("div")(({ theme }) => ({ - padding: theme.spacing(), - color: theme.palette.text.primary, - height: "100%", - display: "flex", - alignItems: "center", -})); - -type HealthyHabitsTrackingFormListProps = { - trackingForms: HealthyHabitsTrackingForm[]; -}; - -function HealthyHabitsTrackingFormList({ - trackingForms: initialForms, -}: HealthyHabitsTrackingFormListProps) { - const deviceTranslation: Record = { - hasScale: "Scale", - hasBloodPressureCuff: "Blood Pressure Cuff", - hasGlucoseMonitor: "Glucose Monitor", - hasA1cHomeTest: "A1c Home Test", - hasFitnessTracker: "Fitness Tracker", - hasBodyTapeMeasure: "Body Tape Measure", - hasResistanceBands: "Resistance Bands", - hasOtherExerciseEquipment: "Other Exercise Equipment", - noneOfTheAbove: "No Devices", - }; - - const [trackingForms, setTrackingForms] = - useState(initialForms); - const [selectedForm, setSelectedForm] = - useState(null); - const [detailsOpen, setDetailsOpen] = useState(false); - const [deleteModalOpen, setDeleteModalOpen] = useState(false); - const [formToDelete, setFormToDelete] = useState(null); - const [loading, setLoading] = useState(false); - const [snackbarOpen, setSnackbarOpen] = useState(false); - const [snackbarMessage, setSnackbarMessage] = useState(""); - - const handleOpenDetails = (form: HealthyHabitsTrackingForm) => { - setSelectedForm(form); - setDetailsOpen(true); - }; - - const handleCloseDetails = () => { - setDetailsOpen(false); - setSelectedForm(null); - }; - - const handleOpenDeleteModal = (formId: string) => { - setFormToDelete(formId); - setDeleteModalOpen(true); - }; - - const handleCloseDeleteModal = () => { - setDeleteModalOpen(false); - setFormToDelete(null); - }; - - const handleConfirmDelete = async () => { - if (!formToDelete) return; - - setLoading(true); - try { - const response = - await handleHealthyHabitsTrackingFormDeletion(formToDelete); - if (response[1] !== null) { - console.error("Error deleting form:", response[1]); - setSnackbarMessage("Failed to delete the form. Please try again."); - setSnackbarOpen(true); - return; - } - - setTrackingForms((prevForms) => - prevForms.filter((form) => form._id !== formToDelete), - ); - - setSnackbarMessage("Form deleted successfully."); - setSnackbarOpen(true); - } catch (error) { - console.error("Deletion error:", error); - setSnackbarMessage("An unexpected error occurred."); - setSnackbarOpen(true); - } finally { - setLoading(false); - handleCloseDeleteModal(); - } - }; - - const groupFormsByWeek = (forms: HealthyHabitsTrackingForm[]) => { - return [...forms].sort((a, b) => - dayjsUtil(b.submittedDate, "MM/DD/YYYY").diff( - dayjsUtil(a.submittedDate, "MM/DD/YYYY"), - ), - ); - }; - - const sortedForms = groupFormsByWeek(trackingForms); - - return ( - <> - setSnackbarOpen(false)} - message={snackbarMessage} - /> - - - {sortedForms.map((form) => ( - - - - - - - - Week of{" "} - {dayjsUtil(form.submittedDate, "MM/DD/YYYY").format( - "MM/D/YYYY", - )} - - - - - - - - - - - - - - ))} - - - {/* Details Dialog */} - - {selectedForm && ( - <> - - Health Tracking Details -{" "} - {dayjsUtil(selectedForm.submittedDate, "MM/DD/YYYY").format( - "MMM D, YYYY", - )} - - - - - - - - - Health Conditions: - {" "} - {selectedForm.healthConditions} - - - - - - Measurements - - - - - Weight: {selectedForm.weight} lbs - - - - - - - Blood Pressure:{" "} - {selectedForm.systolicBloodPressure}/ - {selectedForm.diastolicBloodPressure} - - - - - - - Blood Glucose: {selectedForm.bloodGlucose} - - - - - - - Devices - - - {Object.entries(selectedForm.devices) - .filter(([_, isAvailable]) => isAvailable) // Only include available devices - .map(([deviceKey]) => deviceTranslation[deviceKey]) // Translate device keys - .join(", ")} - - - - - Daily Activity - - - Movement Minutes: {selectedForm.movementMinutes} - - - - - - Wellness Rankings - - - - - Sleep: {selectedForm.sleepRanking}/5 - - - - - - - Energy: {selectedForm.energyRanking}/5 - - - - - - - Emotional Health:{" "} - {selectedForm.emotionalHealthRanking}/5 - - - - - - - - Goals - - {selectedForm.qualitativeGoals} - - - - - - - - - - )} - - - {/* Delete Confirmation Modal */} - - - Confirm Deletion - - Are you sure you want to delete this health tracking record? This - action cannot be undone. - - - - - - - - - - ); -} - -export default HealthyHabitsTrackingFormList; diff --git a/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList/HealthyHabitsTrackingFormListItem.tsx b/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList/HealthyHabitsTrackingFormListItem.tsx new file mode 100644 index 0000000..1f627d8 --- /dev/null +++ b/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList/HealthyHabitsTrackingFormListItem.tsx @@ -0,0 +1,57 @@ +"use client"; + +import DeleteIcon from "@mui/icons-material/Delete"; +import DescriptionIcon from "@mui/icons-material/Description"; +import { + Avatar, + Box, + IconButton, + ListItem, + ListItemAvatar, + ListItemText, +} from "@mui/material"; +import { useState } from "react"; + +import HealthyHabitsTrackingFormModal from "@/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList/HealthyHabitsTrackingFormModal"; +import { HealthyHabitsTrackingForm } from "@/types"; +import dayjsUtil from "@/utils/dayjsUtil"; + +type HealthyHabitsTrackingFormListItemProps = { + form: HealthyHabitsTrackingForm; + handleDelete: (form: HealthyHabitsTrackingForm) => void; +}; + +export default function HealthyHabitsTrackingFormListItem({ + form, + handleDelete, +}: HealthyHabitsTrackingFormListItemProps) { + const [open, setOpen] = useState(false); + + return ( + + + + + handleDelete(form)}> + + + + } + > + + + + + + + + ); +} diff --git a/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList/HealthyHabitsTrackingFormModal.tsx b/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList/HealthyHabitsTrackingFormModal.tsx new file mode 100644 index 0000000..ec2e49f --- /dev/null +++ b/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList/HealthyHabitsTrackingFormModal.tsx @@ -0,0 +1,168 @@ +"use client"; + +import InfoIcon from "@mui/icons-material/Info"; +import { Box, Button, Fade, Modal, Typography } from "@mui/material"; +import { Dispatch, SetStateAction } from "react"; + +import FormResponse from "@/components/AdminDashboard/PendingApplicationDashboard/PendingApplicationInfoModal/FormResponse"; +import { HealthyHabitsTrackingForm } from "@/types"; +import dayjsUtil from "@/utils/dayjsUtil"; + +const style = { + position: "absolute", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + width: "40vw", + maxHeight: "80vh", + overflowY: "auto", + bgcolor: "background.paper", + boxShadow: 2, + p: 4, + display: "flex", + flexDirection: "column", + gap: 2, +}; + +type HealthyHabitsTrackingFormModalProps = { + open: boolean; + setOpen: Dispatch>; + form: HealthyHabitsTrackingForm; +}; + +export default function HealthyHabitsTrackingFormModal({ + form, + open, + setOpen, +}: HealthyHabitsTrackingFormModalProps) { + return ( + <> + setOpen(true)} /> + + setOpen(false)} + closeAfterTransition + > + + + + {dayjsUtil.utc(form.submittedDate).format("MM/DD/YYYY")} + + + + + Devices + + + + + + + + + + + Measurements + + + + + Daily Activity + + + Wellness Rankings + + + + + Goals + + + + + + + + + ); +} diff --git a/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList/index.tsx b/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList/index.tsx new file mode 100644 index 0000000..71ec9e9 --- /dev/null +++ b/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList/index.tsx @@ -0,0 +1,70 @@ +"use client"; + +import { Box, List, Snackbar, Typography } from "@mui/material"; +import { Dispatch, SetStateAction, useState } from "react"; + +import HealthyHabitsTrackingFormListItem from "@/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList/HealthyHabitsTrackingFormListItem"; +import { handleHealthyHabitsTrackingFormDeletion } from "@/server/api/healthy-habits-tracking-forms/public-mutations"; +import { ClientUser, HealthyHabitsTrackingForm } from "@/types"; + +type HealthyHabitsTrackingFormListProps = { + trackingForms: HealthyHabitsTrackingForm[]; + setTrackingForms: Dispatch>; + user: ClientUser; +}; + +export default function HealthyHabitsTrackingFormList({ + trackingForms, + setTrackingForms, + user, +}: HealthyHabitsTrackingFormListProps) { + const [snackbarOpen, setSnackbarOpen] = useState(false); + const [snackbarMessage, setSnackbarMessage] = useState(""); + + const handleDelete = async (form: HealthyHabitsTrackingForm) => { + const confirm = window.confirm( + "Are you sure you want to delete this health tracking record?", + ); + + if (!confirm) { + return; + } + + setTrackingForms((prevForms) => + prevForms.filter((prevForm) => prevForm._id !== form._id), + ); + + const [, error] = await handleHealthyHabitsTrackingFormDeletion(form, user); + + if (error !== null) { + setSnackbarMessage("An unexpected error occurred."); + } else { + setSnackbarMessage("Form deleted successfully."); + } + + setSnackbarOpen(true); + }; + + return ( + <> + setSnackbarOpen(false)} + message={snackbarMessage} + /> + + Previous forms + + {trackingForms.map((form) => ( + + ))} + + + + ); +} diff --git a/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/ModularBarChart.tsx b/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/ModularBarChart.tsx index 45c4cea..72500c7 100644 --- a/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/ModularBarChart.tsx +++ b/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/ModularBarChart.tsx @@ -23,13 +23,11 @@ export default function ModularBarChart({ const getChartData = () => { return trackingForms .sort((a, b) => - dayjsUtil(a.submittedDate, "MM/DD/YYYY").diff( - dayjsUtil(b.submittedDate, "MM/DD/YYYY"), - ), + dayjsUtil.utc(a.submittedDate).diff(dayjsUtil.utc(b.submittedDate)), ) .map((form) => ({ value: Number(form[dataKey]) || 0, - label: `Week of ${dayjsUtil(form.submittedDate, "MM/DD/YYYY").format("MM/DD/YY")}`, + label: `Week of ${dayjsUtil.utc(form.submittedDate).format("MM/DD/YY")}`, })); }; diff --git a/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/ModularLineChart.tsx b/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/ModularLineChart.tsx index 3467e46..a7e01b1 100644 --- a/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/ModularLineChart.tsx +++ b/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/ModularLineChart.tsx @@ -42,13 +42,11 @@ export default function ModularLineChart({ const getChartDataAndSeries = (): ChartConfig => { const sortedData = trackingForms.sort((a, b) => - dayjsUtil(a.submittedDate, "MM/DD/YYYY").diff( - dayjsUtil(b.submittedDate, "MM/DD/YYYY"), - ), + dayjsUtil.utc(a.submittedDate).diff(dayjsUtil.utc(b.submittedDate)), ); const chartData: RegularChartDataPoint[] = sortedData.map((entry) => ({ - x: dayjsUtil(entry.submittedDate, "MM/DD/YYYY").toDate(), + x: dayjsUtil.utc(entry.submittedDate).toDate(), y: entry[dataKey] as number, ...(dataKey2 && { y2: entry[dataKey2] as number }), })); @@ -96,7 +94,7 @@ export default function ModularLineChart({ label: "Date", scaleType: "time", valueFormatter: (value) => - `Week of ${dayjsUtil(value).format("MM/DD/YYYY")}`, + `Week of ${dayjsUtil.utc(value).format("MM/DD/YYYY")}`, tickInterval: chartData.map((item) => item.x), }, ]} diff --git a/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/index.tsx b/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/index.tsx index e5e1ddd..1b06fef 100644 --- a/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/index.tsx +++ b/src/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/index.tsx @@ -1,19 +1,42 @@ "use client"; -import { Box } from "@mui/material"; +import { Box, Typography } from "@mui/material"; +import { useState } from "react"; import HealthyHabitsTrackingFormList from "@/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/HealthyHabitsTrackingFormList"; import ModularBarChart from "@/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/ModularBarChart"; import ModularLineChart from "@/components/ClientDashboard/HealthyHabits/HealthyHabitsHistory/ModularLineChart"; -import { HealthyHabitsTrackingForm } from "@/types"; +import { ClientUser, HealthyHabitsTrackingForm } from "@/types"; type HealthyHabitsHistoryProps = { - trackingForms: HealthyHabitsTrackingForm[]; + initialForms: HealthyHabitsTrackingForm[]; + user: ClientUser; }; export default function HealthyHabitsHistory({ - trackingForms, + initialForms, + user, }: HealthyHabitsHistoryProps) { + const [trackingForms, setTrackingForms] = + useState(initialForms); + + if (trackingForms.length === 0) { + return ( + + No forms submitted yet. + + ); + } + return ( - + ); } diff --git a/src/components/ClientDashboard/HealthyHabits/HealthyHabitsTracking.tsx b/src/components/ClientDashboard/HealthyHabits/HealthyHabitsTracking.tsx index 2b4d32e..cd69d5c 100644 --- a/src/components/ClientDashboard/HealthyHabits/HealthyHabitsTracking.tsx +++ b/src/components/ClientDashboard/HealthyHabits/HealthyHabitsTracking.tsx @@ -85,6 +85,7 @@ export default function HealthyHabitsTracking({ const healthyHabitsTrackingForm: HealthyHabitsTrackingForm = { ...data, + submittedDate: dayjsUtil(data.submittedDate).utc().toISOString(), user, }; diff --git a/src/components/ClientDashboard/HealthyHabits/index.tsx b/src/components/ClientDashboard/HealthyHabits/index.tsx index bdd2e0c..9a27fbb 100644 --- a/src/components/ClientDashboard/HealthyHabits/index.tsx +++ b/src/components/ClientDashboard/HealthyHabits/index.tsx @@ -31,7 +31,8 @@ export default function HealthyHabits({ user }: HealthyHabitsProps) { case "history": return ( ); case "info": diff --git a/src/server/api/email-verification-tokens/private-mutations.ts b/src/server/api/email-verification-tokens/private-mutations.ts index adc0834..9e9f748 100644 --- a/src/server/api/email-verification-tokens/private-mutations.ts +++ b/src/server/api/email-verification-tokens/private-mutations.ts @@ -1,10 +1,11 @@ -import { getEmailVerificationTokenByToken } from "@/server/api/email-verification-tokens/queries"; import dbConnect from "@/server/dbConnect"; import { EmailVerificationTokenModel } from "@/server/models"; import { ApiResponse, EmailVerificationToken } from "@/types"; import apiErrors from "@/utils/constants/apiErrors"; import handleMongooseError from "@/utils/handleMongooseError"; +import { getEmailVerificationTokenByToken } from "./queries"; + export async function createEmailVerificationToken( token: string, userId: string, diff --git a/src/server/api/email-verification-tokens/public-mutations.ts b/src/server/api/email-verification-tokens/public-mutations.ts index 3c5f6be..83dfa34 100644 --- a/src/server/api/email-verification-tokens/public-mutations.ts +++ b/src/server/api/email-verification-tokens/public-mutations.ts @@ -1,10 +1,11 @@ "use server"; -import { createEmailVerificationToken } from "@/server/api/email-verification-tokens/private-mutations"; -import { sendEmailVerificationEmail } from "@/server/api/emails/private-mutations"; -import { getUserByEmail } from "@/server/api/users/queries"; import { EmailVerificationToken } from "@/types"; +import { sendEmailVerificationEmail } from "../emails/private-mutations"; +import { getUserByEmail } from "../users/queries"; +import { createEmailVerificationToken } from "./private-mutations"; + export async function handleEmailVerificationTokenRequest( email: string, ): Promise { diff --git a/src/server/api/emails/private-mutations.tsx b/src/server/api/emails/private-mutations.tsx index 2b227ea..72c017e 100644 --- a/src/server/api/emails/private-mutations.tsx +++ b/src/server/api/emails/private-mutations.tsx @@ -5,7 +5,8 @@ import PasswordChangedEmail from "@/components/emails/PasswordChangedEmail"; import RejectionEmail from "@/components/emails/RejectionEmail"; import ResetPasswordEmail from "@/components/emails/ResetPasswordEmail"; import WelcomeEmail from "@/components/emails/WelcomeEmail"; -import sendEmail from "@/server/api/emails/helpers"; + +import sendEmail from "./helpers"; export async function sendPasswordChangeEmail( recipientEmail: string, diff --git a/src/server/api/enrollment-forms/private-mutations.ts b/src/server/api/enrollment-forms/private-mutations.ts index 84a7bba..bd204e2 100644 --- a/src/server/api/enrollment-forms/private-mutations.ts +++ b/src/server/api/enrollment-forms/private-mutations.ts @@ -1,4 +1,3 @@ -import { getEnrollmentFormByEmail } from "@/server/api/enrollment-forms/queries"; import dbConnect from "@/server/dbConnect"; import { EnrollmentFormModel } from "@/server/models"; import { ApiResponse, EnrollmentForm } from "@/types"; @@ -6,6 +5,8 @@ import apiErrors from "@/utils/constants/apiErrors"; import handleMongooseError from "@/utils/handleMongooseError"; import { serializeMongooseObject } from "@/utils/serializeMongooseObject"; +import { getEnrollmentFormByEmail } from "./queries"; + export async function createEnrollmentForm( enrollmentForm: EnrollmentForm, ): Promise> { diff --git a/src/server/api/enrollment-forms/public-mutations.ts b/src/server/api/enrollment-forms/public-mutations.ts index 7d204d0..e2aa4dd 100644 --- a/src/server/api/enrollment-forms/public-mutations.ts +++ b/src/server/api/enrollment-forms/public-mutations.ts @@ -1,11 +1,12 @@ "use server"; -import { handleEmailVerificationTokenRequest } from "@/server/api/email-verification-tokens/public-mutations"; -import { createEnrollmentForm } from "@/server/api/enrollment-forms/private-mutations"; -import { createProgramEnrollmentsFromEnrollmentForm } from "@/server/api/program-enrollments/private-mutations"; -import { createClientUser } from "@/server/api/users/private-mutations"; import { ApiResponse, ClientUser, EnrollmentForm } from "@/types"; +import { handleEmailVerificationTokenRequest } from "../email-verification-tokens/public-mutations"; +import { createProgramEnrollmentsFromEnrollmentForm } from "../program-enrollments/private-mutations"; +import { createClientUser } from "../users/private-mutations"; +import { createEnrollmentForm } from "./private-mutations"; + export async function handleEnrollmentFormSubmission( enrollmentForm: EnrollmentForm, ): Promise> { diff --git a/src/server/api/healthy-habits-tracking-forms/private-mutations.ts b/src/server/api/healthy-habits-tracking-forms/private-mutations.ts index f9c4157..d6ae1a9 100644 --- a/src/server/api/healthy-habits-tracking-forms/private-mutations.ts +++ b/src/server/api/healthy-habits-tracking-forms/private-mutations.ts @@ -1,11 +1,12 @@ -import { getHealthyHabitsTrackingForm } from "@/server/api/healthy-habits-tracking-forms/queries"; import dbConnect from "@/server/dbConnect"; import { HealthyHabitsTrackingFormModel, UserModel } from "@/server/models"; -import { ApiResponse, HealthyHabitsTrackingForm } from "@/types"; +import { ApiResponse, ClientUser, HealthyHabitsTrackingForm } from "@/types"; import apiErrors from "@/utils/constants/apiErrors"; import handleMongooseError from "@/utils/handleMongooseError"; import { serializeMongooseObject } from "@/utils/serializeMongooseObject"; +import { getHealthyHabitsTrackingForm } from "./queries"; + export async function createHealthyHabitsTrackingForm( healthyHabitsTrackingForm: HealthyHabitsTrackingForm, ): Promise> { @@ -45,13 +46,14 @@ export async function createHealthyHabitsTrackingForm( } export async function deleteHealthyHabitsTrackingForm( - id: string, + form: HealthyHabitsTrackingForm, + user: ClientUser, ): Promise> { await dbConnect(); try { const healthyHabitsTrackingForm = - await HealthyHabitsTrackingFormModel.findByIdAndDelete(id).exec(); + await HealthyHabitsTrackingFormModel.findByIdAndDelete(form._id).exec(); if (!healthyHabitsTrackingForm) { return [ @@ -60,6 +62,11 @@ export async function deleteHealthyHabitsTrackingForm( ]; } + // delete the user's healthy habits tracking form if it exists + await UserModel.findByIdAndUpdate(user._id, { + $pull: { healthyHabitsTrackingForms: form._id }, + }); + return [serializeMongooseObject(healthyHabitsTrackingForm), null]; } catch (error) { console.error(error); diff --git a/src/server/api/healthy-habits-tracking-forms/public-mutations.ts b/src/server/api/healthy-habits-tracking-forms/public-mutations.ts index 34c09ff..a93c212 100644 --- a/src/server/api/healthy-habits-tracking-forms/public-mutations.ts +++ b/src/server/api/healthy-habits-tracking-forms/public-mutations.ts @@ -1,29 +1,62 @@ "use server"; +import { ApiResponse, ClientUser, HealthyHabitsTrackingForm } from "@/types"; +import authenticateServerFunction from "@/utils/authenticateServerFunction"; +import apiErrors from "@/utils/constants/apiErrors"; + import { createHealthyHabitsTrackingForm, deleteHealthyHabitsTrackingForm, -} from "@/server/api/healthy-habits-tracking-forms/private-mutations"; -import { ApiResponse, HealthyHabitsTrackingForm } from "@/types"; +} from "./private-mutations"; export async function handleHealthyHabitsTrackingFormSubmission( healthyHabitsTrackingForm: HealthyHabitsTrackingForm, ): Promise> { - const [, error] = await createHealthyHabitsTrackingForm( + const [session, authError] = await authenticateServerFunction(); + + if (authError !== null) { + return [null, authError]; + } + + if ( + session.user.role === "client" && + session.user.email !== healthyHabitsTrackingForm.user.email + ) { + return [null, apiErrors.unauthorized]; + } + + const [, createError] = await createHealthyHabitsTrackingForm( healthyHabitsTrackingForm, ); - if (error !== null) { - return [null, error]; + if (createError !== null) { + return [null, createError]; } return [null, null]; } export async function handleHealthyHabitsTrackingFormDeletion( - id: string, + healthyHabitsTrackingForm: HealthyHabitsTrackingForm, + user: ClientUser, ): Promise> { - const [, error] = await deleteHealthyHabitsTrackingForm(id); + const [session, authError] = await authenticateServerFunction(); + + if (authError !== null) { + return [null, authError]; + } + + if ( + session.user.role === "client" && + session.user.email !== healthyHabitsTrackingForm.user.email + ) { + return [null, apiErrors.unauthorized]; + } + + const [, error] = await deleteHealthyHabitsTrackingForm( + healthyHabitsTrackingForm, + user, + ); if (error !== null) { return [null, error]; diff --git a/src/server/api/healthy-habits-tracking-forms/queries.ts b/src/server/api/healthy-habits-tracking-forms/queries.ts index 562ae95..0cc9b10 100644 --- a/src/server/api/healthy-habits-tracking-forms/queries.ts +++ b/src/server/api/healthy-habits-tracking-forms/queries.ts @@ -1,4 +1,3 @@ -import { getUserByEmail } from "@/server/api/users/queries"; import dbConnect from "@/server/dbConnect"; import { HealthyHabitsTrackingFormModel } from "@/server/models"; import { ApiResponse, HealthyHabitsTrackingForm } from "@/types"; @@ -6,6 +5,8 @@ import apiErrors from "@/utils/constants/apiErrors"; import handleMongooseError from "@/utils/handleMongooseError"; import { serializeMongooseObject } from "@/utils/serializeMongooseObject"; +import { getUserByEmail } from "../users/queries"; + export async function getHealthyHabitsTrackingForm( email: string, submittedDate: string, diff --git a/src/server/api/password-reset-tokens/private-mutations.ts b/src/server/api/password-reset-tokens/private-mutations.ts index 965e599..955b741 100644 --- a/src/server/api/password-reset-tokens/private-mutations.ts +++ b/src/server/api/password-reset-tokens/private-mutations.ts @@ -1,4 +1,3 @@ -import { getPasswordResetTokenByToken } from "@/server/api/password-reset-tokens/queries"; import dbConnect from "@/server/dbConnect"; import { PasswordResetTokenModel } from "@/server/models"; import { ApiResponse, PasswordResetToken } from "@/types"; @@ -6,6 +5,8 @@ import apiErrors from "@/utils/constants/apiErrors"; import handleMongooseError from "@/utils/handleMongooseError"; import { serializeMongooseObject } from "@/utils/serializeMongooseObject"; +import { getPasswordResetTokenByToken } from "./queries"; + export async function createPasswordResetToken( token: string, userId: string, diff --git a/src/server/api/password-reset-tokens/public-mutations.ts b/src/server/api/password-reset-tokens/public-mutations.ts index 6d00c29..c1c288c 100644 --- a/src/server/api/password-reset-tokens/public-mutations.ts +++ b/src/server/api/password-reset-tokens/public-mutations.ts @@ -1,11 +1,12 @@ "use server"; -import { sendPasswordResetEmail } from "@/server/api/emails/private-mutations"; -import { createPasswordResetToken } from "@/server/api/password-reset-tokens/private-mutations"; -import { getUserByEmail } from "@/server/api/users/queries"; import { PasswordResetToken } from "@/types"; import dayjsUtil from "@/utils/dayjsUtil"; +import { sendPasswordResetEmail } from "../emails/private-mutations"; +import { getUserByEmail } from "../users/queries"; +import { createPasswordResetToken } from "./private-mutations"; + export default async function handlePasswordResetRequest(email: string) { // check if email exists const [user, userError] = await getUserByEmail(email); diff --git a/src/server/api/program-enrollments/private-mutations.ts b/src/server/api/program-enrollments/private-mutations.ts index 451a81d..d15cf61 100644 --- a/src/server/api/program-enrollments/private-mutations.ts +++ b/src/server/api/program-enrollments/private-mutations.ts @@ -1,4 +1,3 @@ -import { getProgramEnrollmentForUser } from "@/server/api/program-enrollments/queries"; import dbConnect from "@/server/dbConnect"; import { ProgramEnrollmentModel, UserModel } from "@/server/models"; import { ApiResponse, EnrollmentForm, ProgramEnrollment, User } from "@/types"; @@ -7,6 +6,8 @@ import dayjsUtil from "@/utils/dayjsUtil"; import handleMongooseError from "@/utils/handleMongooseError"; import { serializeMongooseObject } from "@/utils/serializeMongooseObject"; +import { getProgramEnrollmentForUser } from "./queries"; + export async function createProgramEnrollment( programEnrollment: ProgramEnrollment, ): Promise> { diff --git a/src/server/api/program-enrollments/public-mutations.ts b/src/server/api/program-enrollments/public-mutations.ts index 79dce5a..609c7cb 100644 --- a/src/server/api/program-enrollments/public-mutations.ts +++ b/src/server/api/program-enrollments/public-mutations.ts @@ -1,15 +1,16 @@ "use server"; +import { ProgramEnrollment } from "@/types"; +import authenticateServerFunction from "@/utils/authenticateServerFunction"; + import { sendRejectionEmail, sendWelcomeEmail, -} from "@/server/api/emails/private-mutations"; +} from "../emails/private-mutations"; import { approveProgramEnrollment, rejectProgramEnrollment, -} from "@/server/api/program-enrollments/private-mutations"; -import { ProgramEnrollment } from "@/types"; -import authenticateServerFunction from "@/utils/authenticateServerFunction"; +} from "./private-mutations"; export async function handleRejectProgramApplication( programEnrollment: ProgramEnrollment, diff --git a/src/server/api/users/private-mutations.ts b/src/server/api/users/private-mutations.ts index 8142b6b..62b1ea3 100644 --- a/src/server/api/users/private-mutations.ts +++ b/src/server/api/users/private-mutations.ts @@ -2,7 +2,6 @@ import bcrypt from "bcrypt"; import mongoose from "mongoose"; import { AdminUserRequest } from "@/app/api/users/actions/create-admin-account/route"; -import { getUserByEmail } from "@/server/api/users/queries"; import dbConnect from "@/server/dbConnect"; import { UserModel } from "@/server/models"; import { AdminUser, ApiResponse, ClientUser, User } from "@/types"; @@ -11,6 +10,8 @@ import apiErrors from "@/utils/constants/apiErrors"; import dayjsUtil from "@/utils/dayjsUtil"; import handleMongooseError from "@/utils/handleMongooseError"; +import { getUserByEmail } from "./queries"; + export async function createAdminUser( adminUserRequest: AdminUserRequest, ): Promise> { diff --git a/src/server/api/users/public-mutations.ts b/src/server/api/users/public-mutations.ts index 490c8d4..a27f986 100644 --- a/src/server/api/users/public-mutations.ts +++ b/src/server/api/users/public-mutations.ts @@ -1,22 +1,19 @@ "use server"; - import bcrypt from "bcrypt"; -import { deleteEmailVerificationToken } from "@/server/api/email-verification-tokens/private-mutations"; -import { getEmailVerificationTokenByToken } from "@/server/api/email-verification-tokens/queries"; -import { sendPasswordChangeEmail } from "@/server/api/emails/private-mutations"; -import { deletePasswordResetToken } from "@/server/api/password-reset-tokens/private-mutations"; -import { getPasswordResetTokenByToken } from "@/server/api/password-reset-tokens/queries"; -import { - changePassword, - updateUser, -} from "@/server/api/users/private-mutations"; -import { getUserById } from "@/server/api/users/queries"; import dbConnect from "@/server/dbConnect"; import { ApiResponse } from "@/types"; import authenticateServerFunction from "@/utils/authenticateServerFunction"; import apiErrors from "@/utils/constants/apiErrors"; +import { deleteEmailVerificationToken } from "../email-verification-tokens/private-mutations"; +import { getEmailVerificationTokenByToken } from "../email-verification-tokens/queries"; +import { sendPasswordChangeEmail } from "../emails/private-mutations"; +import { deletePasswordResetToken } from "../password-reset-tokens/private-mutations"; +import { getPasswordResetTokenByToken } from "../password-reset-tokens/queries"; +import { changePassword, updateUser } from "./private-mutations"; +import { getUserById } from "./queries"; + export async function resetPasswordWithToken( token: string, newPassword: string, diff --git a/src/server/api/users/queries.ts b/src/server/api/users/queries.ts index dd8ed20..45ac4bd 100644 --- a/src/server/api/users/queries.ts +++ b/src/server/api/users/queries.ts @@ -22,7 +22,10 @@ async function getUser( const userQuery = UserModel.findOne(filters); if (options?.populateHealthyHabitsTrackingForms) { - userQuery.populate("healthyHabitsTrackingForms"); + userQuery.populate({ + path: "healthyHabitsTrackingForms", + options: { sort: { submittedDate: -1 } }, + }); } if (options?.populateProgramEnrollments) { diff --git a/src/server/models/index.ts b/src/server/models/index.ts index 010106d..829ee0a 100644 --- a/src/server/models/index.ts +++ b/src/server/models/index.ts @@ -1,6 +1,6 @@ -export { default as EmailVerificationTokenModel } from "@/server/models/EmailVerificationToken"; -export { default as EnrollmentFormModel } from "@/server/models/EnrollmentForm"; -export { default as HealthyHabitsTrackingFormModel } from "@/server/models/HealthyHabitsTrackingForm"; -export { default as PasswordResetTokenModel } from "@/server/models/PasswordResetToken"; -export { default as ProgramEnrollmentModel } from "@/server/models/ProgramEnrollment"; -export { default as UserModel } from "@/server/models/User"; +export { default as EmailVerificationTokenModel } from "./EmailVerificationToken"; +export { default as EnrollmentFormModel } from "./EnrollmentForm"; +export { default as HealthyHabitsTrackingFormModel } from "./HealthyHabitsTrackingForm"; +export { default as PasswordResetTokenModel } from "./PasswordResetToken"; +export { default as ProgramEnrollmentModel } from "./ProgramEnrollment"; +export { default as UserModel } from "./User"; diff --git a/src/types/EnrollmentForm/EnrollmentForm.ts b/src/types/EnrollmentForm/EnrollmentForm.ts index f850a47..c073aa1 100644 --- a/src/types/EnrollmentForm/EnrollmentForm.ts +++ b/src/types/EnrollmentForm/EnrollmentForm.ts @@ -1,7 +1,9 @@ -import { GeneralInformationSection } from "@/types/EnrollmentForm/GeneralInformationSection"; -import { ProgramSelectionSection } from "@/types/EnrollmentForm/ProgramSelectionSection"; -import { ProgramSpecificQuestionsSection } from "@/types/EnrollmentForm/ProgramSpecificQuestionsSection"; -import { QualifyingQuestionsSection } from "@/types/EnrollmentForm/QualifyingQuestionsSection"; +import { + GeneralInformationSection, + ProgramSelectionSection, + ProgramSpecificQuestionsSection, + QualifyingQuestionsSection, +} from "."; export type EnrollmentForm = { _id?: string; diff --git a/src/types/EnrollmentForm/index.ts b/src/types/EnrollmentForm/index.ts index 2acaa3d..bf0533b 100644 --- a/src/types/EnrollmentForm/index.ts +++ b/src/types/EnrollmentForm/index.ts @@ -1,5 +1,5 @@ -export * from "@/types/EnrollmentForm/EnrollmentForm"; -export * from "@/types/EnrollmentForm/GeneralInformationSection"; -export * from "@/types/EnrollmentForm/ProgramSelectionSection"; -export * from "@/types/EnrollmentForm/ProgramSpecificQuestionsSection"; -export * from "@/types/EnrollmentForm/QualifyingQuestionsSection"; +export * from "./EnrollmentForm"; +export * from "./GeneralInformationSection"; +export * from "./ProgramSelectionSection"; +export * from "./ProgramSpecificQuestionsSection"; +export * from "./QualifyingQuestionsSection"; diff --git a/src/types/HealthyHabitsTrackingForm.ts b/src/types/HealthyHabitsTrackingForm.ts index 75cd542..3b9be95 100644 --- a/src/types/HealthyHabitsTrackingForm.ts +++ b/src/types/HealthyHabitsTrackingForm.ts @@ -1,6 +1,6 @@ import z from "zod"; -import { User } from "@/types"; +import { User } from "."; export const healthyHabitsValidator = z.object({ submittedDate: z.string(), diff --git a/src/types/ProgramEnrollment.ts b/src/types/ProgramEnrollment.ts index 12096b0..ef6a367 100644 --- a/src/types/ProgramEnrollment.ts +++ b/src/types/ProgramEnrollment.ts @@ -1,4 +1,4 @@ -import { Program, User } from "@/types"; +import { Program, User } from "."; export type ProgramEnrollment = { _id?: string; diff --git a/src/types/User.ts b/src/types/User.ts index d46c642..84bc892 100644 --- a/src/types/User.ts +++ b/src/types/User.ts @@ -2,7 +2,7 @@ import { EnrollmentForm, HealthyHabitsTrackingForm, ProgramEnrollment, -} from "@/types"; +} from "."; export type AdminUser = { _id?: string; diff --git a/src/types/index.ts b/src/types/index.ts index 8e52dca..ef2247b 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,9 +1,9 @@ -export * from "@/types/ApiResponse"; -export * from "@/types/EmailVerificationToken"; -export * from "@/types/EnrollmentForm/EnrollmentForm"; -export * from "@/types/HealthyHabitsTrackingForm"; -export * from "@/types/PasswordResetToken"; -export * from "@/types/Program"; -export * from "@/types/ProgramEnrollment"; -export * from "@/types/SearchParams"; -export * from "@/types/User"; +export * from "./ApiResponse"; +export * from "./EmailVerificationToken"; +export * from "./EnrollmentForm"; +export * from "./HealthyHabitsTrackingForm"; +export * from "./PasswordResetToken"; +export * from "./Program"; +export * from "./ProgramEnrollment"; +export * from "./SearchParams"; +export * from "./User"; diff --git a/src/utils/authenticateServerFunction.ts b/src/utils/authenticateServerFunction.ts index d8f458d..5f969c3 100644 --- a/src/utils/authenticateServerFunction.ts +++ b/src/utils/authenticateServerFunction.ts @@ -1,8 +1,9 @@ import { Session } from "next-auth"; import { ApiResponse } from "@/types"; -import apiErrors from "@/utils/constants/apiErrors"; -import getUserSession from "@/utils/getUserSession"; + +import apiErrors from "./constants/apiErrors"; +import getUserSession from "./getUserSession"; export default async function authenticateServerFunction( role?: "admin" | "client", diff --git a/src/utils/calculateAge.ts b/src/utils/calculateAge.ts index c6b15dd..675dca0 100644 --- a/src/utils/calculateAge.ts +++ b/src/utils/calculateAge.ts @@ -1,4 +1,4 @@ -import dayjsUtil from "@/utils/dayjsUtil"; +import dayjsUtil from "./dayjsUtil"; export default function calculateAge(dateString: string) { const birthDate = dayjsUtil(dateString, "DD/MM/YYYY"); diff --git a/src/utils/getClosestPastSunday.ts b/src/utils/getClosestPastSunday.ts index 16d3793..85f5e75 100644 --- a/src/utils/getClosestPastSunday.ts +++ b/src/utils/getClosestPastSunday.ts @@ -1,4 +1,4 @@ -import dayjsUtil from "@/utils/dayjsUtil"; +import dayjsUtil from "./dayjsUtil"; export default function getClosestPastSunday() { const today = dayjsUtil(); diff --git a/src/utils/handleMongooseError.ts b/src/utils/handleMongooseError.ts index 95a3c5b..fc2277d 100644 --- a/src/utils/handleMongooseError.ts +++ b/src/utils/handleMongooseError.ts @@ -1,6 +1,6 @@ import mongoose from "mongoose"; -import apiErrors from "@/utils/constants/apiErrors"; +import apiErrors from "./constants/apiErrors"; // hand back specific error messages for Mongoose-related errors export default function handleMongooseError(error: unknown): string {