mirror of
https://github.com/temetro/temetro.git
synced 2026-08-24 17:46:33 +00:00
Wire frontend to the backend: auth, organizations, real patient API
Connects the UI-only app to the new backend over Better Auth + a small patient API client, replacing the in-memory fixture and placeholder identity. - Better Auth React client (lib/auth-client.ts) + shared access-control roles; API client (lib/api-client.ts) sending credentials cross-origin. - Designed (auth) route group: login, signup, verify-email, forgot/reset password, clinic onboarding, and accept-invite. - proxy.ts (this Next's renamed middleware) optimistic redirect + an authoritative client AppAuthGuard requiring a session and active clinic. - Real identity in nav-user (useSession + sign out); the unused team switcher repurposed as an organization (clinic) switcher; Care-team settings tab manages members and invitations. - lib/patients.ts now calls the org-scoped API (types unchanged); patients table and create/edit dialog updated for async create/update. - next.config: standalone output + Dockerfile for containerized runs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -70,7 +70,14 @@ export function ChatPanel() {
|
||||
},
|
||||
]);
|
||||
|
||||
const patient = await getPatient(fileNumber);
|
||||
let patient: Patient | null = null;
|
||||
try {
|
||||
patient = await getPatient(fileNumber);
|
||||
} catch {
|
||||
// Network / auth errors fall through as "not found"; a 401 will have
|
||||
// already redirected to /login via the API client.
|
||||
patient = null;
|
||||
}
|
||||
setMessages((prev) =>
|
||||
prev.map((message) =>
|
||||
message.id === resultId &&
|
||||
|
||||
@@ -16,11 +16,12 @@ import {
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
addPatient,
|
||||
type AllergySeverity,
|
||||
createPatient,
|
||||
generateFileNumber,
|
||||
type LabFlag,
|
||||
type Patient,
|
||||
updatePatient,
|
||||
} from "@/lib/patients";
|
||||
|
||||
type PatientFormDialogProps = {
|
||||
@@ -127,6 +128,9 @@ export function PatientFormDialog({
|
||||
}: PatientFormDialogProps) {
|
||||
const isEdit = mode === "edit";
|
||||
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const [fileNumber, setFileNumber] = useState(() =>
|
||||
isEdit && patient ? patient.fileNumber : generateFileNumber()
|
||||
);
|
||||
@@ -163,9 +167,9 @@ export function PatientFormDialog({
|
||||
})) ?? []
|
||||
);
|
||||
|
||||
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
|
||||
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
if (!name.trim()) {
|
||||
if (!name.trim() || submitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -207,13 +211,25 @@ export function PatientFormDialog({
|
||||
})),
|
||||
};
|
||||
|
||||
addPatient(built);
|
||||
if (isEdit) {
|
||||
onSaved?.(built);
|
||||
} else {
|
||||
onCreated?.(fileNumber);
|
||||
setSubmitting(true);
|
||||
setError(null);
|
||||
try {
|
||||
const saved = isEdit
|
||||
? await updatePatient(built)
|
||||
: await createPatient(built);
|
||||
if (isEdit) {
|
||||
onSaved?.(saved);
|
||||
} else {
|
||||
onCreated?.(saved.fileNumber);
|
||||
}
|
||||
onOpenChange(false);
|
||||
} catch (err) {
|
||||
setError(
|
||||
err instanceof Error ? err.message : "Could not save the patient.",
|
||||
);
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -497,12 +513,15 @@ export function PatientFormDialog({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<DialogFooter className="flex-col items-stretch gap-2 sm:flex-row sm:items-center">
|
||||
{error && (
|
||||
<p className="text-sm text-destructive sm:mr-auto">{error}</p>
|
||||
)}
|
||||
<DialogClose render={<Button type="button" variant="outline" />}>
|
||||
Cancel
|
||||
</DialogClose>
|
||||
<Button disabled={!name.trim()} type="submit">
|
||||
{isEdit ? "Save changes" : "Save patient"}
|
||||
<Button disabled={!name.trim() || submitting} type="submit">
|
||||
{submitting ? "Saving…" : isEdit ? "Save changes" : "Save patient"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user