frontend tailwind half working without external styling

This commit is contained in:
2025-05-09 21:51:02 +05:30
parent ae99e25228
commit 9a431e63db
42 changed files with 1112 additions and 273 deletions

1
.npmrc
View File

@@ -1 +0,0 @@
auto-install-peers = true

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title> <title>Dental Management</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View File

@@ -10,8 +10,6 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@repo/ui": "*",
"@repo/db": "*",
"@hookform/resolvers": "^3.10.0", "@hookform/resolvers": "^3.10.0",
"@jridgewell/trace-mapping": "^0.3.25", "@jridgewell/trace-mapping": "^0.3.25",
"@radix-ui/react-accordion": "^1.2.4", "@radix-ui/react-accordion": "^1.2.4",
@@ -42,8 +40,13 @@
"@radix-ui/react-toggle-group": "^1.1.3", "@radix-ui/react-toggle-group": "^1.1.3",
"@radix-ui/react-tooltip": "^1.2.0", "@radix-ui/react-tooltip": "^1.2.0",
"@replit/vite-plugin-shadcn-theme-json": "^0.0.4", "@replit/vite-plugin-shadcn-theme-json": "^0.0.4",
"@repo/db": "*",
"@repo/tailwind-config": "*",
"@repo/typescript-config": "*",
"@repo/ui": "*",
"@tailwindcss/vite": "^4.1.3", "@tailwindcss/vite": "^4.1.3",
"@tanstack/react-query": "^5.60.5", "@tanstack/react-query": "^5.60.5",
"autoprefixer": "^10.4.21",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.1.1", "cmdk": "^1.1.1",
@@ -59,6 +62,7 @@
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"passport": "^0.7.0", "passport": "^0.7.0",
"passport-local": "^1.0.0", "passport-local": "^1.0.0",
"postcss": "^8.5.3",
"react": "^19.1.0", "react": "^19.1.0",
"react-contexify": "^6.0.0", "react-contexify": "^6.0.0",
"react-day-picker": "^8.10.1", "react-day-picker": "^8.10.1",
@@ -70,6 +74,7 @@
"react-resizable-panels": "^2.1.7", "react-resizable-panels": "^2.1.7",
"recharts": "^2.15.2", "recharts": "^2.15.2",
"tailwind-merge": "^2.6.0", "tailwind-merge": "^2.6.0",
"tailwindcss": "^4.1.5",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"tw-animate-css": "^1.2.5", "tw-animate-css": "^1.2.5",
"vaul": "^1.1.2", "vaul": "^1.1.2",

View File

@@ -0,0 +1,3 @@
import { postcssConfig } from "@repo/tailwind-config/postcss";
export default postcssConfig;

View File

