diff --git a/app/api/pharmacy/order/route.ts b/app/api/pharmacy/order/route.ts new file mode 100644 index 0000000..7bc78f1 --- /dev/null +++ b/app/api/pharmacy/order/route.ts @@ -0,0 +1,131 @@ +import { prisma } from "@/app/utils/db"; +import { requireUser } from "@/lib/requireUser"; +import { NextResponse } from "next/server"; + +export async function POST(request: Request) { + try { + const user = await requireUser("PHARMACY"); + + if (!user || user.role !== "PHARMACY" || !user.pharmacy) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const body = await request.json(); + const { prescriptionId, items } = body; + + if (!prescriptionId || !items || items.length === 0) { + return NextResponse.json( + { error: "Missing required fields" }, + { status: 400 } + ); + } + + // Verify prescription exists and belongs to a patient + const prescription = await prisma.prescription.findUnique({ + where: { id: prescriptionId }, + include: { Patient: true }, + }); + + if (!prescription) { + return NextResponse.json( + { error: "Prescription not found" }, + { status: 404 } + ); + } + + // Calculate total amount + const totalAmount = items.reduce( + (total: number, item: { price: number; quantity: number }) => + total + item.price * item.quantity, + 0 + ); + + // Create the order + const order = await prisma.order.create({ + data: { + pharmacyId: user.pharmacy.id, + patientId: prescription.Patient.id, + prescriptionId: prescription.id, + totalAmount, + items: { + create: items.map( + (item: { name: string; price: number; quantity: number }) => ({ + name: item.name, + price: item.price, + quantity: item.quantity, + }) + ), + }, + }, + include: { + items: true, + Patient: true, + pharmacy: true, + }, + }); + + return NextResponse.json(order, { status: 201 }); + } catch (error) { + console.error("[ORDERS_POST]", error); + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 } + ); + } +} + +export async function GET(request: Request) { + try { + const user = await requireUser("PHARMACY"); + + if (!user || !["PHARMACY", "ADMIN"].includes(user.role)) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const { searchParams } = new URL(request.url); + const pharmacyId = searchParams.get("pharmacyId"); + const patientId = searchParams.get("patientId"); + + let where = {}; + + if (user.role === "PHARMACY" && user.pharmacy) { + where = { pharmacyId: user.pharmacy.id }; + } else if (pharmacyId) { + where = { pharmacyId }; + } + + if (patientId) { + where = { ...where, patientId }; + } + + const orders = await prisma.order.findMany({ + where, + include: { + items: true, + Patient: { + include: { + user: { + select: { + email: true, + phone: true, + }, + }, + }, + }, + pharmacy: true, + prescription: true, + }, + orderBy: { + createdAt: "desc", + }, + }); + + return NextResponse.json(orders); + } catch (error) { + console.error("[ORDERS_GET]", error); + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 } + ); + } +} diff --git a/app/dashboardNew/patient/page.tsx b/app/dashboardNew/patient/page.tsx deleted file mode 100644 index d8c6884..0000000 --- a/app/dashboardNew/patient/page.tsx +++ /dev/null @@ -1,281 +0,0 @@ -import { prisma } from "@/app/utils/db"; -import { signOut } from "@/lib/auth"; -import { requireUser } from "@/lib/requireUser"; -import { redirect } from "next/navigation"; -import Link from "next/link"; - -export default async function PatientDashboard() { - const user = await requireUser("PATIENT"); - - // Fetch complete patient data with all relations - const patient = await prisma.patient.findUnique({ - where: { - userId: user.id, - }, - include: { - geoLocation: true, - prescriptions: { - orderBy: { - createdAt: "desc", - }, - take: 5, // Only get the 5 most recent prescriptions - }, - subscriptions: { - include: { - medicine: { - include: { - pharmacy: true, - }, - }, - }, - orderBy: { - nextDelivery: "asc", - }, - }, - orders: { - include: { - medicines: { - include: { - medicine: true, - }, - }, - pharmacy: true, - payment: true, - delivery: true, - }, - orderBy: { - createdAt: "desc", - }, - take: 5, // Only get the 5 most recent orders - }, - }, - }); - - if (!patient) { - redirect("/auth/complete-profile"); - } - - return ( -
- {/* Header Section */} -
-
-

Welcome, {patient.fullName}

-
-
{ - "use server"; - await signOut(); - redirect("/login"); - }} - > - -
-
- - {/* Personal Information Card */} -
-

Personal Information

-
-
-

Date of Birth

-

{new Date(patient.dateOfBirth).toLocaleDateString()}

-
-
-

Gender

-

{patient.gender || "Not specified"}

-
-
-

Email

-

{user.email}

-
-
-
- - {/* Address Card */} -
-

Address

-
-

{patient.streetAddress}

-

- {patient.city}, {patient.stateProvince} {patient.postalCode} -

-

{patient.country}

- {patient.geoLocation && ( -

- Location coordinates: {patient.geoLocation.latitude.toFixed(4)},{" "} - {patient.geoLocation.longitude.toFixed(4)} -

- )} -
-
- - {/* Medical Information Card */} -
-

Medical Information

-
-
-

Medical Conditions

-

- {patient.medicalConditions || "None reported"} -

-
-
-

Allergies

-

- {patient.allergies || "None reported"} -

-
-
-
- - {/* Recent Prescriptions Card */} -
-
-

Recent Prescriptions

- - View All - -
- {patient.prescriptions.length > 0 ? ( -
- {patient.prescriptions.map((prescription) => ( -
-

- {new Date(prescription.createdAt).toLocaleDateString()} -

- - View Prescription - -
- ))} -
- ) : ( -

No prescriptions found

- )} -
- - {/* Active Subscriptions Card */} -
-
-

Active Subscriptions

- - View All - -
- {patient.subscriptions.length > 0 ? ( -
- {patient.subscriptions.map((subscription) => ( -
-
-
-

- {subscription.medicine.name} -{" "} - {subscription.medicine.pharmacy.name} -

-

{subscription.frequency} day delivery frequency

-
-
-

- Next delivery:{" "} - {new Date(subscription.nextDelivery).toLocaleDateString()} -

-

- {subscription.medicine.pharmacy.city} -

-
-
-
- ))} -
- ) : ( -

No active subscriptions

- )} -
- - {/* Recent Orders Card */} -
-
-

Recent Orders

- - View All - -
- {patient.orders.length > 0 ? ( -
- {patient.orders.map((order) => ( -
-
-
-

- Order #{order.id.slice(-6).toUpperCase()} -

-

- {order.pharmacy.name} -{" "} - {new Date(order.createdAt).toLocaleDateString()} -

-
- {order.medicines.slice(0, 2).map((item) => ( -

- {item.quantity}x {item.medicine.name} -

- ))} - {order.medicines.length > 2 && ( -

- +{order.medicines.length - 2} more items -

- )} -
-
-
-

- ${order.totalAmount.toFixed(2)} -

-

- Status: {order.delivery ? "Shipped" : "Processing"} -

- {order.delivery?.trackingId && ( -

- Track #{order.delivery.trackingId} -

- )} -
-
-
- ))} -
- ) : ( -

No recent orders

- )} -
-
- ); -} diff --git a/app/layout.tsx b/app/layout.tsx index b3963b0..8e583ca 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,33 +1,27 @@ -import type React from "react" -import type { Metadata } from "next" -import { Inter } from "next/font/google" -import "./globals.css" -import { ThemeProvider } from "@/components/theme-provider" +import type React from "react"; +import type { Metadata } from "next"; +import { Inter } from "next/font/google"; +import "./globals.css"; +import { ThemeProvider } from "@/components/theme-provider"; -const inter = Inter({ subsets: ["latin"] }) +const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { title: "Medi-Link", description: "Bridging Patients & Pharmacies Effortlessly", - generator: 'v0.dev' -} + generator: "v0.dev", +}; export default function RootLayout({ children, }: Readonly<{ - children: React.ReactNode + children: React.ReactNode; }>) { return ( - - - {children} - - + {children} - ) + ); } - - -import './globals.css' \ No newline at end of file +import "./globals.css"; diff --git a/app/login/page.tsx b/app/login/page.tsx index 555cff6..addfa41 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -50,9 +50,7 @@ export default function LoginPage() { // Redirect based on the role from session router.push( - session.user.role === "PHARMACY" - ? "/dashboardNew/pharmacy" - : "/dashboardNew/patient" + session.user.role === "PHARMACY" ? "/site/pharmacy" : "/site/patient" ); } catch (error) { toast({ diff --git a/app/pharmacy/orders/page.tsx b/app/pharmacy/orders/page.tsx index 4265ea7..c66da24 100644 --- a/app/pharmacy/orders/page.tsx +++ b/app/pharmacy/orders/page.tsx @@ -1,46 +1,25 @@ -"use client" +"use client"; -import { useEffect, useState } from "react" -import { useRouter } from "next/navigation" -import Image from "next/image" -import { Search, Filter } from "lucide-react" -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import PharmacySidebar from "@/components/pharmacy-sidebar" -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; +import Image from "next/image"; +import { Search, Filter } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import PharmacySidebar from "@/components/pharmacy-sidebar"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; type Order = { - id: number - name: string - date: string - avatar: string - status: "pending" | "accepted" | "completed" -} + id: number; + name: string; + date: string; + avatar: string; + status: "pending" | "accepted" | "completed"; +}; export default function PharmacyOrdersPage() { - const [user, setUser] = useState(null) - const router = useRouter() - - useEffect(() => { - // Check if user is logged in - const userData = localStorage.getItem("user") - if (!userData) { - router.push("/login") - return - } - - const parsedUser = JSON.parse(userData) - if (parsedUser.type !== "pharmacy") { - router.push("/login") - return - } - - setUser(parsedUser) - }, [router]) - - if (!user) { - return null // Loading state - } + const [user, setUser] = useState(null); + const router = useRouter(); return (
@@ -100,53 +79,67 @@ export default function PharmacyOrdersPage() { - {[...pendingOrders, ...acceptedOrders, ...completedOrders].map((order) => ( -
-
-
-
- {order.name} + {[...pendingOrders, ...acceptedOrders, ...completedOrders].map( + (order) => ( +
+
+
+
+ {order.name} +
+
+

{order.name}

+

+ {order.date} +

+
-
-

{order.name}

-

{order.date}

-
-
-
- - + + +
-
- ))} + ) + )} {pendingOrders.map((order) => ( -
+
@@ -164,10 +157,17 @@ export default function PharmacyOrdersPage() {
- -
@@ -178,7 +178,10 @@ export default function PharmacyOrdersPage() { {acceptedOrders.map((order) => ( -
+
@@ -196,10 +199,17 @@ export default function PharmacyOrdersPage() {
- -
@@ -210,7 +220,10 @@ export default function PharmacyOrdersPage() { {completedOrders.map((order) => ( -
+
@@ -228,10 +241,17 @@ export default function PharmacyOrdersPage() {
- -
@@ -244,7 +264,7 @@ export default function PharmacyOrdersPage() {
- ) + ); } const pendingOrders: Order[] = [ @@ -262,7 +282,7 @@ const pendingOrders: Order[] = [ avatar: "/placeholder.svg?height=64&width=64", status: "pending", }, -] +]; const acceptedOrders: Order[] = [ { @@ -279,7 +299,7 @@ const acceptedOrders: Order[] = [ avatar: "/placeholder.svg?height=64&width=64", status: "accepted", }, -] +]; const completedOrders: Order[] = [ { @@ -296,5 +316,4 @@ const completedOrders: Order[] = [ avatar: "/placeholder.svg?height=64&width=64", status: "completed", }, -] - +]; diff --git a/app/site/components/pharmacy/OrderForm.tsx b/app/site/components/pharmacy/OrderForm.tsx new file mode 100644 index 0000000..dc69df4 --- /dev/null +++ b/app/site/components/pharmacy/OrderForm.tsx @@ -0,0 +1,175 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { toast } from "sonner"; + +interface OrderItem { + name: string; + price: string; + quantity: string; +} + +export function OrderForm({ prescription }: { prescription: string }) { + const router = useRouter(); + const [items, setItems] = useState([ + { name: "", price: "", quantity: "1" }, + ]); + const [isSubmitting, setIsSubmitting] = useState(false); + + // Calculate total amount by parsing the string values + const totalAmount = items.reduce((sum, item) => { + const price = parseFloat(item.price) || 0; + const quantity = parseInt(item.quantity) || 0; + return sum + price * quantity; + }, 0); + + const handleAddItem = () => { + setItems([...items, { name: "", price: "", quantity: "1" }]); + }; + + const handleRemoveItem = (index: number) => { + if (items.length > 1) { + const newItems = [...items]; + newItems.splice(index, 1); + setItems(newItems); + } + }; + + const handleItemChange = ( + index: number, + field: keyof OrderItem, + value: string + ) => { + const newItems = [...items]; + newItems[index] = { ...newItems[index], [field]: value }; + setItems(newItems); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setIsSubmitting(true); + + // Convert string values to numbers before submitting + const itemsToSubmit = items.map((item) => ({ + ...item, + price: parseFloat(item.price) || 0, + quantity: parseInt(item.quantity) || 1, + })); + + const createOrderPromise = new Promise(async (resolve, reject) => { + try { + const response = await fetch("/api/pharmacy/order", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + prescriptionId: prescription, + items: itemsToSubmit, // Use the converted items + }), + }); + + if (!response.ok) { + throw new Error("Failed to create order"); + } + + const data = await response.json(); + resolve(data); + router.push("/pharmacy/orders"); + } catch (error) { + console.error("Error creating order:", error); + reject(error); + } finally { + setIsSubmitting(false); + } + }); + + toast.promise(createOrderPromise, { + loading: "Creating order...", + success: (data: any) => { + return `Order #${data.id} has been created successfully`; + }, + error: "Failed to create order. Please try again.", + }); + }; + + return ( +
+
+ {items.map((item, index) => ( +
+
+ + + handleItemChange(index, "name", e.target.value) + } + required + /> +
+
+ + + handleItemChange(index, "price", e.target.value) + } + placeholder="0.00" + required + /> +
+
+ + + handleItemChange(index, "quantity", e.target.value) + } + required + /> +
+
+ +
+
+ ))} +
+ +
+ + +
+ Total: ${totalAmount.toFixed(2)} +
+
+ +
+ +
+
+ ); +} diff --git a/app/site/patient/page.tsx b/app/site/patient/page.tsx new file mode 100644 index 0000000..ce64610 --- /dev/null +++ b/app/site/patient/page.tsx @@ -0,0 +1,31 @@ +import { prisma } from "@/app/utils/db"; +import { signOut } from "@/lib/auth"; +import { requireUser } from "@/lib/requireUser"; +import { redirect } from "next/navigation"; +import Link from "next/link"; + +export default async function PatientDashboard() { + const user = await requireUser("PATIENT"); + + // Fetch complete patient data with all relations + const patient = await prisma.patient.findUnique({ + where: { + userId: user.id, + }, + include: { + geoLocation: true, + prescriptions: { + orderBy: { + createdAt: "desc", + }, + take: 5, // Only get the 5 most recent prescriptions + }, + }, + }); + + if (!patient) { + redirect("/auth/complete-profile"); + } + + return
This is patient header
; +} diff --git a/app/dashboardNew/pharmacy/page.tsx b/app/site/pharmacy/page.tsx similarity index 82% rename from app/dashboardNew/pharmacy/page.tsx rename to app/site/pharmacy/page.tsx index 36fc160..2f99c28 100644 --- a/app/dashboardNew/pharmacy/page.tsx +++ b/app/site/pharmacy/page.tsx @@ -2,7 +2,7 @@ import { signOut } from "@/lib/auth"; import { requireUser } from "@/lib/requireUser"; export default async function PharmacyDashboard() { - const user = await requireUser("PHARMACY"); // Note: You're requiring PHARMACY role but component is named PatientDashboard + const user = await requireUser("PHARMACY"); return (
diff --git a/app/site/pharmacy/prescriptions/[prescriptionId]/page.tsx b/app/site/pharmacy/prescriptions/[prescriptionId]/page.tsx new file mode 100644 index 0000000..2fc933b --- /dev/null +++ b/app/site/pharmacy/prescriptions/[prescriptionId]/page.tsx @@ -0,0 +1,21 @@ +import { OrderForm } from "@/app/site/components/pharmacy/OrderForm"; +import { requireUser } from "@/lib/requireUser"; + +export default async function ReviewOrder({ + params, +}: { + params: Promise<{ prescriptionId: string }>; +}) { + const user = await requireUser("PHARMACY"); + + const { prescriptionId } = await params; + + return ( +
+

Hello {user.pharmacy?.id}

+

Hello {user.id}

+

This is prescription {prescriptionId}

+ +
+ ); +} diff --git a/middleware.ts b/middleware.ts index 6fbca47..f10c48c 100644 --- a/middleware.ts +++ b/middleware.ts @@ -6,11 +6,11 @@ import { requireUser } from "./lib/requireUser"; // Define the protected routes and their allowed roles const protectedRoutes = [ { - path: "/dashboardNew/patient", + path: "/site/patient", roles: ["PATIENT"], }, { - path: "/dashboardNew/pharmacy", + path: "/site/pharmacy", roles: ["PHARMACY"], }, { diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b45d621..26d252b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -20,7 +20,6 @@ model User { sessions Session[] authenticator Authenticator[] - // Profile relations (only one will be populated based on role) patient Patient? pharmacy Pharmacy? } @@ -31,7 +30,6 @@ enum Role { ADMIN } -// Authentication models (unchanged from your current schema) model Account { id String @id @default(auto()) @map("_id") @db.ObjectId userId String @db.ObjectId @@ -102,9 +100,9 @@ model Patient { } model Pharmacy { - id String @id @default(auto()) @map("_id") @db.ObjectId - userId String @unique @db.ObjectId - user User @relation(fields: [userId], references: [id]) + id String @id @default(auto()) @map("_id") @db.ObjectId + userId String @unique @db.ObjectId + user User @relation(fields: [userId], references: [id]) name String phone String streetAddress String @@ -112,57 +110,49 @@ model Pharmacy { stateProvince String postalCode String country String - geoLocation GeoLocation? @relation(fields: [geoLocationId], references: [id]) - geoLocationId String? @db.ObjectId - medicines Medicine[] + geoLocation GeoLocation? @relation(fields: [geoLocationId], references: [id]) + geoLocationId String? @db.ObjectId orders Order[] - licenseNumber String @unique + licenseNumber String @unique licenseFile String? - verified Boolean @default(false) + verified Boolean @default(false) + Prescription Prescription[] } model Prescription { - id String @id @default(auto()) @map("_id") @db.ObjectId - PharmacistId String @db.ObjectId - Patient Patient @relation(fields: [PharmacistId], references: [id]) - fileUrl String // URL to the uploaded prescription image/file - createdAt DateTime @default(now()) -} - -model Medicine { - id String @id @default(auto()) @map("_id") @db.ObjectId - pharmacyId String @db.ObjectId - pharmacy Pharmacy @relation(fields: [pharmacyId], references: [id]) - name String - price Float - stock Int - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - OrderMedicine OrderMedicine[] - Subscription Subscription[] + id String @id @default(auto()) @map("_id") @db.ObjectId + patientId String @db.ObjectId + patient Patient @relation(fields: [patientId], references: [id]) + fileUrl String // URL to the uploaded prescription image/file + createdAt DateTime @default(now()) + pharmacyId String @db.ObjectId + pharmacy Pharmacy @relation(fields: [pharmacyId], references: [id]) + Order Order[] } model Order { - id String @id @default(auto()) @map("_id") @db.ObjectId - PharmacistId String @db.ObjectId - Patient Patient @relation(fields: [PharmacistId], references: [id]) - pharmacyId String @db.ObjectId - pharmacy Pharmacy @relation(fields: [pharmacyId], references: [id]) - medicines OrderMedicine[] - totalAmount Float - payment Payment? - delivery Delivery? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt -} - -model OrderMedicine { - id String @id @default(auto()) @map("_id") @db.ObjectId - orderId String @db.ObjectId - order Order @relation(fields: [orderId], references: [id]) - medicineId String @db.ObjectId - medicine Medicine @relation(fields: [medicineId], references: [id]) - quantity Int + id String @id @default(auto()) @map("_id") @db.ObjectId + pharmacyId String @db.ObjectId + pharmacy Pharmacy @relation(fields: [pharmacyId], references: [id]) + items OrderItem[] + totalAmount Float + payment Payment? + delivery Delivery? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + Patient Patient? @relation(fields: [patientId], references: [id]) + patientId String? @db.ObjectId + prescriptionId String @db.ObjectId + prescription Prescription @relation(fields: [prescriptionId], references: [id]) +} + +model OrderItem { + id String @id @default(auto()) @map("_id") @db.ObjectId + orderId String @db.ObjectId + order Order @relation(fields: [orderId], references: [id]) + name String + price Float + quantity Int } model Payment { @@ -177,9 +167,7 @@ model Subscription { id String @id @default(auto()) @map("_id") @db.ObjectId PharmacistId String @db.ObjectId Patient Patient @relation(fields: [PharmacistId], references: [id]) - medicineId String @db.ObjectId - medicine Medicine @relation(fields: [medicineId], references: [id]) - frequency Int // Days between each delivery + frequency Int nextDelivery DateTime createdAt DateTime @default(now()) updatedAt DateTime @updatedAt