fix: add patient form validation and auth proxy

- Fix dateOfBirth default from empty string to null (caused Invalid date error)
- Add noValidate to form to prevent browser native email validation blocking submit
- Reset form when switching from edit to add mode
- Export API_BASE_URL from queryClient; switch patient table to raw fetch (prevents token wipe on 401)
- Add Authorization header forwarding in Vite proxy (was stripped by nginx Connection:upgrade)
- Make only firstName, lastName, dateOfBirth, phone required; gender optional
- Add +1 prefix to phone number input (stores as 1XXXXXXXXXX)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gitead
2026-05-02 13:15:00 -04:00
parent e26ebf7fd5
commit 3d409d4a84
5 changed files with 68 additions and 20 deletions

View File

@@ -67,13 +67,13 @@ export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
...sanitizedPatient,
dateOfBirth: patient.dateOfBirth
? formatLocalDate(new Date(patient.dateOfBirth))
: "",
: null,
};
}
return {
firstName: extractedInfo?.firstName || "",
lastName: extractedInfo?.lastName || "",
dateOfBirth: extractedInfo?.dateOfBirth || "",
dateOfBirth: extractedInfo?.dateOfBirth || null,
gender: "",
phone: "",
email: "",
@@ -119,9 +119,11 @@ export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
...sanitizedPatient,
dateOfBirth: patient.dateOfBirth
? formatLocalDate(new Date(patient.dateOfBirth))
: "",
: null,
};
form.reset(resetValues);
} else {
form.reset(computedDefaultValues);
}
}, [patient, computedDefaultValues, form]);
@@ -134,6 +136,7 @@ export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
<form
id="patient-form"
key={patient?.id || "new"}
noValidate
onSubmit={form.handleSubmit((data) => {
handleSubmit2(data);
})}
@@ -185,7 +188,7 @@ export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
name="gender"
render={({ field }) => (
<FormItem>
<FormLabel>Gender *</FormLabel>
<FormLabel>Gender</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value as string}
@@ -217,15 +220,36 @@ export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
<FormField
control={form.control}
name="phone"
render={({ field }) => (
<FormItem>
<FormLabel>Phone Number *</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
render={({ field }) => {
const display = (field.value as string)?.startsWith("1")
? (field.value as string).slice(1)
: (field.value as string) || "";
return (
<FormItem>
<FormLabel>Phone Number *</FormLabel>
<FormControl>
<div className="flex">
<span className="inline-flex items-center px-3 rounded-l-md border border-r-0 border-input bg-muted text-muted-foreground text-sm">
+1
</span>
<Input
className="rounded-l-none"
placeholder="6175551234"
value={display}
onChange={(e) => {
const digits = e.target.value.replace(/\D/g, "").slice(0, 10);
field.onChange(digits ? "1" + digits : "");
}}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
</div>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
<FormField
@@ -310,7 +334,7 @@ export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
return (
<FormItem>
<FormLabel>Status *</FormLabel>
<FormLabel>Status</FormLabel>
<Select
value={(field.value as PatientStatus) ?? "UNKNOWN"}
onValueChange={(v) =>

View File

@@ -18,7 +18,7 @@ import {
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination";
import { apiRequest, queryClient } from "@/lib/queryClient";
import { apiRequest, queryClient, API_BASE_URL } from "@/lib/queryClient";
import { useMutation, useQuery } from "@tanstack/react-query";
import LoadingScreen from "@/components/ui/LoadingScreen";
import { useToast } from "@/hooks/use-toast";
@@ -749,11 +749,21 @@ export function PatientTable({
url = `/api/patients/recent?limit=${patientsPerPage}&offset=${offset}`;
}
const res = await apiRequest("GET", url);
const token = localStorage.getItem("token");
const res = await fetch(`${API_BASE_URL}${url}`, {
headers: {
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
credentials: "include",
});
if (res.status === 401 || res.status === 403) {
return { patients: [], totalCount: 0 };
}
if (!res.ok) {
const errorData = await res.json();
throw new Error(errorData.message || "Search failed");
const errorData = await res.json().catch(() => ({}));
throw new Error(errorData.message || "Failed to load patients");
}
return res.json();

View File

@@ -1,6 +1,6 @@
import { QueryClient, QueryFunction } from "@tanstack/react-query";
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL_BACKEND ?? "";
export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL_BACKEND ?? "";
async function throwIfResNotOk(res: Response) {
if (!res.ok) {