@@ -1,7 +1,28 @@
import { useState } from "react"; import { useState } from "react";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { AppointmentForm } from "./appointment-form"; import { AppointmentForm } from "./appointment-form";
import { Appointment, InsertAppointment, UpdateAppointment, Patient } from "@shared/schema"; // import { Appointment, InsertAppointment, UpdateAppointment, Patient } from "@repo/db/shared/schemas";
import { AppointmentUncheckedCreateInputObjectSchema, PatientUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
import {z} from "zod";
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
const insertAppointmentSchema = (AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
});
type InsertAppointment = z.infer<typeof insertAppointmentSchema>;
const updateAppointmentSchema = (AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
}).partial();
type UpdateAppointment = z.infer<typeof updateAppointmentSchema>;
const PatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
interface AddAppointmentModalProps { interface AddAppointmentModalProps {
open: boolean; open: boolean;

View File

@@ -1,9 +1,30 @@
import { useState, useEffect } from "react"; import { useEffect } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { format } from "date-fns"; import { format } from "date-fns";
import { InsertAppointment, UpdateAppointment, Appointment, Patient } from "@shared/schema"; // import { InsertAppointment, UpdateAppointment, Appointment, Patient } from "@repo/db/shared/schemas";
import { AppointmentUncheckedCreateInputObjectSchema, PatientUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
import {z} from "zod";
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
const insertAppointmentSchema = (AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
});
type InsertAppointment = z.infer<typeof insertAppointmentSchema>;
const updateAppointmentSchema = (AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
}).partial();
type UpdateAppointment = z.infer<typeof updateAppointmentSchema>;
const PatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
// Define staff members (should match those in appointments-page.tsx) // Define staff members (should match those in appointments-page.tsx)
const staffMembers = [ const staffMembers = [
@@ -36,7 +57,6 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover
import { CalendarIcon, Clock } from "lucide-react"; import { CalendarIcon, Clock } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
// Create a schema for appointment validation
const appointmentSchema = z.object({ const appointmentSchema = z.object({
patientId: z.coerce.number().positive(), patientId: z.coerce.number().positive(),
title: z.string().optional(), title: z.string().optional(),
@@ -52,7 +72,7 @@ const appointmentSchema = z.object({
type: z.string().min(1, "Appointment type is required"), type: z.string().min(1, "Appointment type is required"),
notes: z.string().optional(), notes: z.string().optional(),
status: z.string().default("scheduled"), status: z.string().default("scheduled"),
staff: z.string().default(staffMembers[0].id), staff: z.string().default(staffMembers?.[0]?.id ?? "default-id"),
}); });
export type AppointmentFormValues = z.infer<typeof appointmentSchema>; export type AppointmentFormValues = z.infer<typeof appointmentSchema>;
@@ -96,8 +116,8 @@ export function AppointmentForm({
patientId: appointment.patientId, patientId: appointment.patientId,
title: appointment.title, title: appointment.title,
date: new Date(appointment.date), date: new Date(appointment.date),
startTime: appointment.startTime.slice(0, 5), // HH:MM from HH:MM:SS startTime: typeof appointment.startTime === 'string' ? appointment.startTime.slice(0, 5) : "",
endTime: appointment.endTime.slice(0, 5), // HH:MM from HH:MM:SS endTime: typeof appointment.endTime === 'string' ? appointment.endTime.slice(0, 5) : "",
type: appointment.type, type: appointment.type,
notes: appointment.notes || "", notes: appointment.notes || "",
status: appointment.status || "scheduled", status: appointment.status || "scheduled",
@@ -112,7 +132,7 @@ export function AppointmentForm({
type: parsedStoredData.type || "checkup", type: parsedStoredData.type || "checkup",
status: parsedStoredData.status || "scheduled", status: parsedStoredData.status || "scheduled",
notes: parsedStoredData.notes || "", notes: parsedStoredData.notes || "",
staff: parsedStoredData.staff || staffMembers[0].id, staff: parsedStoredData.staff || (staffMembers?.[0]?.id ?? "default-id")
} }
: { : {
date: new Date(), date: new Date(),
@@ -187,7 +207,7 @@ export function AppointmentForm({
// If there's no staff information in the notes, add it // If there's no staff information in the notes, add it
if (!notes.includes('Appointment with')) { if (!notes.includes('Appointment with')) {
notes = notes ? `${notes}\nAppointment with ${selectedStaff.name}` : `Appointment with ${selectedStaff.name}`; notes = notes ? `${notes}\nAppointment with ${selectedStaff?.name}` : `Appointment with ${selectedStaff?.name}`;
} }
onSubmit({ onSubmit({

View File

@@ -1,6 +1,4 @@
import { useState } from "react";
import { format } from "date-fns"; import { format } from "date-fns";
import { Appointment, Patient } from "@shared/schema";
import { import {
Table, Table,
TableBody, TableBody,
@@ -11,13 +9,13 @@ import {
} from "@/components/ui/table"; } from "@/components/ui/table";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { import {
MoreHorizontal, MoreHorizontal,
Edit, Edit,
Trash2, Trash2,
Eye, Eye,
Calendar, Calendar,
Clock Clock,
} from "lucide-react"; } from "lucide-react";
import { import {
DropdownMenu, DropdownMenu,
@@ -27,6 +25,21 @@ import {
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
// import { Appointment, Patient } from "@repo/db/shared/schemas";
import {
AppointmentUncheckedCreateInputObjectSchema,
PatientUncheckedCreateInputObjectSchema,
} from "@repo/db/shared/schemas";
import { z } from "zod";
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
const PatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
interface AppointmentTableProps { interface AppointmentTableProps {
appointments: Appointment[]; appointments: Appointment[];
@@ -35,21 +48,34 @@ interface AppointmentTableProps {
onDelete: (id: number) => void; onDelete: (id: number) => void;
} }
export function AppointmentTable({ export function AppointmentTable({
appointments, appointments,
patients, patients,
onEdit, onEdit,
onDelete onDelete,
}: AppointmentTableProps) { }: AppointmentTableProps) {
// Helper function to get patient name // Helper function to get patient name
const getPatientName = (patientId: number) => { const getPatientName = (patientId: number) => {
const patient = patients.find(p => p.id === patientId); const patient = patients.find((p) => p.id === patientId);
return patient ? `${patient.firstName} ${patient.lastName}` : "Unknown Patient"; return patient
? `${patient.firstName} ${patient.lastName}`
: "Unknown Patient";
}; };
// Helper function to get status badge // Helper function to get status badge
const getStatusBadge = (status: string) => { const getStatusBadge = (status: string) => {
const statusConfig: Record<string, { variant: "default" | "secondary" | "destructive" | "outline" | "success"; label: string }> = { const statusConfig: Record<
string,
{
variant:
| "default"
| "secondary"
| "destructive"
| "outline"
| "success";
label: string;
}
> = {
scheduled: { variant: "default", label: "Scheduled" }, scheduled: { variant: "default", label: "Scheduled" },
confirmed: { variant: "secondary", label: "Confirmed" }, confirmed: { variant: "secondary", label: "Confirmed" },
completed: { variant: "success", label: "Completed" }, completed: { variant: "success", label: "Completed" },
@@ -57,20 +83,20 @@ export function AppointmentTable({
"no-show": { variant: "outline", label: "No Show" }, "no-show": { variant: "outline", label: "No Show" },
}; };
const config = statusConfig[status] || { variant: "default", label: status }; const config = statusConfig[status] || {
variant: "default",
return ( label: status,
<Badge variant={config.variant as any}> };
{config.label}
</Badge> return <Badge variant={config.variant as any}>{config.label}</Badge>;
);
}; };
// Sort appointments by date and time (newest first) // Sort appointments by date and time (newest first)
const sortedAppointments = [...appointments].sort((a, b) => { const sortedAppointments = [...appointments].sort((a, b) => {
const dateComparison = new Date(b.date).getTime() - new Date(a.date).getTime(); const dateComparison =
new Date(b.date).getTime() - new Date(a.date).getTime();
if (dateComparison !== 0) return dateComparison; if (dateComparison !== 0) return dateComparison;
return a.startTime.localeCompare(b.startTime); return a.startTime.toString().localeCompare(b.startTime.toString());
}); });
return ( return (
@@ -108,15 +134,20 @@ export function AppointmentTable({
<TableCell> <TableCell>
<div className="flex items-center"> <div className="flex items-center">
<Clock className="mr-2 h-4 w-4 text-muted-foreground" /> <Clock className="mr-2 h-4 w-4 text-muted-foreground" />
{appointment.startTime.slice(0, 5)} - {appointment.endTime.slice(0, 5)} {appointment.startTime instanceof Date
? appointment.startTime.toISOString().slice(11, 16)
: appointment.startTime.slice(0, 5)}{" "}
-
{appointment.endTime instanceof Date
? appointment.endTime.toISOString().slice(11, 16)
: appointment.endTime.slice(0, 5)}
{/* {appointment.startTime.slice(0, 5)} - {appointment.endTime.slice(0, 5)} */}
</div> </div>
</TableCell> </TableCell>
<TableCell className="capitalize"> <TableCell className="capitalize">
{appointment.type.replace('-', ' ')} {appointment.type.replace("-", " ")}
</TableCell>
<TableCell>
{getStatusBadge(appointment.status!)}
</TableCell> </TableCell>
<TableCell>{getStatusBadge(appointment.status!)}</TableCell>
<TableCell className="text-right"> <TableCell className="text-right">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
@@ -132,8 +163,14 @@ export function AppointmentTable({
Edit Edit
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItem <DropdownMenuItem
onClick={() => onDelete(appointment.id)} onClick={() => {
if (typeof appointment.id === "number") {
onDelete(appointment.id);
} else {
console.error("Invalid appointment ID");
}
}}
className="text-destructive focus:text-destructive" className="text-destructive focus:text-destructive"
> >
<Trash2 className="mr-2 h-4 w-4" /> <Trash2 className="mr-2 h-4 w-4" />
@@ -149,4 +186,4 @@ export function AppointmentTable({
</Table> </Table>
</div> </div>
); );
} }

View File

@@ -1,21 +1,28 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { Button } from "../ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "../ui/input"; import { Input } from "@/components/ui/input";
import { import {
Select, Select,
SelectContent, SelectContent,
SelectItem, SelectItem,
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "../ui/select"; } from "@/components/ui/select";
import { format, parse } from "date-fns"; import { format, parse } from "date-fns";
import { Patient } from "@shared/schema"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"; import { Label } from "@/components/ui/label";
import { Label } from "../ui/label";
import { X, Calendar as CalendarIcon } from "lucide-react"; import { X, Calendar as CalendarIcon } from "lucide-react";
import { useToast } from "../../hooks/use-toast"; import { useToast } from "@/hooks/use-toast";
import { Calendar } from "../ui/calendar"; import { Calendar } from "@/components/ui/calendar";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
// import { Patient } from "@repo/db/shared/schemas";
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
import {z} from "zod";
const PatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
interface ClaimFormProps { interface ClaimFormProps {
patientId: number; patientId: number;

View File

@@ -1,6 +1,13 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { ClaimForm } from "./claim-form"; import { ClaimForm } from "./claim-form";
import { Patient } from "@shared/schema"; // import { Patient } from "@repo/db/shared/schemas";
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
import {z} from "zod";
const PatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
interface ClaimModalProps { interface ClaimModalProps {
open: boolean; open: boolean;

View File

@@ -1,6 +1,5 @@
import { Link, useLocation } from "wouter"; import { Link, useLocation } from "wouter";
import { Search, LayoutDashboard, Users, Calendar, FileText, Settings } from "lucide-react"; import { LayoutDashboard, Users, Calendar, FileText, Settings } from "lucide-react";
import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
interface SidebarProps { interface SidebarProps {

View File

@@ -1,5 +1,4 @@
import { Bell, Menu, Search } from "lucide-react"; import { Bell, Menu} from "lucide-react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { useAuth } from "@/hooks/use-auth"; import { useAuth } from "@/hooks/use-auth";

View File

@@ -9,10 +9,32 @@ import {
DialogFooter, DialogFooter,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { PatientForm } from "./patient-form"; import { PatientForm } from "./patient-form";
import { InsertPatient, Patient, UpdatePatient } from "@shared/schema";
import { useToast } from "@/hooks/use-toast"; import { useToast } from "@/hooks/use-toast";
import { X, Calendar } from "lucide-react"; import { X, Calendar } from "lucide-react";
import { useLocation } from "wouter"; import { useLocation } from "wouter";
// import { InsertPatient, Patient, UpdatePatient } from "@repo/db/shared/schemas";
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
import {z} from "zod";
const PatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
const insertPatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
});
type InsertPatient = z.infer<typeof insertPatientSchema>;
const updatePatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
userId: true,
}).partial();
type UpdatePatient = z.infer<typeof updatePatientSchema>;
interface AddPatientModalProps { interface AddPatientModalProps {
open: boolean; open: boolean;

View File

@@ -1,7 +1,8 @@
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import { insertPatientSchema, InsertPatient, Patient, updatePatientSchema, UpdatePatient } from "@shared/schema"; import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
// import { insertPatientSchema, InsertPatient, Patient, updatePatientSchema, UpdatePatient } from "@repo/db/shared/schemas";
import { useAuth } from "@/hooks/use-auth"; import { useAuth } from "@/hooks/use-auth";
import { import {
Form, Form,
@@ -21,6 +22,26 @@ import {
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
const PatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
const insertPatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
});
type InsertPatient = z.infer<typeof insertPatientSchema>;
const updatePatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
userId: true,
}).partial();
type UpdatePatient = z.infer<typeof updatePatientSchema>;
interface PatientFormProps { interface PatientFormProps {
patient?: Patient; patient?: Patient;
extractedInfo?: { extractedInfo?: {

View File

@@ -1,5 +1,4 @@
import { useState } from "react"; import { useState } from "react";
import { Patient } from "@shared/schema";
import { import {
Table, Table,
TableBody, TableBody,
@@ -26,6 +25,15 @@ import {
PaginationNext, PaginationNext,
PaginationPrevious, PaginationPrevious,
} from "@/components/ui/pagination"; } from "@/components/ui/pagination";
// import { Patient } from "@repo/db/shared/schemas";
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
import {z} from "zod";
const PatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
interface PatientTableProps { interface PatientTableProps {
patients: Patient[]; patients: Patient[];

View File

@@ -15,6 +15,8 @@ const badgeVariants = cva(
destructive: destructive:
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
outline: "text-foreground", outline: "text-foreground",
success: "border-transparent bg-success text-success-foreground hover:bg-success/80", // ✅ Added success variant
warning: "border-transparent bg-warning text-warning-foreground hover:bg-warning/80", // ✅ Added warning variant
}, },
}, },
defaultVariants: { defaultVariants: {

View File

@@ -4,9 +4,25 @@ import {
useMutation, useMutation,
UseMutationResult, UseMutationResult,
} from "@tanstack/react-query"; } from "@tanstack/react-query";
import { insertUserSchema, User as SelectUser, InsertUser } from "@shared/schema"; // import { insertUserSchema, User as SelectUser, InsertUser } from "@repo/db/shared/schemas";
import { UserUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
import {z} from "zod";
import { getQueryFn, apiRequest, queryClient } from "../lib/queryClient"; import { getQueryFn, apiRequest, queryClient } from "../lib/queryClient";
import { useToast } from "../hooks/use-toast"; import { useToast } from "@/hooks/use-toast";
type SelectUser = z.infer<typeof UserUncheckedCreateInputObjectSchema>;
const insertUserSchema = (UserUncheckedCreateInputObjectSchema as unknown as z.ZodObject<{
username: z.ZodString;
password: z.ZodString;
}>).pick({
username: true,
password: true,
});
type InsertUser = z.infer<typeof insertUserSchema>;
type AuthContextType = { type AuthContextType = {
user: SelectUser | null; user: SelectUser | null;
@@ -17,7 +33,11 @@ type AuthContextType = {
registerMutation: UseMutationResult<SelectUser, Error, InsertUser>; registerMutation: UseMutationResult<SelectUser, Error, InsertUser>;
}; };
type LoginData = Pick<InsertUser, "username" | "password">; // type LoginData = Pick<InsertUser, "username" | "password">;
type LoginData = {
username: InsertUser["username"];
password: InsertUser["password"];
};
export const AuthContext = createContext<AuthContextType | null>(null); export const AuthContext = createContext<AuthContextType | null>(null);
export function AuthProvider({ children }: { children: ReactNode }) { export function AuthProvider({ children }: { children: ReactNode }) {

View File

@@ -3,7 +3,7 @@ import * as React from "react"
import type { import type {
ToastActionElement, ToastActionElement,
ToastProps, ToastProps,
} from "../components/ui/toast" } from "@/components/ui/toast"
const TOAST_LIMIT = 1 const TOAST_LIMIT = 1
const TOAST_REMOVE_DELAY = 1000000 const TOAST_REMOVE_DELAY = 1000000

View File

@@ -1,3 +1,3 @@
@import "tailwindcss"; @import "tailwindcss";
@import "@repo/tailwind-config"; @import "@repo/tailwind-config";
@import "@repo/ui/styles.css"; @import "@repo/ui/styles.css";

View File

@@ -1,4 +1,4 @@
import { useAuth } from "../hooks/use-auth"; import { useAuth } from "@/hooks/use-auth";
import { Loader2 } from "lucide-react"; import { Loader2 } from "lucide-react";
import { Redirect, Route } from "wouter"; import { Redirect, Route } from "wouter";

View File

@@ -1,6 +1,6 @@
import { createRoot } from 'react-dom/client' import { createRoot } from 'react-dom/client'
import './index.css' import './index.css'
import App from './App.tsx' import App from './App'
document.title = "DentalConnect - Patient Management System"; document.title = "DentalConnect - Patient Management System";

View File

@@ -1,15 +1,15 @@
import { useState, useEffect } from "react"; import { useState, useRef, useEffect } from "react";
import { useQuery, useMutation } from "@tanstack/react-query"; import { useQuery, useMutation } from "@tanstack/react-query";
import { format, addDays, startOfToday, addMinutes } from "date-fns"; import { format, addDays, parse, startOfToday, startOfDay, addMinutes, isEqual } from "date-fns";
import { TopAppBar } from "../components/layout/top-app-bar"; import { TopAppBar } from "@/components/layout/top-app-bar";
import { Sidebar } from "../components/layout/sidebar"; import { Sidebar } from "@/components/layout/sidebar";
import { AddAppointmentModal } from "../components/appointments/add-appointment-modal"; import { AddAppointmentModal } from "@/components/appointments/add-appointment-modal";
import { ClaimModal } from "../components/claims/claim-modal"; import { ClaimModal } from "@/components/claims/claim-modal";
import { Button } from "../components/ui/button"; import { Button } from "@/components/ui/button";
import { z } from "zod";
import { import {
Calendar as CalendarIcon, Calendar as CalendarIcon,
Plus, Plus,
Users,
ChevronLeft, ChevronLeft,
ChevronRight, ChevronRight,
RefreshCw, RefreshCw,
@@ -17,17 +17,42 @@ import {
Trash2, Trash2,
FileText FileText
} from "lucide-react"; } from "lucide-react";
import { useToast } from "../hooks/use-toast"; import { useToast } from "@/hooks/use-toast";
import { Calendar } from "../components/ui/calendar"; import { z } from "zod";
import { apiRequest, queryClient } from "../lib/queryClient"; import { Calendar } from "@/components/ui/calendar";
import { useAuth } from "../hooks/use-auth"; import { AppointmentUncheckedCreateInputObjectSchema, PatientUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
import { Card, CardContent, CardHeader, CardDescription, CardTitle } from "../components/ui/card"; // import { Appointment, InsertAppointment, UpdateAppointment, Patient } from "@repo/db/shared/schemas";
import { apiRequest, queryClient } from "@/lib/queryClient";
import { useAuth } from "@/hooks/use-auth";
import { Card, CardContent, CardHeader, CardDescription, CardTitle } from "@/components/ui/card";
import { DndProvider, useDrag, useDrop } from 'react-dnd'; import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend'; import { HTML5Backend } from 'react-dnd-html5-backend';
import { Menu, Item, useContextMenu } from 'react-contexify'; import { Menu, Item, useContextMenu } from 'react-contexify';
import 'react-contexify/ReactContexify.css'; import 'react-contexify/ReactContexify.css';
import { useLocation } from "wouter"; import { useLocation } from "wouter";
//creating types out of schema auto generated.
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
const insertAppointmentSchema = (AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
});
type InsertAppointment = z.infer<typeof insertAppointmentSchema>;
const updateAppointmentSchema = (AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
}).partial();
type UpdateAppointment = z.infer<typeof updateAppointmentSchema>;
const PatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
// Define types for scheduling // Define types for scheduling
interface TimeSlot { interface TimeSlot {
time: string; time: string;
@@ -42,13 +67,13 @@ interface Staff {
} }
interface ScheduledAppointment { interface ScheduledAppointment {
id: number; id?: number;
patientId: number; patientId: number;
patientName: string; patientName: string;
staffId: string; staffId: string;
date: string; date: string | Date; // Allow both string and Date
startTime: string; startTime: string | Date; // Allow both string and Date
endTime: string; endTime: string | Date; // Allow both string and Date
status: string | null; status: string | null;
type: string; type: string;
} }
@@ -63,7 +88,7 @@ export default function AppointmentsPage() {
const [isClaimModalOpen, setIsClaimModalOpen] = useState(false); const [isClaimModalOpen, setIsClaimModalOpen] = useState(false);
const [claimAppointmentId, setClaimAppointmentId] = useState<number | null>(null); const [claimAppointmentId, setClaimAppointmentId] = useState<number | null>(null);
const [claimPatientId, setClaimPatientId] = useState<number | null>(null); const [claimPatientId, setClaimPatientId] = useState<number | null>(null);
const [editingAppointment, setEditingAppointment] = useState<any | undefined>(undefined); const [editingAppointment, setEditingAppointment] = useState<Appointment | undefined>(undefined);
const [selectedDate, setSelectedDate] = useState<Date>(startOfToday()); const [selectedDate, setSelectedDate] = useState<Date>(startOfToday());
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [location] = useLocation(); const [location] = useLocation();
@@ -96,10 +121,10 @@ export default function AppointmentsPage() {
// Fetch appointments // Fetch appointments
const { const {
data: appointments, data: appointments = [] as Appointment[],
isLoading: isLoadingAppointments, isLoading: isLoadingAppointments,
refetch: refetchAppointments, refetch: refetchAppointments,
} = useQuery<any>({ } = useQuery<Appointment[]>({
queryKey: ["/api/appointments"], queryKey: ["/api/appointments"],
enabled: !!user, enabled: !!user,
}); });
@@ -108,7 +133,7 @@ export default function AppointmentsPage() {
const { const {
data: patients = [], data: patients = [],
isLoading: isLoadingPatients, isLoading: isLoadingPatients,
} = useQuery<any>({ } = useQuery<Patient[]>({
queryKey: ["/api/patients"], queryKey: ["/api/patients"],
enabled: !!user, enabled: !!user,
}); });
@@ -167,8 +192,7 @@ export default function AppointmentsPage() {
if (newPatientId) { if (newPatientId) {
const patientId = parseInt(newPatientId); const patientId = parseInt(newPatientId);
// Find the patient in our list // Find the patient in our list
const patient = patients.find((p: { id: number }) => p.id === patientId) ?? undefined; const patient = (patients as Patient[]).find(p => p.id === patientId);
if (patient) { if (patient) {
toast({ toast({
@@ -177,7 +201,6 @@ export default function AppointmentsPage() {
}); });
// Select first available staff member // Select first available staff member
const staffId = staffMembers[0]!.id; const staffId = staffMembers[0]!.id;
// Find first time slot today (9:00 AM is a common starting time) // Find first time slot today (9:00 AM is a common starting time)
@@ -207,7 +230,7 @@ export default function AppointmentsPage() {
// Create appointment mutation // Create appointment mutation
const createAppointmentMutation = useMutation({ const createAppointmentMutation = useMutation({
mutationFn: async (appointment: any) => { mutationFn: async (appointment: InsertAppointment) => {
const res = await apiRequest("POST", "/api/appointments", appointment); const res = await apiRequest("POST", "/api/appointments", appointment);
return await res.json(); return await res.json();
}, },
@@ -230,7 +253,7 @@ export default function AppointmentsPage() {
// Update appointment mutation // Update appointment mutation
const updateAppointmentMutation = useMutation({ const updateAppointmentMutation = useMutation({
mutationFn: async ({ id, appointment }: { id: number; appointment: any }) => { mutationFn: async ({ id, appointment }: { id: number; appointment: UpdateAppointment }) => {
const res = await apiRequest("PUT", `/api/appointments/${id}`, appointment); const res = await apiRequest("PUT", `/api/appointments/${id}`, appointment);
return await res.json(); return await res.json();
}, },
@@ -274,7 +297,7 @@ export default function AppointmentsPage() {
}); });
// Handle appointment submission (create or update) // Handle appointment submission (create or update)
const handleAppointmentSubmit = (appointmentData: any) => { const handleAppointmentSubmit = (appointmentData: InsertAppointment | UpdateAppointment) => {
// Make sure the date is for the selected date // Make sure the date is for the selected date
const updatedData = { const updatedData = {
...appointmentData, ...appointmentData,
@@ -285,13 +308,13 @@ export default function AppointmentsPage() {
if (editingAppointment && 'id' in editingAppointment && typeof editingAppointment.id === 'number') { if (editingAppointment && 'id' in editingAppointment && typeof editingAppointment.id === 'number') {
updateAppointmentMutation.mutate({ updateAppointmentMutation.mutate({
id: editingAppointment.id, id: editingAppointment.id,
appointment: updatedData as any, appointment: updatedData as unknown as UpdateAppointment,
}); });
} else { } else {
// This is a new appointment // This is a new appointment
if (user) { if (user) {
createAppointmentMutation.mutate({ createAppointmentMutation.mutate({
...updatedData as any, ...updatedData as unknown as InsertAppointment,
userId: user.id userId: user.id
}); });
} }
@@ -299,7 +322,7 @@ export default function AppointmentsPage() {
}; };
// Handle edit appointment // Handle edit appointment
const handleEditAppointment = (appointment: any) => { const handleEditAppointment = (appointment: Appointment) => {
setEditingAppointment(appointment); setEditingAppointment(appointment);
setIsAddModalOpen(true); setIsAddModalOpen(true);
}; };
@@ -319,21 +342,19 @@ export default function AppointmentsPage() {
const formattedDate = format(selectedDate, 'MMMM d, yyyy'); const formattedDate = format(selectedDate, 'MMMM d, yyyy');
// Filter appointments for the selected date // Filter appointments for the selected date
const selectedDateAppointments = appointments.filter((apt: { date: string }) => const selectedDateAppointments = appointments.filter(apt =>
apt.date === format(selectedDate, 'yyyy-MM-dd') apt.date === format(selectedDate, 'yyyy-MM-dd')
); );
// Add debugging logs // Add debugging logs
console.log("Selected date:", format(selectedDate, 'yyyy-MM-dd')); console.log("Selected date:", format(selectedDate, 'yyyy-MM-dd'));
console.log("All appointments:", appointments); console.log("All appointments:", appointments);
console.log("Filtered appointments for selected date:", selectedDateAppointments); console.log("Filtered appointments for selected date:", selectedDateAppointments);
// Process appointments for the scheduler view // Process appointments for the scheduler view
const processedAppointments: ScheduledAppointment[] = selectedDateAppointments.map((apt: any) => { const processedAppointments: ScheduledAppointment[] = selectedDateAppointments.map(apt => {
// Find patient name // Find patient name
const patient = patients.find((p: any) => p.id === apt.patientId); const patient = patients.find(p => p.id === apt.patientId);
const patientName = patient ? `${patient.firstName} ${patient.lastName}` : 'Unknown Patient'; const patientName = patient ? `${patient.firstName} ${patient.lastName}` : 'Unknown Patient';
// Try to determine the staff from the notes or title // Try to determine the staff from the notes or title
@@ -376,7 +397,9 @@ export default function AppointmentsPage() {
const processed = { const processed = {
...apt, ...apt,
patientName, patientName,
staffId staffId,
status: apt.status ?? null, // Default to null if status is undefined
date: apt.date instanceof Date ? apt.date.toISOString() : apt.date, // Ensure d
}; };
console.log("Processed appointment:", processed); console.log("Processed appointment:", processed);
@@ -393,7 +416,8 @@ export default function AppointmentsPage() {
// In a real application, you might want to show multiple or stack them // In a real application, you might want to show multiple or stack them
const appointmentsAtSlot = processedAppointments.filter(apt => { const appointmentsAtSlot = processedAppointments.filter(apt => {
// Fix time format comparison - the database adds ":00" seconds to the time // Fix time format comparison - the database adds ":00" seconds to the time
const dbTime = apt.startTime.substring(0, 5); // Get just HH:MM, removing seconds const dbTime = typeof apt.startTime === 'string' ? apt.startTime.substring(0, 5) : '';
const timeMatches = dbTime === timeSlot.time; const timeMatches = dbTime === timeSlot.time;
const staffMatches = apt.staffId === staffId; const staffMatches = apt.staffId === staffId;
@@ -417,7 +441,7 @@ export default function AppointmentsPage() {
// Handle moving an appointment to a new time slot and staff // Handle moving an appointment to a new time slot and staff
const handleMoveAppointment = (appointmentId: number, newTimeSlot: TimeSlot, newStaffId: string) => { const handleMoveAppointment = (appointmentId: number, newTimeSlot: TimeSlot, newStaffId: string) => {
const appointment = appointments.find((a:any) => a.id === appointmentId); const appointment = appointments.find(a => a.id === appointmentId);
if (!appointment) return; if (!appointment) return;
// Calculate new end time (30 minutes from start) // Calculate new end time (30 minutes from start)
@@ -434,7 +458,7 @@ export default function AppointmentsPage() {
// Update appointment data // Update appointment data
// Make sure we handle the time format correctly - backend expects HH:MM but stores as HH:MM:SS // Make sure we handle the time format correctly - backend expects HH:MM but stores as HH:MM:SS
const updatedAppointment: any = { const updatedAppointment: UpdateAppointment = {
...appointment, ...appointment,
startTime: newTimeSlot.time, // Already in HH:MM format startTime: newTimeSlot.time, // Already in HH:MM format
endTime: endTime, // Already in HH:MM format endTime: endTime, // Already in HH:MM format
@@ -475,7 +499,6 @@ export default function AppointmentsPage() {
return ( return (
<div <div
ref={drag as unknown as React.RefObject<HTMLDivElement>} // Type assertion to make TypeScript happy ref={drag as unknown as React.RefObject<HTMLDivElement>} // Type assertion to make TypeScript happy
className={`${staff.color} border border-white shadow-md text-white rounded p-1 text-xs h-full overflow-hidden cursor-move relative ${ className={`${staff.color} border border-white shadow-md text-white rounded p-1 text-xs h-full overflow-hidden cursor-move relative ${
isDragging ? 'opacity-50' : 'opacity-100' isDragging ? 'opacity-50' : 'opacity-100'
}`} }`}
@@ -483,14 +506,14 @@ export default function AppointmentsPage() {
onClick={(e) => { onClick={(e) => {
// Only allow edit on click if we're not dragging // Only allow edit on click if we're not dragging
if (!isDragging) { if (!isDragging) {
const fullAppointment = appointments.find((a:any) => a.id === appointment.id); const fullAppointment = appointments.find(a => a.id === appointment.id);
if (fullAppointment) { if (fullAppointment) {
e.stopPropagation(); e.stopPropagation();
handleEditAppointment(fullAppointment); handleEditAppointment(fullAppointment);
} }
} }
}} }}
onContextMenu={(e) => handleContextMenu(e, appointment.id)} onContextMenu={(e) => handleContextMenu(e, appointment.id ?? 0)}
> >
<div className="font-bold truncate flex items-center gap-1"> <div className="font-bold truncate flex items-center gap-1">
<Move className="h-3 w-3" /> <Move className="h-3 w-3" />
@@ -531,8 +554,6 @@ export default function AppointmentsPage() {
return ( return (
<td <td
ref={drop as unknown as React.RefObject<HTMLTableCellElement>} ref={drop as unknown as React.RefObject<HTMLTableCellElement>}
key={`${timeSlot.time}-${staffId}`} key={`${timeSlot.time}-${staffId}`}
className={`px-1 py-1 border relative h-14 ${isOver && canDrop ? 'bg-green-100' : ''}`} className={`px-1 py-1 border relative h-14 ${isOver && canDrop ? 'bg-green-100' : ''}`}
> >
@@ -583,7 +604,7 @@ export default function AppointmentsPage() {
<Menu id={APPOINTMENT_CONTEXT_MENU_ID} animation="fade"> <Menu id={APPOINTMENT_CONTEXT_MENU_ID} animation="fade">
<Item <Item
onClick={({ props }) => { onClick={({ props }) => {
const fullAppointment = appointments.find((a:any) => a.id === props.appointmentId); const fullAppointment = appointments.find(a => a.id === props.appointmentId);
if (fullAppointment) { if (fullAppointment) {
handleEditAppointment(fullAppointment); handleEditAppointment(fullAppointment);
} }
@@ -596,14 +617,14 @@ export default function AppointmentsPage() {
</Item> </Item>
<Item <Item
onClick={({ props }) => { onClick={({ props }) => {
const fullAppointment = appointments.find((a:any) => a.id === props.appointmentId); const fullAppointment = appointments.find(a => a.id === props.appointmentId);
if (fullAppointment) { if (fullAppointment) {
// Set the appointment and patient IDs for the claim modal // Set the appointment and patient IDs for the claim modal
setClaimAppointmentId(fullAppointment.id); setClaimAppointmentId(fullAppointment.id ?? null);
setClaimPatientId(fullAppointment.patientId); setClaimPatientId(fullAppointment.patientId);
// Find the patient name for the toast notification // Find the patient name for the toast notification
const patient = patients.find((p:any) => p.id === fullAppointment.patientId); const patient = patients.find(p => p.id === fullAppointment.patientId);
const patientName = patient ? `${patient.firstName} ${patient.lastName}` : `Patient #${fullAppointment.patientId}`; const patientName = patient ? `${patient.firstName} ${patient.lastName}` : `Patient #${fullAppointment.patientId}`;
// Show a toast notification // Show a toast notification

View File

@@ -1,11 +1,12 @@
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import { UserCreateOneSchema } from "@repo/db/shared"; import { UserUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
// import { insertUserSchema } from "@repo/db/shared/schemas";
import { useState } from "react"; import { useState } from "react";
import { useAuth } from "../hooks/use-auth"; import { useAuth } from "@/hooks/use-auth";
import { Redirect } from "wouter"; import { Redirect } from "wouter";
import { Button } from "../components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Form, Form,
FormControl, FormControl,
@@ -13,18 +14,25 @@ import {
FormItem, FormItem,
FormLabel, FormLabel,
FormMessage, FormMessage,
} from "../components/ui/form"; } from "@/components/ui/form";
import { Input } from "../components/ui/input"; import { Input } from "@/components/ui/input";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Checkbox } from "../components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { Card, CardContent } from "../components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { BriefcaseMedical, CheckCircle, Torus } from "lucide-react"; import { BriefcaseMedical, CheckCircle, Torus } from "lucide-react";
import { CheckedState } from "@radix-ui/react-checkbox";
const loginSchema = UserCreateOneSchema.extend({ const insertUserSchema = (UserUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).pick({
username: true,
password: true,
});
const loginSchema = (insertUserSchema as unknown as z.ZodObject<any>).extend({
rememberMe: z.boolean().optional(), rememberMe: z.boolean().optional(),
}); });
const registerSchema = UserCreateOne.extend({
const registerSchema = (insertUserSchema as unknown as z.ZodObject<any>).extend({
confirmPassword: z.string().min(6, { confirmPassword: z.string().min(6, {
message: "Password must be at least 6 characters long", message: "Password must be at least 6 characters long",
}), }),
@@ -143,7 +151,7 @@ export default function AuthPage() {
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<Checkbox <Checkbox
id="remember-me" id="remember-me"
checked={field.value} checked={field.value as CheckedState}
onCheckedChange={field.onChange} onCheckedChange={field.onChange}
/> />
<label <label
@@ -216,6 +224,8 @@ export default function AuthPage() {
placeholder="••••••••" placeholder="••••••••"
type="password" type="password"
{...field} {...field}
value={typeof field.value === 'string' ? field.value : ''}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@@ -230,7 +240,7 @@ export default function AuthPage() {
<FormItem className="flex items-start space-x-2 mt-4"> <FormItem className="flex items-start space-x-2 mt-4">
<FormControl> <FormControl>
<Checkbox <Checkbox
checked={field.value} checked={field.value as CheckedState}
onCheckedChange={field.onChange} onCheckedChange={field.onChange}
/> />
</FormControl> </FormControl>

View File

@@ -1,18 +1,19 @@
import { useState } from "react"; import { useState } from "react";
import { useQuery, useMutation } from "@tanstack/react-query"; import { useQuery, useMutation } from "@tanstack/react-query";
import { format } from "date-fns"; import { format } from "date-fns";
import { TopAppBar } from "../components/layout/top-app-bar"; import { TopAppBar } from "@/components/layout/top-app-bar";
import { Sidebar } from "../components/layout/sidebar"; import { Sidebar } from "@/components/layout/sidebar";
import { StatCard } from "../components/ui/stat-card"; import { StatCard } from "@/components/ui/stat-card";
import { PatientTable } from "../components/patients/patient-table"; import { PatientTable } from "@/components/patients/patient-table";
import { AddPatientModal } from "../components/patients/add-patient-modal"; import { AddPatientModal } from "@/components/patients/add-patient-modal";
import { AddAppointmentModal } from "../components/appointments/add-appointment-modal"; import { AddAppointmentModal } from "@/components/appointments/add-appointment-modal";
import { Card, CardContent } from "../components/ui/card"; import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { Button } from "../components/ui/button"; import { Button } from "@/components/ui/button";
import { useToast } from "../hooks/use-toast"; import { useToast } from "@/hooks/use-toast";
import { useAuth } from "../hooks/use-auth"; import { useAuth } from "@/hooks/use-auth";
import { apiRequest, queryClient } from "../lib/queryClient"; import { apiRequest, queryClient } from "@/lib/queryClient";
import { InsertPatient, Patient, UpdatePatient, Appointment, InsertAppointment, UpdateAppointment } from "@repo/db/schema"; import { AppointmentUncheckedCreateInputObjectSchema, PatientUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
// import { InsertPatient, Patient, UpdatePatient, Appointment, InsertAppointment, UpdateAppointment } from "@repo/db/shared/schemas";
import { Users, Calendar, CheckCircle, CreditCard, Plus, Clock } from "lucide-react"; import { Users, Calendar, CheckCircle, CreditCard, Plus, Clock } from "lucide-react";
import { Link } from "wouter"; import { Link } from "wouter";
import { import {
@@ -21,7 +22,43 @@ import {
DialogDescription, DialogDescription,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from "../components/ui/dialog"; } from "@/components/ui/dialog";
import {z} from "zod";
//creating types out of schema auto generated.
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
const insertAppointmentSchema = (AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
});
type InsertAppointment = z.infer<typeof insertAppointmentSchema>;
const updateAppointmentSchema = (AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
}).partial();
type UpdateAppointment = z.infer<typeof updateAppointmentSchema>;
const PatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
const insertPatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
});
type InsertPatient = z.infer<typeof insertPatientSchema>;
const updatePatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
userId: true,
}).partial();
type UpdatePatient = z.infer<typeof updatePatientSchema>;
export default function Dashboard() { export default function Dashboard() {
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
@@ -174,7 +211,7 @@ export default function Dashboard() {
// Handle appointment submission (create or update) // Handle appointment submission (create or update)
const handleAppointmentSubmit = (appointmentData: InsertAppointment | UpdateAppointment) => { const handleAppointmentSubmit = (appointmentData: InsertAppointment | UpdateAppointment) => {
if (selectedAppointment) { if (selectedAppointment && typeof selectedAppointment.id === 'number') {
updateAppointmentMutation.mutate({ updateAppointmentMutation.mutate({
id: selectedAppointment.id, id: selectedAppointment.id,
appointment: appointmentData as UpdateAppointment, appointment: appointmentData as UpdateAppointment,
@@ -274,7 +311,7 @@ export default function Dashboard() {
{patient ? `${patient.firstName} ${patient.lastName}` : 'Unknown Patient'} {patient ? `${patient.firstName} ${patient.lastName}` : 'Unknown Patient'}
</h3> </h3>
<div className="text-sm text-gray-500 flex items-center space-x-2"> <div className="text-sm text-gray-500 flex items-center space-x-2">
<span>{appointment.startTime} - {appointment.endTime}</span> <span>{new Date(appointment.startTime).toLocaleString()} - {new Date(appointment.endTime).toLocaleString()}</span>
<span>•</span> <span>•</span>
<span>{appointment.type.charAt(0).toUpperCase() + appointment.type.slice(1)}</span> <span>{appointment.type.charAt(0).toUpperCase() + appointment.type.slice(1)}</span>
</div> </div>

View File

@@ -1,4 +1,4 @@
import { Card, CardContent } from "../components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { AlertCircle } from "lucide-react"; import { AlertCircle } from "lucide-react";
export default function NotFound() { export default function NotFound() {

View File

@@ -1,18 +1,41 @@
import { useState, useMemo, useRef } from "react"; import { useState, useMemo, useRef } from "react";
import { useQuery, useMutation } from "@tanstack/react-query"; import { useQuery, useMutation } from "@tanstack/react-query";
import { TopAppBar } from "../components/layout/top-app-bar"; import { TopAppBar } from "@/components/layout/top-app-bar";
import { Sidebar } from "../components/layout/sidebar"; import { Sidebar } from "@/components/layout/sidebar";
import { PatientTable } from "../components/patients/patient-table"; import { PatientTable } from "@/components/patients/patient-table";
import { AddPatientModal } from "../components/patients/add-patient-modal"; import { AddPatientModal } from "@/components/patients/add-patient-modal";
import { PatientSearch, SearchCriteria } from "../components/patients/patient-search"; import { PatientSearch, SearchCriteria } from "@/components/patients/patient-search";
import { FileUploadZone } from "../components/file-upload/file-upload-zone"; import { FileUploadZone } from "@/components/file-upload/file-upload-zone";
import { Button } from "../components/ui/button"; import { Button } from "@/components/ui/button";
import { Plus, RefreshCw, File, FilePlus } from "lucide-react"; import { Plus, RefreshCw, File, FilePlus } from "lucide-react";
import { useToast } from "../hooks/use-toast"; import { useToast } from "@/hooks/use-toast";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../components/ui/card"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Patient, InsertPatient, UpdatePatient } from "@shared/schema"; import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
import { apiRequest, queryClient } from "../lib/queryClient"; // import { Patient, InsertPatient, UpdatePatient } from "@repo/db/shared/schemas";
import { useAuth } from "../hooks/use-auth"; import { apiRequest, queryClient } from "@/lib/queryClient";
import { useAuth } from "@/hooks/use-auth";
import {z} from "zod";
const PatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
const insertPatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
});
type InsertPatient = z.infer<typeof insertPatientSchema>;
const updatePatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
userId: true,
}).partial();
type UpdatePatient = z.infer<typeof updatePatientSchema>;
// Type for the ref to access modal methods // Type for the ref to access modal methods
type AddPatientModalRef = { type AddPatientModalRef = {

View File

@@ -0,0 +1,4 @@
const tailwindConfig = require("tailwindcss");
type Config = typeof tailwindConfig;
export default Config;

View File

@@ -1,11 +1,13 @@
{ {
"extends": "@repo/typescript-config/nextjs.json", "extends": "@repo/typescript-config/base.json",
"compilerOptions": { "compilerOptions": {
"plugins": [ "moduleResolution": "node",
{ "module": "ESNext",
"name": "next" "jsx": "preserve",
} "baseUrl": ".", // This tells TypeScript where to start resolving paths
] "paths": {
"@/*": ["src/*"] // The @ alias will point to src folder inside the Frontend app
}
}, },
"include": [ "include": [
"**/*.ts", "**/*.ts",

View File

@@ -1,7 +1,13 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react' import react from '@vitejs/plugin-react'
import path from 'path';
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()],
}) resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
});

518
package-lock.json generated
View File

@@ -12,7 +12,9 @@
"packages/*" "packages/*"
], ],
"dependencies": { "dependencies": {
"prisma": "^6.7.0" "@tailwindcss/postcss": "^4.1.6",
"@tailwindcss/typography": "^0.5.16",
"tailwindcss-animate": "^1.0.7"
}, },
"devDependencies": { "devDependencies": {
"prettier": "^3.5.3", "prettier": "^3.5.3",
@@ -83,9 +85,12 @@
"@radix-ui/react-tooltip": "^1.2.0", "@radix-ui/react-tooltip": "^1.2.0",
"@replit/vite-plugin-shadcn-theme-json": "^0.0.4", "@replit/vite-plugin-shadcn-theme-json": "^0.0.4",
"@repo/db": "*", "@repo/db": "*",
"@repo/tailwind-config": "*",
"@repo/typescript-config": "*",
"@repo/ui": "*", "@repo/ui": "*",
"@tailwindcss/vite": "^4.1.3", "@tailwindcss/vite": "^4.1.3",
"@tanstack/react-query": "^5.60.5", "@tanstack/react-query": "^5.60.5",
"autoprefixer": "^10.4.21",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.1.1", "cmdk": "^1.1.1",
@@ -101,6 +106,7 @@
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"passport": "^0.7.0", "passport": "^0.7.0",
"passport-local": "^1.0.0", "passport-local": "^1.0.0",
"postcss": "^8.5.3",
"react": "^19.1.0", "react": "^19.1.0",
"react-contexify": "^6.0.0", "react-contexify": "^6.0.0",
"react-day-picker": "^8.10.1", "react-day-picker": "^8.10.1",
@@ -112,6 +118,7 @@
"react-resizable-panels": "^2.1.7", "react-resizable-panels": "^2.1.7",
"recharts": "^2.15.2", "recharts": "^2.15.2",
"tailwind-merge": "^2.6.0", "tailwind-merge": "^2.6.0",
"tailwindcss": "^4.1.5",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"tw-animate-css": "^1.2.5", "tw-animate-css": "^1.2.5",
"vaul": "^1.1.2", "vaul": "^1.1.2",
@@ -195,11 +202,22 @@
"typescript": "5.8.2" "typescript": "5.8.2"
} }
}, },
"node_modules/@alloc/quick-lru": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@ampproject/remapping": { "node_modules/@ampproject/remapping": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/gen-mapping": "^0.3.5",
@@ -1187,11 +1205,22 @@
"url": "https://github.com/sponsors/nzakas" "url": "https://github.com/sponsors/nzakas"
} }
}, },
"node_modules/@isaacs/fs-minipass": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
"integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
"license": "ISC",
"dependencies": {
"minipass": "^7.0.4"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.8", "version": "0.3.8",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/set-array": "^1.2.1", "@jridgewell/set-array": "^1.2.1",
@@ -1215,7 +1244,6 @@
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6.0.0" "node": ">=6.0.0"
@@ -1648,6 +1676,7 @@
"version": "6.7.0", "version": "6.7.0",
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.7.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.7.0.tgz",
"integrity": "sha512-di8QDdvSz7DLUi3OOcCHSwxRNeW7jtGRUD2+Z3SdNE3A+pPiNT8WgUJoUyOwJmUr5t+JA2W15P78C/N+8RXrOA==", "integrity": "sha512-di8QDdvSz7DLUi3OOcCHSwxRNeW7jtGRUD2+Z3SdNE3A+pPiNT8WgUJoUyOwJmUr5t+JA2W15P78C/N+8RXrOA==",
"devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"esbuild": ">=0.12 <1", "esbuild": ">=0.12 <1",
@@ -1658,12 +1687,14 @@
"version": "6.7.0", "version": "6.7.0",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.7.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.7.0.tgz",
"integrity": "sha512-RabHn9emKoYFsv99RLxvfG2GHzWk2ZI1BuVzqYtmMSIcuGboHY5uFt3Q3boOREM9de6z5s3bQoyKeWnq8Fz22w==", "integrity": "sha512-RabHn9emKoYFsv99RLxvfG2GHzWk2ZI1BuVzqYtmMSIcuGboHY5uFt3Q3boOREM9de6z5s3bQoyKeWnq8Fz22w==",
"devOptional": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@prisma/engines": { "node_modules/@prisma/engines": {
"version": "6.7.0", "version": "6.7.0",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.7.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.7.0.tgz",
"integrity": "sha512-3wDMesnOxPrOsq++e5oKV9LmIiEazFTRFZrlULDQ8fxdub5w4NgRBoxtWbvXmj2nJVCnzuz6eFix3OhIqsZ1jw==", "integrity": "sha512-3wDMesnOxPrOsq++e5oKV9LmIiEazFTRFZrlULDQ8fxdub5w4NgRBoxtWbvXmj2nJVCnzuz6eFix3OhIqsZ1jw==",
"devOptional": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@@ -1677,12 +1708,14 @@
"version": "6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed", "version": "6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed.tgz", "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed.tgz",
"integrity": "sha512-EvpOFEWf1KkJpDsBCrih0kg3HdHuaCnXmMn7XFPObpFTzagK1N0Q0FMnYPsEhvARfANP5Ok11QyoTIRA2hgJTA==", "integrity": "sha512-EvpOFEWf1KkJpDsBCrih0kg3HdHuaCnXmMn7XFPObpFTzagK1N0Q0FMnYPsEhvARfANP5Ok11QyoTIRA2hgJTA==",
"devOptional": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@prisma/fetch-engine": { "node_modules/@prisma/fetch-engine": {
"version": "6.7.0", "version": "6.7.0",
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.7.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.7.0.tgz",
"integrity": "sha512-zLlAGnrkmioPKJR4Yf7NfW3hftcvqeNNEHleMZK9yX7RZSkhmxacAYyfGsCcqRt47jiZ7RKdgE0Wh2fWnm7WsQ==", "integrity": "sha512-zLlAGnrkmioPKJR4Yf7NfW3hftcvqeNNEHleMZK9yX7RZSkhmxacAYyfGsCcqRt47jiZ7RKdgE0Wh2fWnm7WsQ==",
"devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/debug": "6.7.0", "@prisma/debug": "6.7.0",
@@ -1754,6 +1787,7 @@
"version": "6.7.0", "version": "6.7.0",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.7.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.7.0.tgz",
"integrity": "sha512-i9IH5lO4fQwnMLvQLYNdgVh9TK3PuWBfQd7QLk/YurnAIg+VeADcZDbmhAi4XBBDD+hDif9hrKyASu0hbjwabw==", "integrity": "sha512-i9IH5lO4fQwnMLvQLYNdgVh9TK3PuWBfQd7QLk/YurnAIg+VeADcZDbmhAi4XBBDD+hDif9hrKyASu0hbjwabw==",
"devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/debug": "6.7.0" "@prisma/debug": "6.7.0"
@@ -3844,6 +3878,297 @@
"node": ">= 10" "node": ">= 10"
} }
}, },
"node_modules/@tailwindcss/postcss": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.6.tgz",
"integrity": "sha512-ELq+gDMBuRXPJlpE3PEen+1MhnHAQQrh2zF0dI1NXOlEWfr2qWf2CQdr5jl9yANv8RErQaQ2l6nIFO9OSCVq/g==",
"license": "MIT",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"@tailwindcss/node": "4.1.6",
"@tailwindcss/oxide": "4.1.6",
"postcss": "^8.4.41",
"tailwindcss": "4.1.6"
}
},
"node_modules/@tailwindcss/postcss/node_modules/@tailwindcss/node": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.6.tgz",
"integrity": "sha512-ed6zQbgmKsjsVvodAS1q1Ld2BolEuxJOSyyNc+vhkjdmfNUDCmQnlXBfQkHrlzNmslxHsQU/bFmzcEbv4xXsLg==",
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.3.0",
"enhanced-resolve": "^5.18.1",
"jiti": "^2.4.2",
"lightningcss": "1.29.2",
"magic-string": "^0.30.17",
"source-map-js": "^1.2.1",
"tailwindcss": "4.1.6"
}
},
"node_modules/@tailwindcss/postcss/node_modules/@tailwindcss/oxide": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.6.tgz",
"integrity": "sha512-0bpEBQiGx+227fW4G0fLQ8vuvyy5rsB1YIYNapTq3aRsJ9taF3f5cCaovDjN5pUGKKzcpMrZst/mhNaKAPOHOA==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"detect-libc": "^2.0.4",
"tar": "^7.4.3"
},
"engines": {
"node": ">= 10"
},
"optionalDependencies": {
"@tailwindcss/oxide-android-arm64": "4.1.6",
"@tailwindcss/oxide-darwin-arm64": "4.1.6",
"@tailwindcss/oxide-darwin-x64": "4.1.6",
"@tailwindcss/oxide-freebsd-x64": "4.1.6",
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.6",
"@tailwindcss/oxide-linux-arm64-gnu": "4.1.6",
"@tailwindcss/oxide-linux-arm64-musl": "4.1.6",
"@tailwindcss/oxide-linux-x64-gnu": "4.1.6",
"@tailwindcss/oxide-linux-x64-musl": "4.1.6",
"@tailwindcss/oxide-wasm32-wasi": "4.1.6",
"@tailwindcss/oxide-win32-arm64-msvc": "4.1.6",
"@tailwindcss/oxide-win32-x64-msvc": "4.1.6"
}
},
"node_modules/@tailwindcss/postcss/node_modules/@tailwindcss/oxide-android-arm64": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.6.tgz",
"integrity": "sha512-VHwwPiwXtdIvOvqT/0/FLH/pizTVu78FOnI9jQo64kSAikFSZT7K4pjyzoDpSMaveJTGyAKvDjuhxJxKfmvjiQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/postcss/node_modules/@tailwindcss/oxide-darwin-arm64": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.6.tgz",
"integrity": "sha512-weINOCcqv1HVBIGptNrk7c6lWgSFFiQMcCpKM4tnVi5x8OY2v1FrV76jwLukfT6pL1hyajc06tyVmZFYXoxvhQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/postcss/node_modules/@tailwindcss/oxide-darwin-x64": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.6.tgz",
"integrity": "sha512-3FzekhHG0ww1zQjQ1lPoq0wPrAIVXAbUkWdWM8u5BnYFZgb9ja5ejBqyTgjpo5mfy0hFOoMnMuVDI+7CXhXZaQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/postcss/node_modules/@tailwindcss/oxide-freebsd-x64": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.6.tgz",
"integrity": "sha512-4m5F5lpkBZhVQJq53oe5XgJ+aFYWdrgkMwViHjRsES3KEu2m1udR21B1I77RUqie0ZYNscFzY1v9aDssMBZ/1w==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/postcss/node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.6.tgz",
"integrity": "sha512-qU0rHnA9P/ZoaDKouU1oGPxPWzDKtIfX7eOGi5jOWJKdxieUJdVV+CxWZOpDWlYTd4N3sFQvcnVLJWJ1cLP5TA==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/postcss/node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.6.tgz",
"integrity": "sha512-jXy3TSTrbfgyd3UxPQeXC3wm8DAgmigzar99Km9Sf6L2OFfn/k+u3VqmpgHQw5QNfCpPe43em6Q7V76Wx7ogIQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/postcss/node_modules/@tailwindcss/oxide-linux-arm64-musl": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.6.tgz",
"integrity": "sha512-8kjivE5xW0qAQ9HX9reVFmZj3t+VmljDLVRJpVBEoTR+3bKMnvC7iLcoSGNIUJGOZy1mLVq7x/gerVg0T+IsYw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/postcss/node_modules/@tailwindcss/oxide-linux-x64-gnu": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.6.tgz",
"integrity": "sha512-A4spQhwnWVpjWDLXnOW9PSinO2PTKJQNRmL/aIl2U/O+RARls8doDfs6R41+DAXK0ccacvRyDpR46aVQJJCoCg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/postcss/node_modules/@tailwindcss/oxide-linux-x64-musl": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.6.tgz",
"integrity": "sha512-YRee+6ZqdzgiQAHVSLfl3RYmqeeaWVCk796MhXhLQu2kJu2COHBkqlqsqKYx3p8Hmk5pGCQd2jTAoMWWFeyG2A==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/postcss/node_modules/@tailwindcss/oxide-wasm32-wasi": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.6.tgz",
"integrity": "sha512-qAp4ooTYrBQ5pk5jgg54/U1rCJ/9FLYOkkQ/nTE+bVMseMfB6O7J8zb19YTpWuu4UdfRf5zzOrNKfl6T64MNrQ==",
"bundleDependencies": [
"@napi-rs/wasm-runtime",
"@emnapi/core",
"@emnapi/runtime",
"@tybys/wasm-util",
"@emnapi/wasi-threads",
"tslib"
],
"cpu": [
"wasm32"
],
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "^1.4.3",
"@emnapi/runtime": "^1.4.3",
"@emnapi/wasi-threads": "^1.0.2",
"@napi-rs/wasm-runtime": "^0.2.9",
"@tybys/wasm-util": "^0.9.0",
"tslib": "^2.8.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@tailwindcss/postcss/node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.6.tgz",
"integrity": "sha512-nqpDWk0Xr8ELO/nfRUDjk1pc9wDJ3ObeDdNMHLaymc4PJBWj11gdPCWZFKSK2AVKjJQC7J2EfmSmf47GN7OuLg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/postcss/node_modules/@tailwindcss/oxide-win32-x64-msvc": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.6.tgz",
"integrity": "sha512-5k9xF33xkfKpo9wCvYcegQ21VwIBU1/qEbYlVukfEIyQbEA47uK8AAwS7NVjNE3vHzcmxMYwd0l6L4pPjjm1rQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/postcss/node_modules/detect-libc": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
"node_modules/@tailwindcss/postcss/node_modules/tailwindcss": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.6.tgz",
"integrity": "sha512-j0cGLTreM6u4OWzBeLBpycK0WIh8w7kSwcUsQZoGLHZ7xDTdM69lN64AgoIEEwFi0tnhs4wSykUa5YWxAzgFYg==",
"license": "MIT"
},
"node_modules/@tailwindcss/typography": {
"version": "0.5.16",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz",
"integrity": "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==",
"license": "MIT",
"dependencies": {
"lodash.castarray": "^4.4.0",
"lodash.isplainobject": "^4.0.6",
"lodash.merge": "^4.6.2",
"postcss-selector-parser": "6.0.10"
},
"peerDependencies": {
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
}
},
"node_modules/@tailwindcss/vite": { "node_modules/@tailwindcss/vite": {
"version": "4.1.5", "version": "4.1.5",
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.5.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.5.tgz",
@@ -4736,6 +5061,43 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/autoprefixer": {
"version": "10.4.21",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
"integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/autoprefixer"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"browserslist": "^4.24.4",
"caniuse-lite": "^1.0.30001702",
"fraction.js": "^4.3.7",
"normalize-range": "^0.1.2",
"picocolors": "^1.1.1",
"postcss-value-parser": "^4.2.0"
},
"bin": {
"autoprefixer": "bin/autoprefixer"
},
"engines": {
"node": "^10 || ^12 || >=14"
},
"peerDependencies": {
"postcss": "^8.1.0"
}
},
"node_modules/available-typed-arrays": { "node_modules/available-typed-arrays": {
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
@@ -4836,7 +5198,6 @@
"version": "4.24.5", "version": "4.24.5",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz",
"integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -4972,7 +5333,6 @@
"version": "1.0.30001717", "version": "1.0.30001717",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001717.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001717.tgz",
"integrity": "sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw==", "integrity": "sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -5021,6 +5381,15 @@
"uuid": "9.0.0" "uuid": "9.0.0"
} }
}, },
"node_modules/chownr": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
"integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=18"
}
},
"node_modules/ci-info": { "node_modules/ci-info": {
"version": "3.8.0", "version": "3.8.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz",
@@ -5297,6 +5666,18 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"license": "MIT",
"bin": {
"cssesc": "bin/cssesc"
},
"engines": {
"node": ">=4"
}
},
"node_modules/csstype": { "node_modules/csstype": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
@@ -5701,7 +6082,6 @@
"version": "1.5.151", "version": "1.5.151",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.151.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.151.tgz",
"integrity": "sha512-Rl6uugut2l9sLojjS4H4SAr3A4IgACMLgpuEMPYCVcKydzfyPrn5absNRju38IhQOf/NwjJY8OGWjlteqYeBCA==", "integrity": "sha512-Rl6uugut2l9sLojjS4H4SAr3A4IgACMLgpuEMPYCVcKydzfyPrn5absNRju38IhQOf/NwjJY8OGWjlteqYeBCA==",
"dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/embla-carousel": { "node_modules/embla-carousel": {
@@ -6006,6 +6386,7 @@
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz", "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz",
"integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"debug": "^4.3.4" "debug": "^4.3.4"
@@ -6018,7 +6399,6 @@
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@@ -6615,6 +6995,19 @@
"integrity": "sha512-bLq+KgbiXdTEoT1zcARrWEpa5z6A/8b7PcDW7Gef3NSisQ+VS7ll2Xbf1E+xsgik0rWub/8u0qP/iTTjj+PhxQ==", "integrity": "sha512-bLq+KgbiXdTEoT1zcARrWEpa5z6A/8b7PcDW7Gef3NSisQ+VS7ll2Xbf1E+xsgik0rWub/8u0qP/iTTjj+PhxQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/fraction.js": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
"integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
"license": "MIT",
"engines": {
"node": "*"
},
"funding": {
"type": "patreon",
"url": "https://github.com/sponsors/rawify"
}
},
"node_modules/framer-motion": { "node_modules/framer-motion": {
"version": "11.18.2", "version": "11.18.2",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz",
@@ -8295,6 +8688,12 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash.castarray": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
"license": "MIT"
},
"node_modules/lodash.defaults": { "node_modules/lodash.defaults": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
@@ -8323,7 +8722,6 @@
"version": "4.6.2", "version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash.union": { "node_modules/lodash.union": {
@@ -8363,6 +8761,15 @@
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
} }
}, },
"node_modules/magic-string": {
"version": "0.30.17",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
"integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0"
}
},
"node_modules/make-dir": { "node_modules/make-dir": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@@ -8521,12 +8928,48 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/minizlib": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz",
"integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==",
"license": "MIT",
"dependencies": {
"minipass": "^7.1.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/mitt": { "node_modules/mitt": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/mkdirp": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
"integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
"license": "MIT",
"bin": {
"mkdirp": "dist/cjs/src/bin.js"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/motion-dom": { "node_modules/motion-dom": {
"version": "11.18.1", "version": "11.18.1",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz", "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz",
@@ -8643,7 +9086,6 @@
"version": "2.0.19", "version": "2.0.19",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/normalize-package-data": { "node_modules/normalize-package-data": {
@@ -8696,6 +9138,15 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/normalize-range": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
"integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/npm-bundled": { "node_modules/npm-bundled": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-2.0.1.tgz", "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-2.0.1.tgz",
@@ -9426,6 +9877,25 @@
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/postcss-selector-parser": {
"version": "6.0.10",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
"license": "MIT",
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
},
"engines": {
"node": ">=4"
}
},
"node_modules/postcss-value-parser": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"license": "MIT"
},
"node_modules/postgres-array": { "node_modules/postgres-array": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
@@ -9574,6 +10044,7 @@
"version": "6.7.0", "version": "6.7.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.7.0.tgz", "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.7.0.tgz",
"integrity": "sha512-vArg+4UqnQ13CVhc2WUosemwh6hr6cr6FY2uzDvCIFwH8pu8BXVv38PktoMLVjtX7sbYThxbnZF5YiR8sN2clw==", "integrity": "sha512-vArg+4UqnQ13CVhc2WUosemwh6hr6cr6FY2uzDvCIFwH8pu8BXVv38PktoMLVjtX7sbYThxbnZF5YiR8sN2clw==",
"devOptional": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@@ -11067,6 +11538,23 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/tar": {
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz",
"integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
"license": "ISC",
"dependencies": {
"@isaacs/fs-minipass": "^4.0.0",
"chownr": "^3.0.0",
"minipass": "^7.1.2",
"minizlib": "^3.0.1",
"mkdirp": "^3.0.1",
"yallist": "^5.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/tar-stream": { "node_modules/tar-stream": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
@@ -11083,6 +11571,15 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/tar/node_modules/yallist": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=18"
}
},
"node_modules/temp-dir": { "node_modules/temp-dir": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz",
@@ -11630,7 +12127,6 @@
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
"integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",

View File

@@ -23,6 +23,8 @@
"packages/*" "packages/*"
], ],
"dependencies": { "dependencies": {
"prisma": "^6.7.0" "@tailwindcss/postcss": "^4.1.6",
"@tailwindcss/typography": "^0.5.16",
"tailwindcss-animate": "^1.0.7"
} }
} }

View File

@@ -12,7 +12,7 @@
"type": "commonjs", "type": "commonjs",
"exports": { "exports": {
"./client": "./src/index.ts", "./client": "./src/index.ts",
"./shared" : "./shared/schemas/index.ts" "./shared/schemas" : "./shared/schemas/index.ts"
}, },
"dependencies": { "dependencies": {
"@prisma/client": "^6.7.0", "@prisma/client": "^6.7.0",

View File

@@ -1,85 +0,0 @@
import { pgTable, text, serial, integer, boolean, date, timestamp, time } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { z } from "zod";
// User schema
export const users = pgTable("users", {
id: serial("id").primaryKey(),
username: text("username").notNull().unique(),
password: text("password").notNull(),
});
export const insertUserSchema = createInsertSchema(users).pick({
username: true,
password: true,
});
export type InsertUser = z.infer<typeof insertUserSchema>;
export type User = typeof users.$inferSelect;
// Patient schema
export const patients = pgTable("patients", {
id: serial("id").primaryKey(),
firstName: text("first_name").notNull(),
lastName: text("last_name").notNull(),
dateOfBirth: date("date_of_birth").notNull(),
gender: text("gender").notNull(),
phone: text("phone").notNull(),
email: text("email"),
address: text("address"),
city: text("city"),
zipCode: text("zip_code"),
insuranceProvider: text("insurance_provider"),
insuranceId: text("insurance_id"),
groupNumber: text("group_number"),
policyHolder: text("policy_holder"),
allergies: text("allergies"),
medicalConditions: text("medical_conditions"),
status: text("status").default("active"),
userId: integer("user_id").notNull().references(() => users.id),
createdAt: timestamp("created_at").defaultNow(),
});
export const insertPatientSchema = createInsertSchema(patients).omit({
id: true,
createdAt: true,
});
export const updatePatientSchema = createInsertSchema(patients).omit({
id: true,
createdAt: true,
userId: true,
}).partial();
export type InsertPatient = z.infer<typeof insertPatientSchema>;
export type UpdatePatient = z.infer<typeof updatePatientSchema>;
export type Patient = typeof patients.$inferSelect;
// Appointment schema
export const appointments = pgTable("appointments", {
id: serial("id").primaryKey(),
patientId: integer("patient_id").notNull().references(() => patients.id),
userId: integer("user_id").notNull().references(() => users.id),
title: text("title").notNull(), // Added title field
date: date("date").notNull(),
startTime: time("start_time").notNull(),
endTime: time("end_time").notNull(),
type: text("type").notNull(), // e.g., "checkup", "cleaning", "filling", etc.
notes: text("notes"),
status: text("status").default("scheduled"), // "scheduled", "completed", "cancelled", "no-show"
createdAt: timestamp("created_at").defaultNow(),
});
export const insertAppointmentSchema = createInsertSchema(appointments).omit({
id: true,
createdAt: true,
});
export const updateAppointmentSchema = createInsertSchema(appointments).omit({
id: true,
createdAt: true,
}).partial();
export type InsertAppointment = z.infer<typeof insertAppointmentSchema>;
export type UpdateAppointment = z.infer<typeof updateAppointmentSchema>;
export type Appointment = typeof appointments.$inferSelect;

View File

@@ -1,6 +1,6 @@
// Optional PostCSS configuration for applications that need it import tailwindcss from "@tailwindcss/postcss";
import autoprefixer from "autoprefixer";
export const postcssConfig = { export const postcssConfig = {
plugins: { plugins: [tailwindcss(), autoprefixer()]
"@tailwindcss/postcss": {},
},
}; };

View File

@@ -1,7 +1,2 @@
@import "tailwindcss"; @import "tailwindcss";
@theme {
--blue-1000: #2a8af6;
--purple-1000: #a853ba;
--red-1000: #e92a67;
}

View File

@@ -0,0 +1,4 @@
import { config } from "@repo/eslint-config/react-internal";
/** @type {import("eslint").Linter.Config} */
export default config;

36
packages/ui/package.json Normal file
View File

@@ -0,0 +1,36 @@
{
"name": "@repo/ui",
"version": "0.0.0",
"sideEffects": [
"**/*.css"
],
"files": [
"dist"
],
"exports": {
"./styles.css": "./dist/index.css",
"./*": "./dist/*.js"
},
"license": "MIT",
"scripts": {
"build:styles": "tailwindcss -i ./src/styles.css -o ./dist/index.css",
"build:components": "tsc",
"check-types": "tsc --noEmit",
"dev:styles": "tailwindcss -i ./src/styles.css -o ./dist/index.css --watch",
"dev:components": "tsc --watch",
"lint": "eslint src --max-warnings 0"
},
"peerDependencies": {
"react": "^19"
},
"devDependencies": {
"@repo/eslint-config": "*",
"@repo/tailwind-config": "*",
"@repo/typescript-config": "*",
"@tailwindcss/cli": "^4.1.5",
"@types/react": "^19.1.0",
"eslint": "^9.26.0",
"tailwindcss": "^4.1.5",
"typescript": "5.8.2"
}
}

28
packages/ui/src/card.tsx Normal file
View File

@@ -0,0 +1,28 @@
import { type ReactNode } from "react";
export function Card({
title,
children,
href,
}: {
title: string;
children: ReactNode;
href: string;
}) {
return (
<a
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-neutral-700 hover:bg-neutral-800/30"
href={`${href}?utm_source=create-turbo&utm_medium=with-tailwind&utm_campaign=create-turbo"`}
rel="noopener noreferrer"
target="_blank"
>
<h2 className="mb-3 text-2xl font-semibold">
{title}{" "}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
-&gt;
</span>
</h2>
<p className="m-0 max-w-[30ch] text-sm opacity-50">{children}</p>
</a>
);
}

View File

@@ -0,0 +1,21 @@
export function Gradient({
conic,
className,
small,
}: {
small?: boolean;
conic?: boolean;
className?: string;
}) {
return (
<span
className={`absolute mix-blend-normal will-change-[filter] rounded-[100%] ${
small ? "blur-[32px]" : "blur-[75px]"
} ${
conic
? "bg-[conic-gradient(from_180deg_at_50%_50%,var(--red-1000)_0deg,_var(--purple-1000)_180deg,_var(--blue-1000)_360deg)]"
: ""
} ${className ?? ""}`}
/>
);
}

View File

@@ -0,0 +1 @@
@import "tailwindcss";

View File

@@ -0,0 +1,35 @@
export const TurborepoLogo = () => {
return (
<svg
aria-label="Turbo logomark"
height="80"
role="img"
viewBox="0 0 40 40"
width="80"
>
<path
d="M19.9845 6.99291C12.818 6.99291 6.98755 12.8279 6.98755 19.9999C6.98755 27.1721 12.818 33.0071 19.9845 33.0071C27.1509 33.0071 32.9814 27.1721 32.9814 19.9999C32.9814 12.8279 27.1509 6.99291 19.9845 6.99291ZM19.9845 26.7313C16.2694 26.7313 13.2585 23.718 13.2585 19.9999C13.2585 16.282 16.2694 13.2687 19.9845 13.2687C23.6996 13.2687 26.7105 16.282 26.7105 19.9999C26.7105 23.718 23.6996 26.7313 19.9845 26.7313Z"
fill="currentcolor"
></path>
<path
clipRule="evenodd"
d="M21.0734 4.85648V0C31.621 0.564369 40 9.30362 40 19.9999C40 30.6963 31.621 39.4332 21.0734 40V35.1435C28.9344 34.5815 35.1594 28.0078 35.1594 19.9999C35.1594 11.9922 28.9344 5.41843 21.0734 4.85648ZM8.52181 29.931C6.43794 27.5233 5.09469 24.4568 4.85508 21.09H0C0.251709 25.8011 2.13468 30.0763 5.08501 33.368L8.51938 29.931H8.52181ZM18.8951 40V35.1435C15.5285 34.9037 12.4644 33.5619 10.0587 31.4739L6.62435 34.9109C9.91593 37.866 14.1876 39.7481 18.8927 40H18.8951Z"
fill="url(#:Sb:paint0_linear_902_224)"
fillRule="evenodd"
></path>
<defs>
<linearGradient
gradientUnits="userSpaceOnUse"
id=":Sb:paint0_linear_902_224"
x1="21.8576"
x2="2.17018"
y1="2.81244"
y2="22.4844"
>
<stop stopColor="#0096FF"></stop>
<stop offset="1" stopColor="#FF1E56"></stop>
</linearGradient>
</defs>
</svg>
);
};

View File

@@ -0,0 +1,8 @@
{
"extends": "@repo/typescript-config/react-library.json",
"compilerOptions": {
"outDir": "dist"
},
"include": ["src"],
"exclude": ["dist", "build", "node_modules"]
}

25
packages/ui/turbo.json Normal file
View File

@@ -0,0 +1,25 @@
{
"extends": ["//"],
"tasks": {
"build": {
"dependsOn": ["build:styles", "build:components"]
},
"build:styles": {
"outputs": ["dist/**"]
},
"build:components": {
"outputs": ["dist/**"]
},
"dev": {
"with": ["dev:styles", "dev:components"]
},
"dev:styles": {
"cache": false,
"persistent": true
},
"dev:components": {
"cache": false,
"persistent": true
}
}
}