Repository: creative-snails/phisio-log-frontend-mob
Pull request #53: 38 add zod validation for all inputs to match the backend
Author: login: danboyce92
Created: 2025-05-22T10:36:47Z | Merged: 2025-05-23T14:06:48Z

Implementing Zod validation & replacing current validation system.

For the most part I copied the structure for the types from the Zod schemas declared in the backend as suggested, apart from HealthRecord where the structure of status has been updated. This will need to be addressed in the backend.

I replaced the old interfaces in the types file with the Zod schemas ( I will remove the commented out code when the PR is given the green-light, it's there as a reference to what has been replaced. )

There has been some small changes made throughout the codebase to account for the several schemas that have changed date strings to date objects.

This date string to date object change has also affected the display of dates in the UI which must be addressed as it is no longer in a tidy format. I began writing a helper function in the utils folder to help with the formatting

After Zod Schemas were added, they were implemented in the validators file to allow type checking during runtime.

Some additional constants taken from the backend have been saved in the constants file.


Diff:
diff --git a/app/HealthRecord.tsx b/app/HealthRecord.tsx
index e0463b2..d73d9ed 100644
--- a/app/HealthRecord.tsx
+++ b/app/HealthRecord.tsx
@@ -40,7 +40,10 @@ const HealthRecord = () => {
         {healthRecord.symptoms.map((symptom, index) => (
           <View key={index} style={styles.item}>
             <Text>Name: {symptom.name}</Text>
-            <Text>Start Date: {symptom.startDate ? symptom.startDate.toString() : ""}</Text>
+            <Text>
+              Start Date:
+              {symptom.startDate ? new Date(symptom.startDate).toISOString().split("T")[0] : ""}
+            </Text>
           </View>
         ))}
         <Pressable style={commonStyles.btn} onPress={() => router.navigate(`/${ROUTES.EDIT.SYMPTOMS}`)}>
@@ -75,7 +78,7 @@ const HealthRecord = () => {
         {healthRecord.medicalConsultations?.map((consultation, index) => (
           <View key={index} style={styles.item}>
             <Text>Consultant: {consultation.consultant}</Text>
-            <Text>Date: {consultation.date ? consultation.date.toString() : ""}</Text>
+            <Text>Date: {consultation.date ? new Date(consultation.date).toISOString().split("T")[0] : ""}</Text>
             <Text>Diagnosis: {consultation.diagnosis}</Text>
             <Text>Follow Up Actions:</Text>
             {consultation.followUpActions?.map((action, actionIndex) => <Text key={actionIndex}> - {action}</Text>)}
diff --git a/app/api.ts b/app/api.ts
index 6d40e6b..38391e9 100644
--- a/app/api.ts
+++ b/app/api.ts
@@ -1,8 +1,8 @@
 import axios from "axios";
 
-import { HealthRecordType } from "@/types/healthRecordTypes";
+import { HealthRecordType } from "@/validation/healthRecordSchema";
 
-const API_URL = "http://localhost:5000/health-records";
+const API_URL = "http://localhost:5555/health-records";
 
 export const getHealthRecord = async (id: number) => {
   try {
diff --git a/app/edit/EditConsultations.tsx b/app/edit/EditConsultations.tsx
index bde83c9..bdab9c8 100644
--- a/app/edit/EditConsultations.tsx
+++ b/app/edit/EditConsultations.tsx
@@ -7,7 +7,6 @@ import { SaveCancelButtons } from "@/components/formElements/SaveCancelButtons";
 import { useDatePicker } from "@/hooks/useDatePicker";
 import { useEditForm } from "@/hooks/useEditForm";
 import { commonStyles } from "@/styles/commonStyles";
-import { MedicalConsultation } from "@/types/healthRecordTypes";
 import {
   addItem,
   addNestedItem,
@@ -17,7 +16,8 @@ import {
   updateNestedItem,
 } from "@/utils/arrayHelpers";
 import { SCREEN_LABELS } from "@/utils/constants";
-import { validators } from "@/utils/validators";
+import { MedicalConsultation } from "@/validation/healthRecordSchema";
+import { validators } from "@/validation/validators";
 
 const EditConsultations = () => {
   const { localValue, setLocalValue, handleSave, loading } = useEditForm(
@@ -30,8 +30,8 @@ const EditConsultations = () => {
     setShowActionsMap((prev) => ({ ...prev, [index]: !prev[index] }));
   };
 
-  const handleDateChange = (index: number, dateString: string) =>
-    setLocalValue(updateItemProperty(localValue, index, "date", dateString));
+  const handleDateChange = (index: number, date: Date) =>
+    setLocalValue(updateItemProperty(localValue, index, "date", new Date(date)));
 
   const getConsultationDate = (consultation: MedicalConsultation) =>
     consultation.date ? new Date(consultation.date) : new Date();
@@ -52,12 +52,13 @@ const EditConsultations = () => {
             value={consultation.consultant}
             onChangeText={(text) => setLocalValue(updateItemProperty(localValue, index, "consultant", text))}
           />
+
           <DatePicker
             isOpen={isOpen && selectedItemIndex === index}
             onDismiss={closeDatePicker}
             onConfirm={({ date }) => handleConfirmDate(date)}
             date={getCurrentDate(localValue)}
-            value={consultation.date}
+            value={new Date(consultation.date).toISOString().split("T")[0]}
             onPress={() => openDatePicker(index)}
           />
           <TextInput
@@ -74,7 +75,7 @@ const EditConsultations = () => {
             </TouchableOpacity>
             <View style={styles.followUps}>
               {showActionsMap[index] &&
-                consultation.followUpActions?.map((action, followUpIndex) => (
+                consultation.followUpActions?.map((action: string, followUpIndex: number) => (
                   <View key={followUpIndex} style={styles.followUpsEntry}>
                     <TextInput
                       placeholder="Enter follow-up"
@@ -117,7 +118,7 @@ const EditConsultations = () => {
             setLocalValue(
               addItem(localValue, {
                 consultant: "",
-                date: new Date().toISOString().split("T")[0],
+                date: new Date(),
                 diagnosis: "",
                 followUpActions: [],
               })
diff --git a/app/edit/EditDescription.tsx b/app/edit/EditDescription.tsx
index 5500e32..e54afb1 100644
--- a/app/edit/EditDescription.tsx
+++ b/app/edit/EditDescription.tsx
@@ -4,7 +4,7 @@ import { EditScreenLayout } from "@/components/formElements/EditScreenLayout";
 import { SaveCancelButtons } from "@/components/formElements/SaveCancelButtons";
 import { useEditForm } from "@/hooks/useEditForm";
 import { SCREEN_LABELS } from "@/utils/constants";
-import { validators } from "@/utils/validators";
+import { validators } from "@/validation/validators";
 
 const EditDescription = () => {
   const { localValue, setLocalValue, handleSave, loading } = useEditForm("description", validators.description);
diff --git a/app/edit/EditStatus.tsx b/app/edit/EditStatus.tsx
index 6818814..8130039 100644
--- a/app/edit/EditStatus.tsx
+++ b/app/edit/EditStatus.tsx
@@ -5,9 +5,9 @@ import DropDownPicker from "react-native-dropdown-picker";
 import { SaveCancelButtons } from "@/components/formElements/SaveCancelButtons";
 import { LoadingIndicator } from "@/components/LoadingIndicator";
 import { useEditForm } from "@/hooks/useEditForm";
-import { StatusOptionsType } from "@/types/healthRecordTypes";
 import { statusConfigs, statusOptions } from "@/utils/constants";
-import { validators } from "@/utils/validators";
+import { StatusOptionsType } from "@/validation/healthRecordSchema";
+import { validators } from "@/validation/validators";
 
 const EditStatus = () => {
   const { localValue, setLocalValue, handleSave, loading } = useEditForm("status", validators.status);
diff --git a/app/edit/EditSymptoms.tsx b/app/edit/EditSymptoms.tsx
index bc4363f..6d2ff55 100644
--- a/app/edit/EditSymptoms.tsx
+++ b/app/edit/EditSymptoms.tsx
@@ -6,16 +6,16 @@ import { SaveCancelButtons } from "@/components/formElements/SaveCancelButtons";
 import { useDatePicker } from "@/hooks/useDatePicker";
 import { useEditForm } from "@/hooks/useEditForm";
 import { commonStyles } from "@/styles/commonStyles";
-import { Symptom } from "@/types/healthRecordTypes";
 import { addItem, removeItem, updateItemProperty } from "@/utils/arrayHelpers";
 import { SCREEN_LABELS } from "@/utils/constants";
-import { validators } from "@/utils/validators";
+import { Symptom } from "@/validation/healthRecordSchema";
+import { validators } from "@/validation/validators";
 
 const EditSymptoms = () => {
   const { localValue, setLocalValue, handleSave, loading } = useEditForm("symptoms", validators.symptoms);
 
-  const handleDateChange = (index: number, dateString: string) => {
-    setLocalValue(updateItemProperty(localValue, index, "startDate", dateString));
+  const handleDateChange = (index: number, date: Date) => {
+    setLocalValue(updateItemProperty(localValue, index, "startDate", date));
   };
 
   const getSymptomDate = (symptom: Symptom) => (symptom.startDate ? new Date(symptom.startDate) : new Date());
@@ -37,7 +37,7 @@ const EditSymptoms = () => {
             onDismiss={closeDatePicker}
             onConfirm={({ date }) => handleConfirmDate(date)}
             date={getCurrentDate(localValue)}
-            value={symptom.startDate}
+            value={symptom.startDate ? new Date(symptom.startDate).toISOString().split("T")[0] : ""}
             onPress={() => openDatePicker(index)}
           />
           <TouchableOpacity style={commonStyles.btn} onPress={() => setLocalValue(removeItem(localValue, index))}>
@@ -48,9 +48,7 @@ const EditSymptoms = () => {
       <View style={styles.btnContainer}>
         <TouchableOpacity
           style={commonStyles.btn}
-          onPress={() =>
-            setLocalValue(addItem(localValue, { name: "", startDate: new Date().toISOString().split("T")[0] }))
-          }
+          onPress={() => setLocalValue(addItem(localValue, { name: "", startDate: new Date() }))}
         >
           <Text>Add Symptom</Text>
         </TouchableOpacity>
diff --git a/app/edit/EditTreatments.tsx b/app/edit/EditTreatments.tsx
index dd224c5..fd7ed03 100644
--- a/app/edit/EditTreatments.tsx
+++ b/app/edit/EditTreatments.tsx
@@ -6,7 +6,7 @@ import { useEditForm } from "@/hooks/useEditForm";
 import { commonStyles } from "@/styles/commonStyles";
 import { addItem, removeItem, updateItem } from "@/utils/arrayHelpers";
 import { SCREEN_LABELS } from "@/utils/constants";
-import { validators } from "@/utils/validators";
+import { validators } from "@/validation/validators";
 
 const EditTreatments = () => {
   const { localValue, setLocalValue, handleSave, loading } = useEditForm("treatmentsTried", validators.treatmentsTried);
diff --git a/hooks/useDatePicker.ts b/hooks/useDatePicker.ts
index 63731b8..0417d26 100644
--- a/hooks/useDatePicker.ts
+++ b/hooks/useDatePicker.ts
@@ -2,7 +2,7 @@ import { useState } from "react";
 import { CalendarDate } from "react-native-paper-dates/lib/typescript/Date/Calendar";
 
 type UseDatePickerProps<T> = {
-  onDateChange: (index: number, date: string) => void;
+  onDateChange: (index: number, date: Date) => void;
   getItemDate: (item: T) => Date;
 };
 
@@ -19,7 +19,7 @@ export function useDatePicker<T>({ onDateChange, getItemDate }: UseDatePickerPro
 
   const handleConfirmDate = (date: CalendarDate) => {
     if (selectedItemIndex !== null && date) {
-      onDateChange(selectedItemIndex, date.toISOString().split("T")[0]);
+      onDateChange(selectedItemIndex, date);
     }
     closeDatePicker();
   };
diff --git a/hooks/useEditForm.ts b/hooks/useEditForm.ts
index c3f048e..f596215 100644
--- a/hooks/useEditForm.ts
+++ b/hooks/useEditForm.ts
@@ -3,7 +3,7 @@ import { Alert, Platform } from "react-native";
 import { router } from "expo-router";
 
 import useAppStore from "@/store/useAppStore";
-import { HealthRecordType } from "@/types/healthRecordTypes";
+import { HealthRecordType } from "@/validation/healthRecordSchema";
 
 type Validator<T> = (value: T) => { valid: boolean; message: string };
 
diff --git a/package-lock.json b/package-lock.json
index fb8a0d8..945e4b7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,6 +12,7 @@
         "@expo/vector-icons": "^14.0.2",
         "@react-navigation/bottom-tabs": "^7.2.0",
         "@react-navigation/native": "^7.0.14",
+        "axios": "^1.8.2",
         "eslint-plugin-react-native": "^5.0.0",
         "expo": "~52.0.28",
         "expo-blur": "~14.0.3",
@@ -28,13 +29,17 @@
         "react": "18.3.1",
         "react-dom": "18.3.1",
         "react-native": "0.76.6",
+        "react-native-dropdown-picker": "^5.4.6",
         "react-native-gesture-handler": "~2.20.2",
+        "react-native-paper-dates": "^0.22.36",
         "react-native-reanimated": "~3.16.1",
         "react-native-safe-area-context": "4.12.0",
         "react-native-screens": "~4.4.0",
         "react-native-svg": "15.8.0",
         "react-native-web": "~0.19.13",
-        "react-native-webview": "13.12.5"
+        "react-native-webview": "13.12.5",
+        "zod": "^3.25.23",
+        "zustand": "^5.0.3"
       },
       "devDependencies": {
         "@babel/core": "^7.25.2",
@@ -2268,6 +2273,28 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/@callstack/react-theme-provider": {
+      "version": "3.0.9",
+      "resolved": "https://registry.npmjs.org/@callstack/react-theme-provider/-/react-theme-provider-3.0.9.tgz",
+      "integrity": "sha512-tTQ0uDSCL0ypeMa8T/E9wAZRGKWj8kXP7+6RYgPTfOPs9N07C9xM8P02GJ3feETap4Ux5S69D9nteq9mEj86NA==",
+      "peer": true,
+      "dependencies": {
+        "deepmerge": "^3.2.0",
+        "hoist-non-react-statics": "^3.3.0"
+      },
+      "peerDependencies": {
+        "react": ">=16.3.0"
+      }
+    },
+    "node_modules/@callstack/react-theme-provider/node_modules/deepmerge": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz",
+      "integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==",
+      "peer": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/@egjs/hammerjs": {
       "version": "2.0.17",
       "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz",
@@ -6324,6 +6351,30 @@
         "node": ">=4"
       }
     },
+    "node_modules/axios": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
+      "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
+      "dependencies": {
+        "follow-redirects": "^1.15.6",
+        "form-data": "^4.0.0",
+        "proxy-from-env": "^1.1.0"
+      }
+    },
+    "node_modules/axios/node_modules/form-data": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
+      "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "es-set-tostringtag": "^2.1.0",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/axobject-query": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
@@ -9801,6 +9852,25 @@
         "node": ">=0.4.0"
       }
     },
+    "node_modules/follow-redirects": {
+      "version": "1.15.9",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
+      "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/RubenVerborgh"
+        }
+      ],
+      "engines": {
+        "node": ">=4.0"
+      },
+      "peerDependenciesMeta": {
+        "debug": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/fontfaceobserver": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz",
@@ -15421,6 +15491,11 @@
       "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
       "license": "MIT"
     },
+    "node_modules/proxy-from-env": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+    },
     "node_modules/psl": {
       "version": "1.15.0",
       "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
@@ -15734,6 +15809,15 @@
         }
       }
     },
+    "node_modules/react-native-dropdown-picker": {
+      "version": "5.4.6",
+      "resolved": "https://registry.npmjs.org/react-native-dropdown-picker/-/react-native-dropdown-picker-5.4.6.tgz",
+      "integrity": "sha512-T1XBHbE++M6aRU3wFYw3MvcOuabhWZ29RK/Ivdls2r1ZkZ62iEBZknLUPeVLMX3x6iUxj4Zgr3X2DGlEGXeHsA==",
+      "peerDependencies": {
+        "react": "*",
+        "react-native": "*"
+      }
+    },
     "node_modules/react-native-gesture-handler": {
       "version": "2.20.2",
       "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.20.2.tgz",
@@ -15774,6 +15858,61 @@
         "react-native": ">=0.73.0"
       }
     },
+    "node_modules/react-native-paper": {
+      "version": "5.14.5",
+      "resolved": "https://registry.npmjs.org/react-native-paper/-/react-native-paper-5.14.5.tgz",
+      "integrity": "sha512-eaIH5bUQjJ/mYm4AkI6caaiyc7BcHDwX6CqNDi6RIxfxfWxROsHpll1oBuwn/cFvknvA8uEAkqLk/vzVihI3AQ==",
+      "peer": true,
+      "dependencies": {
+        "@callstack/react-theme-provider": "^3.0.9",
+        "color": "^3.1.2",
+        "use-latest-callback": "^0.2.3"
+      },
+      "peerDependencies": {
+        "react": "*",
+        "react-native": "*",
+        "react-native-safe-area-context": "*"
+      }
+    },
+    "node_modules/react-native-paper-dates": {
+      "version": "0.22.42",
+      "resolved": "https://registry.npmjs.org/react-native-paper-dates/-/react-native-paper-dates-0.22.42.tgz",
+      "integrity": "sha512-wPPUUFxs2Ya4Ix5htwPcTwglqxxZTyKeP/xAV/SuqekmH4AlqEUAnkBgSLMk639tYhUSh+i5e7N0eh2nZW3H0A==",
+      "dependencies": {
+        "color": "^4.2.3"
+      },
+      "peerDependencies": {
+        "react": "*",
+        "react-native": "*",
+        "react-native-paper": "*",
+        "react-native-safe-area-context": "*"
+      }
+    },
+    "node_modules/react-native-paper/node_modules/color": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
+      "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==",
+      "peer": true,
+      "dependencies": {
+        "color-convert": "^1.9.3",
+        "color-string": "^1.6.0"
+      }
+    },
+    "node_modules/react-native-paper/node_modules/color-convert": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+      "peer": true,
+      "dependencies": {
+        "color-name": "1.1.3"
+      }
+    },
+    "node_modules/react-native-paper/node_modules/color-name": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+      "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+      "peer": true
+    },
     "node_modules/react-native-reanimated": {
       "version": "3.16.7",
       "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.16.7.tgz",
@@ -19421,6 +19560,42 @@
       "funding": {
         "url": "https://github.com/sponsors/sindresorhus"
       }
+    },
+    "node_modules/zod": {
+      "version": "3.25.23",
+      "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.23.tgz",
+      "integrity": "sha512-Od2bdMosahjSrSgJtakrwjMDb1zM1A3VIHCPGveZt/3/wlrTWBya2lmEh2OYe4OIu8mPTmmr0gnLHIWQXdtWBg==",
+      "funding": {
+        "url": "https://github.com/sponsors/colinhacks"
+      }
+    },
+    "node_modules/zustand": {
+      "version": "5.0.5",
+      "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.5.tgz",
+      "integrity": "sha512-mILtRfKW9xM47hqxGIxCv12gXusoY/xTSHBYApXozR0HmQv299whhBeeAcRy+KrPPybzosvJBCOmVjq6x12fCg==",
+      "engines": {
+        "node": ">=12.20.0"
+      },
+      "peerDependencies": {
+        "@types/react": ">=18.0.0",
+        "immer": ">=9.0.6",
+        "react": ">=18.0.0",
+        "use-sync-external-store": ">=1.2.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        },
+        "immer": {
+          "optional": true
+        },
+        "react": {
+          "optional": true
+        },
+        "use-sync-external-store": {
+          "optional": true
+        }
+      }
     }
   }
 }
diff --git a/package.json b/package.json
index 8f27ac9..ed5eb3e 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
   "version": "1.0.0",
   "scripts": {
     "start": "expo start",
-    "start:mock": "json-server --watch db.json --port 5000",
+    "start:mock": "json-server --watch db.json --port 5555",
     "reset-project": "node ./scripts/reset-project.js",
     "android": "expo start --android",
     "ios": "expo start --ios",
@@ -50,6 +50,7 @@
     "react-native-svg": "15.8.0",
     "react-native-web": "~0.19.13",
     "react-native-webview": "13.12.5",
+    "zod": "^3.25.23",
     "zustand": "^5.0.3"
   },
   "devDependencies": {
diff --git a/scripts/reset-project.js b/scripts/reset-project.js
index 5f81463..7ffd31c 100755
--- a/scripts/reset-project.js
+++ b/scripts/reset-project.js
@@ -6,8 +6,8 @@
  * You can remove the `reset-project` script from package.json and safely delete this file after running it.
  */
 
-const fs = require("fs");
-const path = require("path");
+import fs from "fs";
+import path from "path";
 
 const root = process.cwd();
 const oldDirs = ["app", "components", "hooks", "constants", "scripts"];
diff --git a/store/useAppStore.ts b/store/useAppStore.ts
index 4745ef3..8b2e408 100644
--- a/store/useAppStore.ts
+++ b/store/useAppStore.ts
@@ -1,6 +1,6 @@
 import { create } from "zustand";
 
-import { HealthRecordType } from "@/types/healthRecordTypes";
+import { HealthRecordType } from "@/validation/healthRecordSchema";
 
 interface AppStore {
   loading: boolean;
diff --git a/types/healthRecordTypes.ts b/types/healthRecordTypes.ts
deleted file mode 100644
index 8a6d618..0000000
--- a/types/healthRecordTypes.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-export type Stage = "open" | "closed" | "in-progress";
-export type Severity = "mild" | "moderate" | "severe" | "variable";
-export type Progression = "improving" | "stable" | "worsening" | "variable";
-
-export type StatusOptionsType = {
-  stage: { label: string; value: Stage }[];
-  severity: { label: string; value: Severity }[];
-  progression: { label: string; value: Progression }[];
-};
-
-export interface Symptom {
-  name: string;
-  startDate: string;
-}
-
-export interface MedicalConsultation {
-  consultant: string;
-  date: string;
-  diagnosis: string;
-  followUpActions?: string[];
-}
-
-export interface HealthRecordUpdateType {
-  description?: string;
-  symptoms?: Symptom[];
-  status?: {
-    stage?: Stage;
-    severity?: Severity;
-    progression?: Progression;
-  };
-  treatmentsTried?: string[];
-  medicalConsultations?: MedicalConsultation[];
-}
-
-export interface HealthRecordType {
-  description: string;
-  symptoms: Symptom[];
-  status: {
-    stage: Stage;
-    severity: Severity;
-    progression: Progression;
-  };
-  treatmentsTried?: string[];
-  medicalConsultations?: MedicalConsultation[];
-  updates?: HealthRecordUpdateType[];
-}
diff --git a/utils/constants.ts b/utils/constants.ts
index bf3b71d..bfbb449 100644
--- a/utils/constants.ts
+++ b/utils/constants.ts
@@ -1,4 +1,4 @@
-import { StatusOptionsType } from "../types/healthRecordTypes";
+import { StatusOptionsType } from "../validation/healthRecordSchema";
 
 export const ROUTES = {
   EDIT: {
@@ -51,3 +51,7 @@ export const statusConfigs = [
   { field: "severity", title: "Severity", zIndex: 2000, zIndexInverse: 2000 },
   { field: "progression", title: "Progression", zIndex: 1000, zIndexInverse: 3000 },
 ] as const;
+
+export const MAX_CHAR_SHORT = 100;
+export const MAX_CHAR_MEDIUM = 1000;
+export const MAX_CHAR_LONG = 10_000;
diff --git a/utils/validators.ts b/utils/validators.ts
deleted file mode 100644
index 7679b5a..0000000
--- a/utils/validators.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { MedicalConsultation, Progression, Severity, Stage, Symptom } from "@/types/healthRecordTypes";
-
-export const validators = {
-  description: (value: string) => ({
-    valid: value.trim().length >= 10,
-    message: "Description must be at least 10 characters long!",
-  }),
-
-  symptoms: (symptoms: Symptom[]) => ({
-    valid: symptoms.every((symptom) => symptom.startDate && symptom.name.trim().length > 0),
-    message: "All fields are required!",
-  }),
-
-  treatmentsTried: (treatments: string[]) => ({
-    valid: treatments && treatments.every((treatment) => treatment.trim().length >= 3),
-    message: "Each treatment must be at least 3 characters long!",
-  }),
-
-  status: (status: { stage: Stage; severity: Severity; progression: Progression }) => ({
-    valid:
-      status.stage &&
-      status.severity &&
-      status.progression &&
-      ["open", "closed", "in-progress"].includes(status.stage) &&
-      ["mild", "moderate", "severe", "variable"].includes(status.severity) &&
-      ["improving", "stable", "worsening", "variable"].includes(status.progression),
-    message: "Invalid status value(s)!",
-  }),
-
-  medicalConsultations: (medicalConsultations: MedicalConsultation[]) => ({
-    valid:
-      medicalConsultations &&
-      medicalConsultations.every(
-        (consultation) =>
-          consultation.consultant.trim().length > 0 &&
-          consultation.date &&
-          consultation.diagnosis.trim().length > 0 &&
-          consultation.followUpActions?.every((action) => action.trim().length > 0)
-      ),
-    message: "All fields are required!",
-  }),
-};
diff --git a/validation/healthRecordSchema.ts b/validation/healthRecordSchema.ts
new file mode 100644
index 0000000..3f74073
--- /dev/null
+++ b/validation/healthRecordSchema.ts
@@ -0,0 +1,125 @@
+import { z } from "zod";
+
+import { MAX_CHAR_LONG, MAX_CHAR_MEDIUM, MAX_CHAR_SHORT } from "@/utils/constants";
+import { maxValidationMessage, minValidationMessage } from "@/validation/helpers";
+
+export const Z_Stage = z.enum(["open", "closed", "in-progress"]);
+export const Z_Severity = z.enum(["mild", "moderate", "severe", "variable"]);
+export const Z_Progression = z.enum(["improving", "stable", "worsening", "variable"]);
+
+const Z_LabeledEnumOption = <T extends z.ZodTypeAny>(enumSchema: T) =>
+  z.object({
+    label: z.string(),
+    value: enumSchema,
+  });
+
+export const Z_StatusOptions = z.object({
+  stage: z.array(Z_LabeledEnumOption(Z_Stage)),
+  severity: z.array(Z_LabeledEnumOption(Z_Severity)),
+  progression: z.array(Z_LabeledEnumOption(Z_Progression)),
+});
+
+export const Z_Status = z.object({
+  stage: Z_Stage,
+  severity: Z_Severity,
+  progression: Z_Progression,
+});
+
+export const Z_Description = z
+  .string()
+  .min(10, minValidationMessage("Description", 10))
+  .max(MAX_CHAR_MEDIUM, maxValidationMessage("Description", MAX_CHAR_MEDIUM));
+
+export const Z_Symptom = z.object({
+  name: z
+    .string()
+    .trim()
+    .min(2, minValidationMessage("Symptom", 2))
+    .max(MAX_CHAR_MEDIUM, maxValidationMessage("Symptom", MAX_CHAR_MEDIUM)),
+  startDate: z.date().optional(),
+  // TODO: This is causing some strange behaviour, will address it in the future
+  // startDate: z.date().max(new Date(), "Start date cannot be in the future").optional(),
+});
+
+export const Z_MedicalConsultation = z.object({
+  consultant: z
+    .string()
+    .trim()
+    .min(2, minValidationMessage("Consultant", 2))
+    .max(MAX_CHAR_SHORT, maxValidationMessage("Consultant", MAX_CHAR_SHORT)),
+  date: z.date().refine((date) => date <= new Date(), "Consultation date cannot be in the future"),
+  diagnosis: z
+    .string()
+    .trim()
+    .min(10, minValidationMessage("Diagnosis", 10))
+    .max(MAX_CHAR_LONG, maxValidationMessage("Diagnosis", MAX_CHAR_LONG)),
+  followUpActions: z
+    .array(
+      z
+        .string()
+        .trim()
+        .min(2, minValidationMessage("Follow-up actions", 2))
+        .max(MAX_CHAR_LONG, maxValidationMessage("Follow-up actions", MAX_CHAR_LONG))
+    )
+    .optional()
+    .default([]),
+});
+
+export const Z_HealthRecordUpdate = z.object({
+  description: z.string().optional(),
+  symptoms: z.array(Z_Symptom).optional(),
+  status: z
+    .object({
+      stage: Z_Stage.optional(),
+      severity: Z_Severity.optional(),
+      progression: Z_Progression.optional(),
+    })
+    .optional(),
+  treatmentsTried: z
+    .array(
+      z
+        .string()
+        .trim()
+        .min(2, minValidationMessage("Treatments tried", 2))
+        .max(MAX_CHAR_LONG, maxValidationMessage("Treatments tried", MAX_CHAR_LONG))
+    )
+    .optional()
+    .default([]),
+  medicalConsultations: z.array(Z_MedicalConsultation).optional(),
+});
+
+export const Z_HealthRecord = z.object({
+  description: z.string(),
+  symptoms: z.array(Z_Symptom).min(1, "At least one symptom is required"),
+  status: z.object({
+    stage: Z_Stage,
+    severity: Z_Severity,
+    progression: Z_Progression,
+  }),
+  treatmentsTried: z
+    .array(
+      z
+        .string()
+        .trim()
+        .min(2, minValidationMessage("Treatments tried", 2))
+        .max(MAX_CHAR_LONG, maxValidationMessage("Treatments tried", MAX_CHAR_LONG))
+    )
+    .optional()
+    .default([]),
+  medicalConsultations: z.array(Z_MedicalConsultation).optional(),
+  updates: z.array(Z_HealthRecordUpdate).optional(),
+});
+
+export const Z_SymptomsArray = z.array(Z_Symptom);
+export const Z_MedicalConsultationArray = z.array(Z_MedicalConsultation);
+
+export type Stage = z.infer<typeof Z_Stage>;
+export type Severity = z.infer<typeof Z_Severity>;
+export type Progression = z.infer<typeof Z_Progression>;
+export type StatusOptionsType = z.infer<typeof Z_StatusOptions>;
+export type Status = z.infer<typeof Z_Status>;
+export type Description = z.infer<typeof Z_Description>;
+export type Symptom = z.infer<typeof Z_Symptom>;
+export type MedicalConsultation = z.infer<typeof Z_MedicalConsultation>;
+export type HealthRecordUpdateType = z.infer<typeof Z_HealthRecordUpdate>;
+export type HealthRecordType = z.infer<typeof Z_HealthRecord>;
diff --git a/validation/helpers.ts b/validation/helpers.ts
new file mode 100644
index 0000000..b59fa9e
--- /dev/null
+++ b/validation/helpers.ts
@@ -0,0 +1,7 @@
+export function maxValidationMessage(item: string, max: number) {
+  return `${item} must be less than ${max} characters`;
+}
+
+export function minValidationMessage(item: string, min: number) {
+  return `${item} should be more than ${min} characters`;
+}
diff --git a/validation/validators.ts b/validation/validators.ts
new file mode 100644
index 0000000..1b215f3
--- /dev/null
+++ b/validation/validators.ts
@@ -0,0 +1,63 @@
+import {
+  Description,
+  MedicalConsultation,
+  Progression,
+  Severity,
+  Stage,
+  Symptom,
+  Z_Description,
+  Z_MedicalConsultationArray,
+  Z_Status,
+  Z_SymptomsArray,
+} from "@/validation/healthRecordSchema";
+
+export const validators = {
+  description: (description: Description) => {
+    const result = Z_Description.safeParse(description);
+    return {
+      valid: result.success,
+      message: result.success ? "" : result.error.errors.map((e) => e.message).join(", "),
+    };
+  },
+
+  symptoms: (symptoms: Symptom[]) => {
+    const normalized = symptoms.map((s) => ({
+      ...s,
+      startDate: typeof s.startDate === "string" ? new Date(s.startDate) : s.startDate,
+    }));
+    const result = Z_SymptomsArray.safeParse(normalized);
+
+    return {
+      valid: result.success,
+      message: result.success ? "" : result.error.errors.map((e) => e.message).join(", "),
+    };
+  },
+
+  treatmentsTried: (treatments: string[]) => ({
+    valid: treatments && treatments.every((treatment) => treatment.trim().length >= 3),
+    message: "Each treatment must be at least 3 characters long!",
+  }),
+
+  status: (status: { stage: Stage; severity: Severity; progression: Progression }) => {
+    const result = Z_Status.safeParse(status);
+
+    return {
+      valid: result.success,
+      message: result.success ? "" : result.error.errors.map((e) => e.message).join(", "),
+    };
+  },
+
+  medicalConsultations: (consultations: MedicalConsultation[]) => {
+    const normalized = consultations.map((c) => ({
+      ...c,
+      date: typeof c.date === "string" ? new Date(c.date) : c.date,
+    }));
+
+    const result = Z_MedicalConsultationArray.safeParse(normalized);
+
+    return {
+      valid: result.success,
+      message: result.success ? "" : result.error.errors.map((e) => e.message).join(", "),
+    };
+  },
+};


Reviews:
user: login: sttadic
body: Just practicing code reviews a bit. I won't approve or request changes for this since @AladinJmila will do more detailed review
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-22T12:45:07Z
user: login: sttadic
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-22T12:51:01Z
user: login: sttadic
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-22T12:53:08Z
user: login: danboyce92
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-22T13:02:27Z
user: login: danboyce92
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-22T13:02:48Z
user: login: AladinJmila
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T13:09:03Z
user: login: danboyce92
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-22T13:10:20Z
user: login: AladinJmila
body: Nice progress! Let's give it another round of refining 😄
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T07:49:11Z
user: login: danboyce92
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-23T08:49:12Z
user: login: AladinJmila
body: Group all validation-related files into a validation directory. Please rename `validationHelpers.ts` to `helpers.ts`, and consider renaming `healthRecordValidationSchema.ts` to `healthRecordSchema.ts` since it will live in the validation directory, the context will already be clear from the import path.
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T13:40:32Z
user: login: AladinJmila
body: Nice progress! Finally ready to go into `main` 🎉
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T14:05:02Z

Review comments:
diff_hunk: @@ -117,7 +118,7 @@ const EditConsultations = () => {
             setLocalValue(
               addItem(localValue, {
                 consultant: "",
-                date: new Date().toISOString().split("T")[0],
+                date: new Date(),
                 diagnosis: "",
path: app/edit/EditConsultations.tsx
user: login: sttadic
body: As you mentioned this should be formatted to display properly.
created_at: 2025-05-22T12:36:41Z
author_association: MEMBER
diff_hunk: @@ -49,7 +49,7 @@ const EditSymptoms = () => {
         <TouchableOpacity
           style={commonStyles.btn}
           onPress={() =>
-            setLocalValue(addItem(localValue, { name: "", startDate: new Date().toISOString().split("T")[0] }))
+            setLocalValue(addItem(localValue, { name: "", startDate: new Date() }))
           }
path: app/edit/EditSymptoms.tsx
user: login: sttadic
body: Same, formatting needed here.
created_at: 2025-05-22T12:37:18Z
author_association: MEMBER
diff_hunk: @@ -2,7 +2,7 @@ import axios from "axios";
 
 import { HealthRecordType } from "@/types/healthRecordTypes";
 
-const API_URL = "http://localhost:5000/health-records";
+const API_URL = "http://localhost:5555/health-records";
path: app/api.ts
user: login: sttadic
body: This change is environment specific and should not be added and pushed to remote in my opinion. This could be handled via environmental variables or configs, and dockerizing the frontend. Maybe, for now, exclude api.ts from staging.
created_at: 2025-05-22T12:49:24Z
author_association: MEMBER
diff_hunk: @@ -4,7 +4,7 @@
   "version": "1.0.0",
   "scripts": {
     "start": "expo start",
-    "start:mock": "json-server --watch db.json --port 5000",
+    "start:mock": "json-server --watch db.json --port 5555",
     "reset-project": "node ./scripts/reset-project.js",
path: package.json
user: login: sttadic
body: Same as for `api.ts`, this is a local issue.
created_at: 2025-05-22T12:53:08Z
author_association: MEMBER
diff_hunk: @@ -2,7 +2,7 @@ import axios from "axios";
 
 import { HealthRecordType } from "@/types/healthRecordTypes";
 
-const API_URL = "http://localhost:5000/health-records";
+const API_URL = "http://localhost:5555/health-records";
path: app/api.ts
user: login: danboyce92
body: I'm not sure why but on my machine port 5000 is being used by something else
created_at: 2025-05-22T13:02:27Z
author_association: COLLABORATOR
diff_hunk: @@ -2,7 +2,7 @@ import axios from "axios";
 
 import { HealthRecordType } from "@/types/healthRecordTypes";
 
-const API_URL = "http://localhost:5000/health-records";
+const API_URL = "http://localhost:5555/health-records";
path: app/api.ts
user: login: danboyce92
body: But yeah fair enough I can keep it local
created_at: 2025-05-22T13:02:48Z
author_association: COLLABORATOR
diff_hunk: @@ -2,7 +2,7 @@ import axios from "axios";
 
 import { HealthRecordType } from "@/types/healthRecordTypes";
 
-const API_URL = "http://localhost:5000/health-records";
+const API_URL = "http://localhost:5555/health-records";
path: app/api.ts
user: login: AladinJmila
body: I also ran into the issue where port 5000 was taken by another service. Maybe changing to something else less mainstream would help us avoid this. Probably that's the reason, vite and expo have more random ports numbers when the app is starting.
Dockerising the app is not a high priority for now I think, unless @sttadic you want to tackle it sooner.
created_at: 2025-05-22T13:09:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -117,7 +118,7 @@ const EditConsultations = () => {
             setLocalValue(
               addItem(localValue, {
                 consultant: "",
-                date: new Date().toISOString().split("T")[0],
+                date: new Date(),
                 diagnosis: "",
path: app/edit/EditConsultations.tsx
user: login: danboyce92
body: So the reason all of the 
.toISOString().split("T")[0]
have been removed is because the date type has been changed from string to date object, so .toISOString() doesn't work. That's why I was thinking that a new util might be needed which would format the date Object from Object to string for the UI whenever a date needs to be displayed
created_at: 2025-05-22T13:10:20Z
author_association: COLLABORATOR
path: types/healthRecordTypes.ts
user: login: AladinJmila
body: This file has some formatting issues, but for some reason, it made it through the Husky check. We can verify on the call why this was the case, because it works as expected for me:

![image](https://github.com/user-attachments/assets/e5d6c751-ca1c-48bf-a26b-89a711d0bf7a)
created_at: 2025-05-22T16:43:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,46 +1,160 @@
-export type Stage = "open" | "closed" | "in-progress";
-export type Severity = "mild" | "moderate" | "severe" | "variable";
-export type Progression = "improving" | "stable" | "worsening" | "variable";
-
-export type StatusOptionsType = {
-  stage: { label: string; value: Stage }[];
-  severity: { label: string; value: Severity }[];
-  progression: { label: string; value: Progression }[];
-};
-
-export interface Symptom {
-  name: string;
-  startDate: string;
-}
-
-export interface MedicalConsultation {
-  consultant: string;
-  date: string;
-  diagnosis: string;
-  followUpActions?: string[];
-}
-
-export interface HealthRecordUpdateType {
-  description?: string;
-  symptoms?: Symptom[];
-  status?: {
-    stage?: Stage;
-    severity?: Severity;
-    progression?: Progression;
-  };
-  treatmentsTried?: string[];
-  medicalConsultations?: MedicalConsultation[];
-}
-
-export interface HealthRecordType {
-  description: string;
-  symptoms: Symptom[];
-  status: {
-    stage: Stage;
-    severity: Severity;
-    progression: Progression;
-  };
-  treatmentsTried?: string[];
-  medicalConsultations?: MedicalConsultation[];
-  updates?: HealthRecordUpdateType[];
-}
+import { z } from "zod";
+import {
+  MAX_CHAR_SHORT,
+  MAX_CHAR_MEDIUM,
+  MAX_CHAR_LONG,
+  minValidationMessage,
+  maxValidationMessage,
+} from "@/utils/constants";
+
+export const Z_Stage = z.enum(["open", "closed", "in-progress"]);
+export const Z_Severity = z.enum(["mild", "moderate", "severe", "variable"]);
+export const Z_Progression = z.enum(["improving", "stable", "worsening", "variable"]);
+
+const Z_LabeledEnumOption = <T extends z.ZodTypeAny>(enumSchema: T) =>
+  z.object({
+    label: z.string(),
+    value: enumSchema,
+  });
+
+  export const Z_StatusOptions = z.object({
+    stage: z.array(Z_LabeledEnumOption(Z_Stage)),
+    severity: z.array(Z_LabeledEnumOption(Z_Severity)),
+    progression: z.array(Z_LabeledEnumOption(Z_Progression)),
+  });
+
+  export const Z_Status = z.object({
+    stage: Z_Stage,
+    severity: Z_Severity,
+    progression: Z_Progression,
+  });
+
+// export type Stage = "open" | "closed" | "in-progress";
+// export type Severity = "mild" | "moderate" | "severe" | "variable";
+// export type Progression = "improving" | "stable" | "worsening" | "variable";
+
+// export type StatusOptionsType = {
+//   stage: { label: string; value: Stage }[];
+//   severity: { label: string; value: Severity }[];
+//   progression: { label: string; value: Progression }[];
+// };
+
+export const Z_Description = z.object({
+  description: z
+    .string()
+    .min(10, "Longer description is required")
+    .max(MAX_CHAR_MEDIUM, "Maximum length for description reached"),
+});
path: types/healthRecordTypes.ts
user: login: AladinJmila
body: This needs to be a string value, not an object with a `description` property. This error was flagged when I was testing it, and I got a type mismatch in `EditDescription.tsx`. I'm having a suspicion that Typing and Formatting are not behaving as expected in your environment.
```ts
export const Z_Description = z
  .string()
  .min(10, minValidationMessage("Symptom", 10))
  .max(MAX_CHAR_MEDIUM, maxValidationMessage("Symptom", MAX_CHAR_MEDIUM));
```
created_at: 2025-05-22T17:32:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,42 +1,56 @@
-import { MedicalConsultation, Progression, Severity, Stage, Symptom } from "@/types/healthRecordTypes";
+import { MedicalConsultation, Progression, Severity, Stage, Description, Symptom, Z_Status, Z_SymptomsArray, Z_MedicalConsultationArray } from "@/types/healthRecordTypes";
 
 export const validators = {
-  description: (value: string) => ({
-    valid: value.trim().length >= 10,
+  description: (description: Description) => ({
+    valid: description.description,
     message: "Description must be at least 10 characters long!",
   }),
path: utils/validators.ts
user: login: AladinJmila
body: As a result of the change in `healthRecordTypes.ts` this needs to be updated to the following:
```ts
description: (description: Description) => {
    const result = Z_Description.safeParse(description);
    return {
      valid: result.success,
      message: result.success ? "" : result.error.errors.map((e) => e.message).join(", "),
    };
  },
```
created_at: 2025-05-22T17:34:22Z
author_association: CONTRIBUTOR
path: types/healthRecordTypes.ts
user: login: AladinJmila
body: Rename this to `healthRecordValidationSchemas.ts`. I know that this is a lengthy name, but it's more fitting for the new content. If you have a more suitable alternative, please share.
created_at: 2025-05-22T17:38:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -51,3 +51,16 @@ export const statusConfigs = [
   { field: "severity", title: "Severity", zIndex: 2000, zIndexInverse: 2000 },
   { field: "progression", title: "Progression", zIndex: 1000, zIndexInverse: 3000 },
 ] as const;
+
+
+export const MAX_CHAR_SHORT = 100;
+export const MAX_CHAR_MEDIUM = 1000;
+export const MAX_CHAR_LONG = 10_000;
+
+export function maxValidationMessage(item: string, max: number) {
+  return `${item} must be less than ${max} characters`;
+}
+
+export function minValidationMessage(item: string, min: number) {
+  return `${item} should be more than ${min} characters`;
+}
path: utils/constants.ts
user: login: AladinJmila
body: Move these functions into `validationHelpers.ts`. I know that they are so little to create an entire module just for them, but that's still better than having them part of the `constants` module. Maybe the new module will grow in the future 😅

After a second thought, it looks like we have plenty of files related to validation. Maybe we should move them all to a `validation` directory, which will contain:
- The suggested `validationHelpers.ts` will now be named `helpers.ts`
- `validators.ts`
- `healthRecordTypes.ts`, which should be renamed to something like `healthRecordValidationSchemas.ts`
created_at: 2025-05-22T17:55:00Z
author_association: CONTRIBUTOR
path: types/healthRecordTypes.ts
user: login: AladinJmila
body: Feel free to clean up all the commented code, you're implementation provides more consistent typing, so we don't need those anymore.
created_at: 2025-05-22T17:59:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,46 +1,160 @@
-export type Stage = "open" | "closed" | "in-progress";
-export type Severity = "mild" | "moderate" | "severe" | "variable";
-export type Progression = "improving" | "stable" | "worsening" | "variable";
-
-export type StatusOptionsType = {
-  stage: { label: string; value: Stage }[];
-  severity: { label: string; value: Severity }[];
-  progression: { label: string; value: Progression }[];
-};
-
-export interface Symptom {
-  name: string;
-  startDate: string;
-}
-
-export interface MedicalConsultation {
-  consultant: string;
-  date: string;
-  diagnosis: string;
-  followUpActions?: string[];
-}
-
-export interface HealthRecordUpdateType {
-  description?: string;
-  symptoms?: Symptom[];
-  status?: {
-    stage?: Stage;
-    severity?: Severity;
-    progression?: Progression;
-  };
-  treatmentsTried?: string[];
-  medicalConsultations?: MedicalConsultation[];
-}
-
-export interface HealthRecordType {
-  description: string;
-  symptoms: Symptom[];
-  status: {
-    stage: Stage;
-    severity: Severity;
-    progression: Progression;
-  };
-  treatmentsTried?: string[];
-  medicalConsultations?: MedicalConsultation[];
-  updates?: HealthRecordUpdateType[];
-}
+import { z } from "zod";
+import {
+  MAX_CHAR_SHORT,
+  MAX_CHAR_MEDIUM,
+  MAX_CHAR_LONG,
+  minValidationMessage,
+  maxValidationMessage,
+} from "@/utils/constants";
+
+export const Z_Stage = z.enum(["open", "closed", "in-progress"]);
+export const Z_Severity = z.enum(["mild", "moderate", "severe", "variable"]);
+export const Z_Progression = z.enum(["improving", "stable", "worsening", "variable"]);
+
+const Z_LabeledEnumOption = <T extends z.ZodTypeAny>(enumSchema: T) =>
+  z.object({
+    label: z.string(),
+    value: enumSchema,
+  });
+
+  export const Z_StatusOptions = z.object({
+    stage: z.array(Z_LabeledEnumOption(Z_Stage)),
+    severity: z.array(Z_LabeledEnumOption(Z_Severity)),
+    progression: z.array(Z_LabeledEnumOption(Z_Progression)),
+  });
+
+  export const Z_Status = z.object({
+    stage: Z_Stage,
+    severity: Z_Severity,
+    progression: Z_Progression,
+  });
+
+// export type Stage = "open" | "closed" | "in-progress";
+// export type Severity = "mild" | "moderate" | "severe" | "variable";
+// export type Progression = "improving" | "stable" | "worsening" | "variable";
+
+// export type StatusOptionsType = {
+//   stage: { label: string; value: Stage }[];
+//   severity: { label: string; value: Severity }[];
+//   progression: { label: string; value: Progression }[];
+// };
+
+export const Z_Description = z.object({
+  description: z
+    .string()
+    .min(10, "Longer description is required")
+    .max(MAX_CHAR_MEDIUM, "Maximum length for description reached"),
+});
+
+
+export const Z_Symptom = z.object({
+  name: z
+    .string()
+    .trim()
+    .min(1, "Symptom name is required")
+    .max(MAX_CHAR_MEDIUM, maxValidationMessage("Symptom", MAX_CHAR_MEDIUM)),
+  startDate: z.date().optional(),
+  // TODO: This is causing some strange behaviour, will address it in the future
+  // startDate: z.date().max(new Date(), "Start date cannot be in the future").optional(),
+});
+
+// export interface Symptom {
+//   name: string;
+//   startDate: string;
+// }
+
+export const Z_MedicalConsultation = z.object({
+  consultant: z
+    .string()
+    .trim()
+    .min(2, minValidationMessage("Consultant", 2))
+    .max(MAX_CHAR_SHORT, maxValidationMessage("Consultant", MAX_CHAR_SHORT)),
+  date: z.date().max(new Date(), "Consultation date cannot be in the future"),
+  diagnosis: z
+    .string()
+    .trim()
+    .min(1, "Diagnosis is required")
+    .max(MAX_CHAR_LONG, maxValidationMessage("Diagnosis", MAX_CHAR_LONG)),
+  followUpActions: z
+    .array(
+      z
+        .string()
+        .trim()
+        .min(2, minValidationMessage("Follow-up actions", 2))
+        .max(MAX_CHAR_LONG, maxValidationMessage("Follow-up actions", MAX_CHAR_LONG))
+    )
+    .optional()
+    .default([]),
+});
+
+// export interface MedicalConsultation {
+//   consultant: string;
+//   date: string;
+//   diagnosis: string;
+//   followUpActions?: string[];
+// }
+
+export const Z_HealthRecordUpdate = z.object({
+  description: z.string().optional(),
+  symptoms: z.array(Z_Symptom).optional(),
+  status: z.object({
+    stage: Z_Stage.optional(),
+    severity: Z_Severity.optional(),
+    progression: Z_Progression.optional(),
+  }).optional(),
+  treatmentsTried: z.array(z.string()).optional(),
+  medicalConsultations: z.array(Z_MedicalConsultation).optional(),
+});
+
+// export interface HealthRecordUpdateType {
+//   description?: string;
+//   symptoms?: Symptom[];
+//   status?: {
+//     stage?: Stage;
+//     severity?: Severity;
+//     progression?: Progression;
+//   };
+//   treatmentsTried?: string[];
+//   medicalConsultations?: MedicalConsultation[];
+// }
+
+export const Z_HealthRecord = z.object({
+  description: z.string(),
+  symptoms: z.array(Z_Symptom),
path: types/healthRecordTypes.ts
user: login: AladinJmila
body: Apologies for the confusion, we didn't explain the difference between `HealthRecord` and `HealthRecordUpdate`. We're not fully convinced of the current implementation, but to be consistent with the backend, be aware that for the first creation of the record, we are requiring at least one symptom:
```ts
z.array(Z_Symptoms).min(1, "At least one symptom is required")
```
We will introduce some changes, and we'll also let the UI know if it's dealing with the initial records creation or appending an update to an existing record.
created_at: 2025-05-23T07:01:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,46 +1,160 @@
-export type Stage = "open" | "closed" | "in-progress";
-export type Severity = "mild" | "moderate" | "severe" | "variable";
-export type Progression = "improving" | "stable" | "worsening" | "variable";
-
-export type StatusOptionsType = {
-  stage: { label: string; value: Stage }[];
-  severity: { label: string; value: Severity }[];
-  progression: { label: string; value: Progression }[];
-};
-
-export interface Symptom {
-  name: string;
-  startDate: string;
-}
-
-export interface MedicalConsultation {
-  consultant: string;
-  date: string;
-  diagnosis: string;
-  followUpActions?: string[];
-}
-
-export interface HealthRecordUpdateType {
-  description?: string;
-  symptoms?: Symptom[];
-  status?: {
-    stage?: Stage;
-    severity?: Severity;
-    progression?: Progression;
-  };
-  treatmentsTried?: string[];
-  medicalConsultations?: MedicalConsultation[];
-}
-
-export interface HealthRecordType {
-  description: string;
-  symptoms: Symptom[];
-  status: {
-    stage: Stage;
-    severity: Severity;
-    progression: Progression;
-  };
-  treatmentsTried?: string[];
-  medicalConsultations?: MedicalConsultation[];
-  updates?: HealthRecordUpdateType[];
-}
+import { z } from "zod";
+import {
+  MAX_CHAR_SHORT,
+  MAX_CHAR_MEDIUM,
+  MAX_CHAR_LONG,
+  minValidationMessage,
+  maxValidationMessage,
+} from "@/utils/constants";
+
+export const Z_Stage = z.enum(["open", "closed", "in-progress"]);
+export const Z_Severity = z.enum(["mild", "moderate", "severe", "variable"]);
+export const Z_Progression = z.enum(["improving", "stable", "worsening", "variable"]);
+
+const Z_LabeledEnumOption = <T extends z.ZodTypeAny>(enumSchema: T) =>
+  z.object({
+    label: z.string(),
+    value: enumSchema,
+  });
+
+  export const Z_StatusOptions = z.object({
+    stage: z.array(Z_LabeledEnumOption(Z_Stage)),
+    severity: z.array(Z_LabeledEnumOption(Z_Severity)),
+    progression: z.array(Z_LabeledEnumOption(Z_Progression)),
+  });
+
+  export const Z_Status = z.object({
+    stage: Z_Stage,
+    severity: Z_Severity,
+    progression: Z_Progression,
+  });
+
+// export type Stage = "open" | "closed" | "in-progress";
+// export type Severity = "mild" | "moderate" | "severe" | "variable";
+// export type Progression = "improving" | "stable" | "worsening" | "variable";
+
+// export type StatusOptionsType = {
+//   stage: { label: string; value: Stage }[];
+//   severity: { label: string; value: Severity }[];
+//   progression: { label: string; value: Progression }[];
+// };
+
+export const Z_Description = z.object({
+  description: z
+    .string()
+    .min(10, "Longer description is required")
+    .max(MAX_CHAR_MEDIUM, "Maximum length for description reached"),
+});
+
+
+export const Z_Symptom = z.object({
+  name: z
+    .string()
+    .trim()
+    .min(1, "Symptom name is required")
path: types/healthRecordTypes.ts
user: login: AladinJmila
body: I know you brought this over from the backend, but it was missed there as well. It should be like this:
```ts
 .min(2, minValidationMessage("Symptom", 2))
```
created_at: 2025-05-23T07:08:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,46 +1,160 @@
-export type Stage = "open" | "closed" | "in-progress";
-export type Severity = "mild" | "moderate" | "severe" | "variable";
-export type Progression = "improving" | "stable" | "worsening" | "variable";
-
-export type StatusOptionsType = {
-  stage: { label: string; value: Stage }[];
-  severity: { label: string; value: Severity }[];
-  progression: { label: string; value: Progression }[];
-};
-
-export interface Symptom {
-  name: string;
-  startDate: string;
-}
-
-export interface MedicalConsultation {
-  consultant: string;
-  date: string;
-  diagnosis: string;
-  followUpActions?: string[];
-}
-
-export interface HealthRecordUpdateType {
-  description?: string;
-  symptoms?: Symptom[];
-  status?: {
-    stage?: Stage;
-    severity?: Severity;
-    progression?: Progression;
-  };
-  treatmentsTried?: string[];
-  medicalConsultations?: MedicalConsultation[];
-}
-
-export interface HealthRecordType {
-  description: string;
-  symptoms: Symptom[];
-  status: {
-    stage: Stage;
-    severity: Severity;
-    progression: Progression;
-  };
-  treatmentsTried?: string[];
-  medicalConsultations?: MedicalConsultation[];
-  updates?: HealthRecordUpdateType[];
-}
+import { z } from "zod";
+import {
+  MAX_CHAR_SHORT,
+  MAX_CHAR_MEDIUM,
+  MAX_CHAR_LONG,
+  minValidationMessage,
+  maxValidationMessage,
+} from "@/utils/constants";
+
+export const Z_Stage = z.enum(["open", "closed", "in-progress"]);
+export const Z_Severity = z.enum(["mild", "moderate", "severe", "variable"]);
+export const Z_Progression = z.enum(["improving", "stable", "worsening", "variable"]);
+
+const Z_LabeledEnumOption = <T extends z.ZodTypeAny>(enumSchema: T) =>
+  z.object({
+    label: z.string(),
+    value: enumSchema,
+  });
+
+  export const Z_StatusOptions = z.object({
+    stage: z.array(Z_LabeledEnumOption(Z_Stage)),
+    severity: z.array(Z_LabeledEnumOption(Z_Severity)),
+    progression: z.array(Z_LabeledEnumOption(Z_Progression)),
+  });
+
+  export const Z_Status = z.object({
+    stage: Z_Stage,
+    severity: Z_Severity,
+    progression: Z_Progression,
+  });
+
+// export type Stage = "open" | "closed" | "in-progress";
+// export type Severity = "mild" | "moderate" | "severe" | "variable";
+// export type Progression = "improving" | "stable" | "worsening" | "variable";
+
+// export type StatusOptionsType = {
+//   stage: { label: string; value: Stage }[];
+//   severity: { label: string; value: Severity }[];
+//   progression: { label: string; value: Progression }[];
+// };
+
+export const Z_Description = z.object({
+  description: z
+    .string()
+    .min(10, "Longer description is required")
+    .max(MAX_CHAR_MEDIUM, "Maximum length for description reached"),
+});
+
+
+export const Z_Symptom = z.object({
+  name: z
+    .string()
+    .trim()
+    .min(1, "Symptom name is required")
+    .max(MAX_CHAR_MEDIUM, maxValidationMessage("Symptom", MAX_CHAR_MEDIUM)),
+  startDate: z.date().optional(),
+  // TODO: This is causing some strange behaviour, will address it in the future
+  // startDate: z.date().max(new Date(), "Start date cannot be in the future").optional(),
+});
+
+// export interface Symptom {
+//   name: string;
+//   startDate: string;
+// }
+
+export const Z_MedicalConsultation = z.object({
+  consultant: z
+    .string()
+    .trim()
+    .min(2, minValidationMessage("Consultant", 2))
+    .max(MAX_CHAR_SHORT, maxValidationMessage("Consultant", MAX_CHAR_SHORT)),
+  date: z.date().max(new Date(), "Consultation date cannot be in the future"),
+  diagnosis: z
+    .string()
+    .trim()
+    .min(1, "Diagnosis is required")
+    .max(MAX_CHAR_LONG, maxValidationMessage("Diagnosis", MAX_CHAR_LONG)),
+  followUpActions: z
+    .array(
+      z
+        .string()
+        .trim()
+        .min(2, minValidationMessage("Follow-up actions", 2))
+        .max(MAX_CHAR_LONG, maxValidationMessage("Follow-up actions", MAX_CHAR_LONG))
+    )
+    .optional()
+    .default([]),
+});
+
+// export interface MedicalConsultation {
+//   consultant: string;
+//   date: string;
+//   diagnosis: string;
+//   followUpActions?: string[];
+// }
+
+export const Z_HealthRecordUpdate = z.object({
+  description: z.string().optional(),
+  symptoms: z.array(Z_Symptom).optional(),
+  status: z.object({
+    stage: Z_Stage.optional(),
+    severity: Z_Severity.optional(),
+    progression: Z_Progression.optional(),
+  }).optional(),
+  treatmentsTried: z.array(z.string()).optional(),
path: types/healthRecordTypes.ts
user: login: AladinJmila
body: This is missing some validation rules:
```ts
 treatmentsTried: z
    .array(
      z
        .string()
        .trim()
        .min(2, minValidationMessage("Treatments tried", 2))
        .max(MAX_CHAR_LONG, maxValidationMessage("Treatments tried", MAX_CHAR_LONG))
    )
    .optional()
    .default([]),
```
created_at: 2025-05-23T07:10:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,46 +1,160 @@
-export type Stage = "open" | "closed" | "in-progress";
-export type Severity = "mild" | "moderate" | "severe" | "variable";
-export type Progression = "improving" | "stable" | "worsening" | "variable";
-
-export type StatusOptionsType = {
-  stage: { label: string; value: Stage }[];
-  severity: { label: string; value: Severity }[];
-  progression: { label: string; value: Progression }[];
-};
-
-export interface Symptom {
-  name: string;
-  startDate: string;
-}
-
-export interface MedicalConsultation {
-  consultant: string;
-  date: string;
-  diagnosis: string;
-  followUpActions?: string[];
-}
-
-export interface HealthRecordUpdateType {
-  description?: string;
-  symptoms?: Symptom[];
-  status?: {
-    stage?: Stage;
-    severity?: Severity;
-    progression?: Progression;
-  };
-  treatmentsTried?: string[];
-  medicalConsultations?: MedicalConsultation[];
-}
-
-export interface HealthRecordType {
-  description: string;
-  symptoms: Symptom[];
-  status: {
-    stage: Stage;
-    severity: Severity;
-    progression: Progression;
-  };
-  treatmentsTried?: string[];
-  medicalConsultations?: MedicalConsultation[];
-  updates?: HealthRecordUpdateType[];
-}
+import { z } from "zod";
+import {
+  MAX_CHAR_SHORT,
+  MAX_CHAR_MEDIUM,
+  MAX_CHAR_LONG,
+  minValidationMessage,
+  maxValidationMessage,
+} from "@/utils/constants";
+
+export const Z_Stage = z.enum(["open", "closed", "in-progress"]);
+export const Z_Severity = z.enum(["mild", "moderate", "severe", "variable"]);
+export const Z_Progression = z.enum(["improving", "stable", "worsening", "variable"]);
+
+const Z_LabeledEnumOption = <T extends z.ZodTypeAny>(enumSchema: T) =>
+  z.object({
+    label: z.string(),
+    value: enumSchema,
+  });
+
+  export const Z_StatusOptions = z.object({
+    stage: z.array(Z_LabeledEnumOption(Z_Stage)),
+    severity: z.array(Z_LabeledEnumOption(Z_Severity)),
+    progression: z.array(Z_LabeledEnumOption(Z_Progression)),
+  });
+
+  export const Z_Status = z.object({
+    stage: Z_Stage,
+    severity: Z_Severity,
+    progression: Z_Progression,
+  });
+
+// export type Stage = "open" | "closed" | "in-progress";
+// export type Severity = "mild" | "moderate" | "severe" | "variable";
+// export type Progression = "improving" | "stable" | "worsening" | "variable";
+
+// export type StatusOptionsType = {
+//   stage: { label: string; value: Stage }[];
+//   severity: { label: string; value: Severity }[];
+//   progression: { label: string; value: Progression }[];
+// };
+
+export const Z_Description = z.object({
+  description: z
+    .string()
+    .min(10, "Longer description is required")
+    .max(MAX_CHAR_MEDIUM, "Maximum length for description reached"),
+});
+
+
+export const Z_Symptom = z.object({
+  name: z
+    .string()
+    .trim()
+    .min(1, "Symptom name is required")
+    .max(MAX_CHAR_MEDIUM, maxValidationMessage("Symptom", MAX_CHAR_MEDIUM)),
+  startDate: z.date().optional(),
+  // TODO: This is causing some strange behaviour, will address it in the future
+  // startDate: z.date().max(new Date(), "Start date cannot be in the future").optional(),
+});
+
+// export interface Symptom {
+//   name: string;
+//   startDate: string;
+// }
+
+export const Z_MedicalConsultation = z.object({
+  consultant: z
+    .string()
+    .trim()
+    .min(2, minValidationMessage("Consultant", 2))
+    .max(MAX_CHAR_SHORT, maxValidationMessage("Consultant", MAX_CHAR_SHORT)),
+  date: z.date().max(new Date(), "Consultation date cannot be in the future"),
+  diagnosis: z
+    .string()
+    .trim()
+    .min(1, "Diagnosis is required")
+    .max(MAX_CHAR_LONG, maxValidationMessage("Diagnosis", MAX_CHAR_LONG)),
+  followUpActions: z
+    .array(
+      z
+        .string()
+        .trim()
+        .min(2, minValidationMessage("Follow-up actions", 2))
+        .max(MAX_CHAR_LONG, maxValidationMessage("Follow-up actions", MAX_CHAR_LONG))
+    )
+    .optional()
+    .default([]),
+});
+
+// export interface MedicalConsultation {
+//   consultant: string;
+//   date: string;
+//   diagnosis: string;
+//   followUpActions?: string[];
+// }
+
+export const Z_HealthRecordUpdate = z.object({
+  description: z.string().optional(),
+  symptoms: z.array(Z_Symptom).optional(),
+  status: z.object({
+    stage: Z_Stage.optional(),
+    severity: Z_Severity.optional(),
+    progression: Z_Progression.optional(),
+  }).optional(),
+  treatmentsTried: z.array(z.string()).optional(),
+  medicalConsultations: z.array(Z_MedicalConsultation).optional(),
+});
+
+// export interface HealthRecordUpdateType {
+//   description?: string;
+//   symptoms?: Symptom[];
+//   status?: {
+//     stage?: Stage;
+//     severity?: Severity;
+//     progression?: Progression;
+//   };
+//   treatmentsTried?: string[];
+//   medicalConsultations?: MedicalConsultation[];
+// }
+
+export const Z_HealthRecord = z.object({
+  description: z.string(),
+  symptoms: z.array(Z_Symptom),
+  status: z.object({
+    stage: Z_Stage,
+    severity: Z_Severity,
+    progression: Z_Progression,
+  }),
+  treatmentsTried: z.array(z.string()).optional(),
path: types/healthRecordTypes.ts
user: login: AladinJmila
body: Here as well, it missing some rules:
```ts
 treatmentsTried: z
    .array(
      z
        .string()
        .trim()
        .min(2, minValidationMessage("Treatments tried", 2))
        .max(MAX_CHAR_LONG, maxValidationMessage("Treatments tried", MAX_CHAR_LONG))
    )
    .optional()
    .default([]),
```
created_at: 2025-05-23T07:11:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,46 +1,160 @@
-export type Stage = "open" | "closed" | "in-progress";
-export type Severity = "mild" | "moderate" | "severe" | "variable";
-export type Progression = "improving" | "stable" | "worsening" | "variable";
-
-export type StatusOptionsType = {
-  stage: { label: string; value: Stage }[];
-  severity: { label: string; value: Severity }[];
-  progression: { label: string; value: Progression }[];
-};
-
-export interface Symptom {
-  name: string;
-  startDate: string;
-}
-
-export interface MedicalConsultation {
-  consultant: string;
-  date: string;
-  diagnosis: string;
-  followUpActions?: string[];
-}
-
-export interface HealthRecordUpdateType {
-  description?: string;
-  symptoms?: Symptom[];
-  status?: {
-    stage?: Stage;
-    severity?: Severity;
-    progression?: Progression;
-  };
-  treatmentsTried?: string[];
-  medicalConsultations?: MedicalConsultation[];
-}
-
-export interface HealthRecordType {
-  description: string;
-  symptoms: Symptom[];
-  status: {
-    stage: Stage;
-    severity: Severity;
-    progression: Progression;
-  };
-  treatmentsTried?: string[];
-  medicalConsultations?: MedicalConsultation[];
-  updates?: HealthRecordUpdateType[];
-}
+import { z } from "zod";
+import {
+  MAX_CHAR_SHORT,
+  MAX_CHAR_MEDIUM,
+  MAX_CHAR_LONG,
+  minValidationMessage,
+  maxValidationMessage,
+} from "@/utils/constants";
+
+export const Z_Stage = z.enum(["open", "closed", "in-progress"]);
+export const Z_Severity = z.enum(["mild", "moderate", "severe", "variable"]);
+export const Z_Progression = z.enum(["improving", "stable", "worsening", "variable"]);
+
+const Z_LabeledEnumOption = <T extends z.ZodTypeAny>(enumSchema: T) =>
+  z.object({
+    label: z.string(),
+    value: enumSchema,
+  });
+
+  export const Z_StatusOptions = z.object({
+    stage: z.array(Z_LabeledEnumOption(Z_Stage)),
+    severity: z.array(Z_LabeledEnumOption(Z_Severity)),
+    progression: z.array(Z_LabeledEnumOption(Z_Progression)),
+  });
+
+  export const Z_Status = z.object({
+    stage: Z_Stage,
+    severity: Z_Severity,
+    progression: Z_Progression,
+  });
+
+// export type Stage = "open" | "closed" | "in-progress";
+// export type Severity = "mild" | "moderate" | "severe" | "variable";
+// export type Progression = "improving" | "stable" | "worsening" | "variable";
+
+// export type StatusOptionsType = {
+//   stage: { label: string; value: Stage }[];
+//   severity: { label: string; value: Severity }[];
+//   progression: { label: string; value: Progression }[];
+// };
+
+export const Z_Description = z.object({
+  description: z
+    .string()
+    .min(10, "Longer description is required")
+    .max(MAX_CHAR_MEDIUM, "Maximum length for description reached"),
+});
+
+
+export const Z_Symptom = z.object({
+  name: z
+    .string()
+    .trim()
+    .min(1, "Symptom name is required")
+    .max(MAX_CHAR_MEDIUM, maxValidationMessage("Symptom", MAX_CHAR_MEDIUM)),
+  startDate: z.date().optional(),
+  // TODO: This is causing some strange behaviour, will address it in the future
+  // startDate: z.date().max(new Date(), "Start date cannot be in the future").optional(),
+});
+
+// export interface Symptom {
+//   name: string;
+//   startDate: string;
+// }
+
+export const Z_MedicalConsultation = z.object({
+  consultant: z
+    .string()
+    .trim()
+    .min(2, minValidationMessage("Consultant", 2))
+    .max(MAX_CHAR_SHORT, maxValidationMessage("Consultant", MAX_CHAR_SHORT)),
+  date: z.date().max(new Date(), "Consultation date cannot be in the future"),
+  diagnosis: z
+    .string()
+    .trim()
+    .min(1, "Diagnosis is required")
+    .max(MAX_CHAR_LONG, maxValidationMessage("Diagnosis", MAX_CHAR_LONG)),
+  followUpActions: z
+    .array(
+      z
+        .string()
+        .trim()
+        .min(2, minValidationMessage("Follow-up actions", 2))
+        .max(MAX_CHAR_LONG, maxValidationMessage("Follow-up actions", MAX_CHAR_LONG))
+    )
+    .optional()
+    .default([]),
+});
+
+// export interface MedicalConsultation {
+//   consultant: string;
+//   date: string;
+//   diagnosis: string;
+//   followUpActions?: string[];
+// }
+
+export const Z_HealthRecordUpdate = z.object({
+  description: z.string().optional(),
+  symptoms: z.array(Z_Symptom).optional(),
+  status: z.object({
+    stage: Z_Stage.optional(),
+    severity: Z_Severity.optional(),
+    progression: Z_Progression.optional(),
+  }).optional(),
+  treatmentsTried: z.array(z.string()).optional(),
+  medicalConsultations: z.array(Z_MedicalConsultation).optional(),
+});
+
+// export interface HealthRecordUpdateType {
+//   description?: string;
+//   symptoms?: Symptom[];
+//   status?: {
+//     stage?: Stage;
+//     severity?: Severity;
+//     progression?: Progression;
+//   };
+//   treatmentsTried?: string[];
+//   medicalConsultations?: MedicalConsultation[];
+// }
+
+export const Z_HealthRecord = z.object({
+  description: z.string(),
+  symptoms: z.array(Z_Symptom),
+  status: z.object({
+    stage: Z_Stage,
+    severity: Z_Severity,
+    progression: Z_Progression,
+  }),
+  treatmentsTried: z.array(z.string()).optional(),
+  medicalConsultations: z.array(Z_MedicalConsultation).optional(),
+  updates: z.array(Z_HealthRecordUpdate).optional(),
+});
+
+// export interface HealthRecordType {
+//   description: string;
+//   symptoms: Symptom[];
+//   status: {
+//     stage: Stage;
+//     severity: Severity;
+//     progression: Progression;
+//   };
+//   treatmentsTried?: string[];
+//   medicalConsultations?: MedicalConsultation[];
+//   updates?: HealthRecordUpdateType[];
+// }
+
+export const Z_SymptomsArray = z.array(Z_Symptom);
+export const Z_MedicalConsultationArray = z.array(Z_MedicalConsultation);
+
+
+export type Stage = z.infer<typeof Z_Stage>;
+export type Severity = z.infer<typeof Z_Severity>;
+export type Progression = z.infer<typeof Z_Progression>;
+export type StatusOptionsType = z.infer<typeof Z_StatusOptions>;
+export type Status = z.infer<typeof Z_Status>;
+export type Description = z.infer<typeof Z_Description>;
+export type Symptom = z.infer<typeof Z_Symptom>;
+export type MedicalConsultation = z.infer<typeof Z_MedicalConsultation>;
+export type HealthRecordUpdateType = z.infer<typeof Z_HealthRecordUpdate>;
+export type HealthRecordType = z.infer<typeof Z_HealthRecord>;
path: types/healthRecordTypes.ts
user: login: AladinJmila
body: Nice clean up of all types using Zod 👍
created_at: 2025-05-23T07:12:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,46 +1,160 @@
-export type Stage = "open" | "closed" | "in-progress";
-export type Severity = "mild" | "moderate" | "severe" | "variable";
-export type Progression = "improving" | "stable" | "worsening" | "variable";
-
-export type StatusOptionsType = {
-  stage: { label: string; value: Stage }[];
-  severity: { label: string; value: Severity }[];
-  progression: { label: string; value: Progression }[];
-};
-
-export interface Symptom {
-  name: string;
-  startDate: string;
-}
-
-export interface MedicalConsultation {
-  consultant: string;
-  date: string;
-  diagnosis: string;
-  followUpActions?: string[];
-}
-
-export interface HealthRecordUpdateType {
-  description?: string;
-  symptoms?: Symptom[];
-  status?: {
-    stage?: Stage;
-    severity?: Severity;
-    progression?: Progression;
-  };
-  treatmentsTried?: string[];
-  medicalConsultations?: MedicalConsultation[];
-}
-
-export interface HealthRecordType {
-  description: string;
-  symptoms: Symptom[];
-  status: {
-    stage: Stage;
-    severity: Severity;
-    progression: Progression;
-  };
-  treatmentsTried?: string[];
-  medicalConsultations?: MedicalConsultation[];
-  updates?: HealthRecordUpdateType[];
-}
+import { z } from "zod";
+import {
+  MAX_CHAR_SHORT,
+  MAX_CHAR_MEDIUM,
+  MAX_CHAR_LONG,
+  minValidationMessage,
+  maxValidationMessage,
+} from "@/utils/constants";
+
+export const Z_Stage = z.enum(["open", "closed", "in-progress"]);
+export const Z_Severity = z.enum(["mild", "moderate", "severe", "variable"]);
+export const Z_Progression = z.enum(["improving", "stable", "worsening", "variable"]);
+
+const Z_LabeledEnumOption = <T extends z.ZodTypeAny>(enumSchema: T) =>
+  z.object({
+    label: z.string(),
+    value: enumSchema,
+  });
+
+  export const Z_StatusOptions = z.object({
+    stage: z.array(Z_LabeledEnumOption(Z_Stage)),
+    severity: z.array(Z_LabeledEnumOption(Z_Severity)),
+    progression: z.array(Z_LabeledEnumOption(Z_Progression)),
+  });
+
+  export const Z_Status = z.object({
+    stage: Z_Stage,
+    severity: Z_Severity,
+    progression: Z_Progression,
+  });
+
+// export type Stage = "open" | "closed" | "in-progress";
+// export type Severity = "mild" | "moderate" | "severe" | "variable";
+// export type Progression = "improving" | "stable" | "worsening" | "variable";
+
+// export type StatusOptionsType = {
+//   stage: { label: string; value: Stage }[];
+//   severity: { label: string; value: Severity }[];
+//   progression: { label: string; value: Progression }[];
+// };
+
+export const Z_Description = z.object({
+  description: z
+    .string()
+    .min(10, "Longer description is required")
+    .max(MAX_CHAR_MEDIUM, "Maximum length for description reached"),
+});
+
+
+export const Z_Symptom = z.object({
+  name: z
+    .string()
+    .trim()
+    .min(1, "Symptom name is required")
+    .max(MAX_CHAR_MEDIUM, maxValidationMessage("Symptom", MAX_CHAR_MEDIUM)),
+  startDate: z.date().optional(),
+  // TODO: This is causing some strange behaviour, will address it in the future
+  // startDate: z.date().max(new Date(), "Start date cannot be in the future").optional(),
+});
+
+// export interface Symptom {
+//   name: string;
+//   startDate: string;
+// }
+
+export const Z_MedicalConsultation = z.object({
+  consultant: z
+    .string()
+    .trim()
+    .min(2, minValidationMessage("Consultant", 2))
+    .max(MAX_CHAR_SHORT, maxValidationMessage("Consultant", MAX_CHAR_SHORT)),
+  date: z.date().max(new Date(), "Consultation date cannot be in the future"),
+  diagnosis: z
+    .string()
+    .trim()
+    .min(1, "Diagnosis is required")
path: types/healthRecordTypes.ts
user: login: AladinJmila
body: This one also came as is from the backend, which was not optimal. We can improve it a bit since we don't want short diagnoses either, they would be meaningless:
```ts
.min(10, minValidationMessage("Diagnosis", 10))
```
The entire logic will be revisited when everyone gets their head around the application, so it can be improved. Maybe we'll decide this field can be optional altogether.
created_at: 2025-05-23T07:22:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,46 +1,160 @@
-export type Stage = "open" | "closed" | "in-progress";
-export type Severity = "mild" | "moderate" | "severe" | "variable";
-export type Progression = "improving" | "stable" | "worsening" | "variable";
-
-export type StatusOptionsType = {
-  stage: { label: string; value: Stage }[];
-  severity: { label: string; value: Severity }[];
-  progression: { label: string; value: Progression }[];
-};
-
-export interface Symptom {
-  name: string;
-  startDate: string;
-}
-
-export interface MedicalConsultation {
-  consultant: string;
-  date: string;
-  diagnosis: string;
-  followUpActions?: string[];
-}
-
-export interface HealthRecordUpdateType {
-  description?: string;
-  symptoms?: Symptom[];
-  status?: {
-    stage?: Stage;
-    severity?: Severity;
-    progression?: Progression;
-  };
-  treatmentsTried?: string[];
-  medicalConsultations?: MedicalConsultation[];
-}
-
-export interface HealthRecordType {
-  description: string;
-  symptoms: Symptom[];
-  status: {
-    stage: Stage;
-    severity: Severity;
-    progression: Progression;
-  };
-  treatmentsTried?: string[];
-  medicalConsultations?: MedicalConsultation[];
-  updates?: HealthRecordUpdateType[];
-}
+import { z } from "zod";
+import {
+  MAX_CHAR_SHORT,
+  MAX_CHAR_MEDIUM,
+  MAX_CHAR_LONG,
+  minValidationMessage,
+  maxValidationMessage,
+} from "@/utils/constants";
+
+export const Z_Stage = z.enum(["open", "closed", "in-progress"]);
+export const Z_Severity = z.enum(["mild", "moderate", "severe", "variable"]);
+export const Z_Progression = z.enum(["improving", "stable", "worsening", "variable"]);
+
+const Z_LabeledEnumOption = <T extends z.ZodTypeAny>(enumSchema: T) =>
+  z.object({
+    label: z.string(),
+    value: enumSchema,
+  });
+
+  export const Z_StatusOptions = z.object({
+    stage: z.array(Z_LabeledEnumOption(Z_Stage)),
+    severity: z.array(Z_LabeledEnumOption(Z_Severity)),
+    progression: z.array(Z_LabeledEnumOption(Z_Progression)),
+  });
+
+  export const Z_Status = z.object({
+    stage: Z_Stage,
+    severity: Z_Severity,
+    progression: Z_Progression,
+  });
+
+// export type Stage = "open" | "closed" | "in-progress";
+// export type Severity = "mild" | "moderate" | "severe" | "variable";
+// export type Progression = "improving" | "stable" | "worsening" | "variable";
+
+// export type StatusOptionsType = {
+//   stage: { label: string; value: Stage }[];
+//   severity: { label: string; value: Severity }[];
+//   progression: { label: string; value: Progression }[];
+// };
+
+export const Z_Description = z.object({
+  description: z
+    .string()
+    .min(10, "Longer description is required")
+    .max(MAX_CHAR_MEDIUM, "Maximum length for description reached"),
+});
+
+
+export const Z_Symptom = z.object({
+  name: z
+    .string()
+    .trim()
+    .min(1, "Symptom name is required")
+    .max(MAX_CHAR_MEDIUM, maxValidationMessage("Symptom", MAX_CHAR_MEDIUM)),
+  startDate: z.date().optional(),
+  // TODO: This is causing some strange behaviour, will address it in the future
+  // startDate: z.date().max(new Date(), "Start date cannot be in the future").optional(),
+});
+
+// export interface Symptom {
+//   name: string;
+//   startDate: string;
+// }
+
+export const Z_MedicalConsultation = z.object({
+  consultant: z
+    .string()
+    .trim()
+    .min(2, minValidationMessage("Consultant", 2))
+    .max(MAX_CHAR_SHORT, maxValidationMessage("Consultant", MAX_CHAR_SHORT)),
+  date: z.date().max(new Date(), "Consultation date cannot be in the future"),
path: types/healthRecordTypes.ts
user: login: AladinJmila
body: I found this by chance...
To make sure date validation in Zod always uses the current time, it's better to use .refine() instead of .max(). The .max() method sets a fixed time when the app starts, which can become outdated, especially in mobile apps where users might keep the app running in the background for days or even weeks.
```ts
date: z.date().refine(
  (date) => date <= new Date(),
  "Consultation date cannot be in the future"
)
```


created_at: 2025-05-23T07:45:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,46 +1,160 @@
-export type Stage = "open" | "closed" | "in-progress";
-export type Severity = "mild" | "moderate" | "severe" | "variable";
-export type Progression = "improving" | "stable" | "worsening" | "variable";
-
-export type StatusOptionsType = {
-  stage: { label: string; value: Stage }[];
-  severity: { label: string; value: Severity }[];
-  progression: { label: string; value: Progression }[];
-};
-
-export interface Symptom {
-  name: string;
-  startDate: string;
-}
-
-export interface MedicalConsultation {
-  consultant: string;
-  date: string;
-  diagnosis: string;
-  followUpActions?: string[];
-}
-
-export interface HealthRecordUpdateType {
-  description?: string;
-  symptoms?: Symptom[];
-  status?: {
-    stage?: Stage;
-    severity?: Severity;
-    progression?: Progression;
-  };
-  treatmentsTried?: string[];
-  medicalConsultations?: MedicalConsultation[];
-}
-
-export interface HealthRecordType {
-  description: string;
-  symptoms: Symptom[];
-  status: {
-    stage: Stage;
-    severity: Severity;
-    progression: Progression;
-  };
-  treatmentsTried?: string[];
-  medicalConsultations?: MedicalConsultation[];
-  updates?: HealthRecordUpdateType[];
-}
+import { z } from "zod";
+import {
+  MAX_CHAR_SHORT,
+  MAX_CHAR_MEDIUM,
+  MAX_CHAR_LONG,
+  minValidationMessage,
+  maxValidationMessage,
+} from "@/utils/constants";
+
+export const Z_Stage = z.enum(["open", "closed", "in-progress"]);
+export const Z_Severity = z.enum(["mild", "moderate", "severe", "variable"]);
+export const Z_Progression = z.enum(["improving", "stable", "worsening", "variable"]);
+
+const Z_LabeledEnumOption = <T extends z.ZodTypeAny>(enumSchema: T) =>
+  z.object({
+    label: z.string(),
+    value: enumSchema,
+  });
+
+  export const Z_StatusOptions = z.object({
+    stage: z.array(Z_LabeledEnumOption(Z_Stage)),
+    severity: z.array(Z_LabeledEnumOption(Z_Severity)),
+    progression: z.array(Z_LabeledEnumOption(Z_Progression)),
+  });
+
+  export const Z_Status = z.object({
+    stage: Z_Stage,
+    severity: Z_Severity,
+    progression: Z_Progression,
+  });
+
+// export type Stage = "open" | "closed" | "in-progress";
+// export type Severity = "mild" | "moderate" | "severe" | "variable";
+// export type Progression = "improving" | "stable" | "worsening" | "variable";
+
+// export type StatusOptionsType = {
+//   stage: { label: string; value: Stage }[];
+//   severity: { label: string; value: Severity }[];
+//   progression: { label: string; value: Progression }[];
+// };
+
+export const Z_Description = z.object({
+  description: z
+    .string()
+    .min(10, "Longer description is required")
+    .max(MAX_CHAR_MEDIUM, "Maximum length for description reached"),
+});
+
+
+export const Z_Symptom = z.object({
+  name: z
+    .string()
+    .trim()
+    .min(1, "Symptom name is required")
+    .max(MAX_CHAR_MEDIUM, maxValidationMessage("Symptom", MAX_CHAR_MEDIUM)),
+  startDate: z.date().optional(),
+  // TODO: This is causing some strange behaviour, will address it in the future
+  // startDate: z.date().max(new Date(), "Start date cannot be in the future").optional(),
+});
+
+// export interface Symptom {
+//   name: string;
+//   startDate: string;
+// }
+
+export const Z_MedicalConsultation = z.object({
+  consultant: z
+    .string()
+    .trim()
+    .min(2, minValidationMessage("Consultant", 2))
+    .max(MAX_CHAR_SHORT, maxValidationMessage("Consultant", MAX_CHAR_SHORT)),
+  date: z.date().max(new Date(), "Consultation date cannot be in the future"),
path: types/healthRecordTypes.ts
user: login: danboyce92
body: Good to know, cheers
created_at: 2025-05-23T08:49:12Z
author_association: COLLABORATOR
diff_hunk: @@ -1,42 +1,56 @@
-import { MedicalConsultation, Progression, Severity, Stage, Symptom } from "@/types/healthRecordTypes";
+import { MedicalConsultation, Progression, Severity, Stage, Description, Symptom, Z_Status, Z_SymptomsArray, Z_MedicalConsultationArray } from "@/types/healthRecordTypes";
 
 export const validators = {
-  description: (value: string) => ({
-    valid: value.trim().length >= 10,
+  description: (description: Description) => ({
+    valid: description.description,
     message: "Description must be at least 10 characters long!",
   }),
path: utils/validators.ts
user: login: AladinJmila
body: Use this exact snippet since now we're generating the correct error message.

created_at: 2025-05-23T13:33:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,6 +1,6 @@
 import axios from "axios";
 
-import { HealthRecordType } from "@/types/healthRecordTypes";
+import { HealthRecordType } from "@/types/healthRecordValidationSchemas";
 
 const API_URL = "http://localhost:5000/health-records";
path: app/api.ts
user: login: AladinJmila
body: Would you mind switching the port back to `5555`? As we discussed on the call, we agreed that using less common port numbers helps reduce the chance of conflicts with other services.
created_at: 2025-05-23T13:36:09Z
author_association: CONTRIBUTOR

PR comments:
user: login: danboyce92
created_at: 2025-05-23T08:36:37Z
body: Just spotted all the additional suggestions, will get through them now. Can confirm husky is now working along with the esLinter hopefully it's all good now!
Will work through the additional stuff you've suggested and then double check the error checking is doing what it's supposed to in the app.
Thanks @AladinJmila  & @sttadic for taking the time to look through it!
author_association: COLLABORATOR
user: login: danboyce92
created_at: 2025-05-23T09:32:46Z
body: Date formatting has been fixed and it appears as though all the form's inputs are correctly being checked before save
author_association: COLLABORATOR