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:
@@ -67,13 +67,13 @@ export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
|
|||||||
...sanitizedPatient,
|
...sanitizedPatient,
|
||||||
dateOfBirth: patient.dateOfBirth
|
dateOfBirth: patient.dateOfBirth
|
||||||
? formatLocalDate(new Date(patient.dateOfBirth))
|
? formatLocalDate(new Date(patient.dateOfBirth))
|
||||||
: "",
|
: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
firstName: extractedInfo?.firstName || "",
|
firstName: extractedInfo?.firstName || "",
|
||||||
lastName: extractedInfo?.lastName || "",
|
lastName: extractedInfo?.lastName || "",
|
||||||
dateOfBirth: extractedInfo?.dateOfBirth || "",
|
dateOfBirth: extractedInfo?.dateOfBirth || null,
|
||||||
gender: "",
|
gender: "",
|
||||||
phone: "",
|
phone: "",
|
||||||
email: "",
|
email: "",
|
||||||
@@ -119,9 +119,11 @@ export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
|
|||||||
...sanitizedPatient,
|
...sanitizedPatient,
|
||||||
dateOfBirth: patient.dateOfBirth
|
dateOfBirth: patient.dateOfBirth
|
||||||
? formatLocalDate(new Date(patient.dateOfBirth))
|
? formatLocalDate(new Date(patient.dateOfBirth))
|
||||||
: "",
|
: null,
|
||||||
};
|
};
|
||||||
form.reset(resetValues);
|
form.reset(resetValues);
|
||||||
|
} else {
|
||||||
|
form.reset(computedDefaultValues);
|
||||||
}
|
}
|
||||||
}, [patient, computedDefaultValues, form]);
|
}, [patient, computedDefaultValues, form]);
|
||||||
|
|
||||||
@@ -134,6 +136,7 @@ export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
|
|||||||
<form
|
<form
|
||||||
id="patient-form"
|
id="patient-form"
|
||||||
key={patient?.id || "new"}
|
key={patient?.id || "new"}
|
||||||
|
noValidate
|
||||||
onSubmit={form.handleSubmit((data) => {
|
onSubmit={form.handleSubmit((data) => {
|
||||||
handleSubmit2(data);
|
handleSubmit2(data);
|
||||||
})}
|
})}
|
||||||
@@ -185,7 +188,7 @@ export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
|
|||||||
name="gender"
|
name="gender"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Gender *</FormLabel>
|
<FormLabel>Gender</FormLabel>
|
||||||
<Select
|
<Select
|
||||||
onValueChange={field.onChange}
|
onValueChange={field.onChange}
|
||||||
defaultValue={field.value as string}
|
defaultValue={field.value as string}
|
||||||
@@ -217,15 +220,36 @@ export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
|
|||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="phone"
|
name="phone"
|
||||||
render={({ field }) => (
|
render={({ field }) => {
|
||||||
<FormItem>
|
const display = (field.value as string)?.startsWith("1")
|
||||||
<FormLabel>Phone Number *</FormLabel>
|
? (field.value as string).slice(1)
|
||||||
<FormControl>
|
: (field.value as string) || "";
|
||||||
<Input {...field} />
|
return (
|
||||||
</FormControl>
|
<FormItem>
|
||||||
<FormMessage />
|
<FormLabel>Phone Number *</FormLabel>
|
||||||
</FormItem>
|
<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
|
<FormField
|
||||||
@@ -310,7 +334,7 @@ export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Status *</FormLabel>
|
<FormLabel>Status</FormLabel>
|
||||||
<Select
|
<Select
|
||||||
value={(field.value as PatientStatus) ?? "UNKNOWN"}
|
value={(field.value as PatientStatus) ?? "UNKNOWN"}
|
||||||
onValueChange={(v) =>
|
onValueChange={(v) =>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
PaginationNext,
|
PaginationNext,
|
||||||
PaginationPrevious,
|
PaginationPrevious,
|
||||||
} from "@/components/ui/pagination";
|
} 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 { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
import LoadingScreen from "@/components/ui/LoadingScreen";
|
import LoadingScreen from "@/components/ui/LoadingScreen";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
@@ -749,11 +749,21 @@ export function PatientTable({
|
|||||||
url = `/api/patients/recent?limit=${patientsPerPage}&offset=${offset}`;
|
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) {
|
if (!res.ok) {
|
||||||
const errorData = await res.json();
|
const errorData = await res.json().catch(() => ({}));
|
||||||
throw new Error(errorData.message || "Search failed");
|
throw new Error(errorData.message || "Failed to load patients");
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.json();
|
return res.json();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { QueryClient, QueryFunction } from "@tanstack/react-query";
|
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) {
|
async function throwIfResNotOk(res: Response) {
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ export default defineConfig(({ mode }) => {
|
|||||||
"/api": {
|
"/api": {
|
||||||
target: env.VITE_API_BASE_URL_BACKEND || "http://localhost:5000",
|
target: env.VITE_API_BASE_URL_BACKEND || "http://localhost:5000",
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
|
configure: (proxy) => {
|
||||||
|
proxy.on("proxyReq", (proxyReq, req) => {
|
||||||
|
const auth = (req as any).headers["authorization"];
|
||||||
|
if (auth) proxyReq.setHeader("Authorization", auth);
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"/socket.io": {
|
"/socket.io": {
|
||||||
target: env.VITE_API_BASE_URL_BACKEND || "http://localhost:5000",
|
target: env.VITE_API_BASE_URL_BACKEND || "http://localhost:5000",
|
||||||
|
|||||||
@@ -51,7 +51,15 @@ export const insertPatientSchema = (
|
|||||||
createdAt: true,
|
createdAt: true,
|
||||||
})
|
})
|
||||||
.extend({
|
.extend({
|
||||||
insuranceId: insuranceIdSchema, // enforce numeric insuranceId
|
firstName: z.string().min(1, "First name is required"),
|
||||||
|
lastName: z.string().min(1, "Last name is required"),
|
||||||
|
dateOfBirth: z.preprocess(
|
||||||
|
(val) => (val === null || val === undefined || val === "" ? undefined : val),
|
||||||
|
z.coerce.date({ required_error: "Date of birth is required" })
|
||||||
|
),
|
||||||
|
gender: z.string().optional().nullable(),
|
||||||
|
phone: z.string().min(1, "Phone number is required"),
|
||||||
|
insuranceId: insuranceIdSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type InsertPatient = z.infer<typeof insertPatientSchema>;
|
export type InsertPatient = z.infer<typeof insertPatientSchema>;
|
||||||
|
|||||||
Reference in New Issue
Block a user