diff --git a/package.json b/package.json index c646905..a7fd19e 100644 --- a/package.json +++ b/package.json @@ -12,12 +12,14 @@ "@clerk/nextjs": "^6.10.2", "@radix-ui/react-dialog": "^1.1.5", "@radix-ui/react-dropdown-menu": "^2.1.5", + "@radix-ui/react-label": "^2.1.1", "@radix-ui/react-navigation-menu": "^1.2.4", "@radix-ui/react-separator": "^1.1.1", "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-tooltip": "^1.1.7", "@vercel/analytics": "^1.4.1", "@vercel/speed-insights": "^1.1.0", + "axios": "^1.7.9", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "hoyahax-2025": "file:", diff --git a/src/app/(web)/account/page.jsx b/src/app/(web)/account/page.jsx new file mode 100644 index 0000000..7a6711f --- /dev/null +++ b/src/app/(web)/account/page.jsx @@ -0,0 +1,157 @@ +"use client"; +import { useState, useEffect } from 'react'; +import axios from 'axios'; +import { Button } from '../../../components/ui/button'; +import { Input } from '../../../components/ui/input'; +import { Label } from '../../../components/ui/label'; +import { Card, CardHeader, CardContent, CardFooter } from '../../../components/ui/card'; + +const AccountPage = () => { + const [user, setUser] = useState(null); + const [patients, setPatients] = useState([]); + const [selectedPatient, setSelectedPatient] = useState(null); + const [medications, setMedications] = useState([]); + const [diagnoses, setDiagnoses] = useState([]); + + useEffect(() => { + axios.get('/api/user').then(response => { + setUser(response.data); + if (response.data.role === 'caregiver') { + axios.get('/api/patients').then(res => setPatients(res.data)); + } + }); + }, []); + + const handleRoleChange = async () => { + const newRole = user.role === 'patient' ? 'caregiver' : 'patient'; + await axios.put('/api/user', { role: newRole }); + setUser({ ...user, role: newRole }); + if (newRole === 'caregiver') { + axios.get('/api/patients').then(res => setPatients(res.data)); + } else { + setPatients([]); + setSelectedPatient(null); + } + }; + + const handlePatientSelect = (patient) => { + setSelectedPatient(patient); + setMedications(patient.medications); + setDiagnoses(patient.medicalConditions); + }; + + const handleMedicationsChange = (index, field, value) => { + const updatedMedications = [...medications]; + updatedMedications[index][field] = value; + setMedications(updatedMedications); + }; + + const handleDiagnosesChange = (e) => { + setDiagnoses(e.target.value.split(',')); + }; + + const handleSave = async () => { + await axios.put(`/api/patients/${selectedPatient.email}`, { + medications, + medicalConditions: diagnoses, + }); + alert('Patient data updated successfully'); + }; + + if (!user) return
Loading...
; + + return ( +
+ + +

Account Page

+
+ +
+ +

{user.name}

+
+
+ +

{user.email}

+
+
+ +

{user.role}

+
+ + + {user.role === 'caregiver' && ( +
+

Patients

+
    + {patients.map(patient => ( +
  • handlePatientSelect(patient)} + className="cursor-pointer hover:bg-gray-200 p-2" + > + {patient.name} +
  • + ))} +
+ + {selectedPatient && ( + + +

Edit Patient: {selectedPatient.name}

+
+ +
+ + {medications.map((medication, index) => ( +
+ handleMedicationsChange(index, 'name', e.target.value)} + className="mb-2" + /> + handleMedicationsChange(index, 'dosage', e.target.value)} + className="mb-2" + /> + handleMedicationsChange(index, 'frequency', e.target.value)} + className="mb-2" + /> +
+ ))} +
+
+ + +
+
+ + + +
+ )} +
+ )} +
+
+
+ ); +}; + +export default AccountPage; \ No newline at end of file diff --git a/src/app/api/layout.tsx b/src/app/api/layout.tsx new file mode 100644 index 0000000..a14e64f --- /dev/null +++ b/src/app/api/layout.tsx @@ -0,0 +1,16 @@ +export const metadata = { + title: 'Next.js', + description: 'Generated by Next.js', +} + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} diff --git a/src/app/api/patients.js b/src/app/api/patients.js new file mode 100644 index 0000000..d8fd75b --- /dev/null +++ b/src/app/api/patients.js @@ -0,0 +1,9 @@ +import User from '../../models/User'; +import { connectDB } from '../../lib/utils'; + +export default async (req, res) => { + await connectDB(); + + const patients = await User.find({ role: 'patient' }); + res.json(patients); +}; \ No newline at end of file diff --git a/src/app/api/patients/[email].js b/src/app/api/patients/[email].js new file mode 100644 index 0000000..4e97c58 --- /dev/null +++ b/src/app/api/patients/[email].js @@ -0,0 +1,21 @@ +import User from '../../../models/User'; +import connectDB from '../../../lib/utils'; + +export default async (req, res) => { + await connectDB(); + + const { email } = req.query; + const { medications, medicalConditions } = req.body; + + const patient = await User.findOne({ email }); + + if (!patient) { + return res.status(404).json({ message: 'Patient not found' }); + } + + patient.medications = medications; + patient.medicalConditions = medicalConditions; + await patient.save(); + + res.json(patient); +}; \ No newline at end of file diff --git a/src/app/api/user.js b/src/app/api/user.js new file mode 100644 index 0000000..03f6bb1 --- /dev/null +++ b/src/app/api/user.js @@ -0,0 +1,29 @@ +import { getAuth } from '@clerk/nextjs/server'; +import User from '../../models/User'; +import { connectDB } from '../../lib/utils'; + +export default async (req, res) => { + await connectDB(); + const { userId } = getAuth(req); + + if (!userId) { + return res.status(401).json({ message: 'Unauthorized' }); + } + + const user = await User.findOne({ id: userId }); + + if (!user) { + return res.status(404).json({ message: 'User not found' }); + } + + if (req.method === 'GET') { + res.json(user); + } else if (req.method === 'PUT') { + const { role } = req.body; + user.role = role; + await user.save(); + res.json(user); + } else { + res.status(405).json({ message: 'Method not allowed' }); + } +}; \ No newline at end of file diff --git a/src/app/api/webhook/route.js b/src/app/api/webhook/route.js index 3e00237..f059fad 100644 --- a/src/app/api/webhook/route.js +++ b/src/app/api/webhook/route.js @@ -1,42 +1,10 @@ import { User } from '../../../models/User'; import { NextResponse } from 'next/server'; import { Webhook } from 'svix'; -import mongoose from "mongoose"; +import { connectDB } from '../../../lib/utils'; const CLERK_WEBHOOK_SECRET = process.env.CLERK_WEBHOOK_SECRET; -const DATABASE_URL = process.env.MONGO_URI; - -let cached = global.mongoose; - -if (!cached) { - cached = global.mongoose = { conn: null, promise: null }; -} -async function connectDB() { - if (cached.conn) { - return cached.conn; - } - - if (!cached.promise) { - const opts = { - bufferCommands: false, - }; - - try { - cached.promise = mongoose.connect(DATABASE_URL, opts).then((mongoose) => { - console.log('MongoDB connected successfully'); - return mongoose; - }); - } catch (error) { - console.error('Error connecting to MongoDB:', error.message); - throw new Error('Error connecting to MongoDB'); - } - } - - cached.conn = await cached.promise; - return cached.conn; -} - export async function POST(req) { console.log('Received request:', req); @@ -106,6 +74,7 @@ export async function POST(req) { } user = new User({ + id, name, email, role: 'patient', diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 0000000..cabfbfc --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,76 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +Card.displayName = "Card" + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardHeader.displayName = "CardHeader" + +const CardTitle = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardDescription.displayName = "CardDescription" + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardContent.displayName = "CardContent" + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardFooter.displayName = "CardFooter" + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx new file mode 100644 index 0000000..5341821 --- /dev/null +++ b/src/components/ui/label.tsx @@ -0,0 +1,26 @@ +"use client" + +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" +) + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, ...props }, ref) => ( + +)) +Label.displayName = LabelPrimitive.Root.displayName + +export { Label } diff --git a/src/lib/utils.ts b/src/lib/utils.ts index bd0c391..710c273 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,6 +1,46 @@ -import { clsx, type ClassValue } from "clsx" -import { twMerge } from "tailwind-merge" +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; +import mongoose, { Mongoose } from "mongoose"; -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)) +export function cn(...inputs: ClassValue[]): string { + return twMerge(clsx(inputs)); } + +interface Cached { + conn: Mongoose | null; + promise: Promise | null; +} + +declare global { + var mongoose: Cached; +} + +export async function connectDB(): Promise { + const DATABASE_URL = process.env.MONGO_URI as string; + + let cached: Cached = global.mongoose; + + if (!cached) { + cached = global.mongoose = { conn: null, promise: null }; + } + if (cached.conn) { + return cached.conn; + } + + if (!cached.promise) { + const opts = { + bufferCommands: false, + }; + + cached.promise = mongoose.connect(DATABASE_URL, opts).then((mongoose) => { + console.log('MongoDB connected successfully'); + return mongoose; + }).catch((error) => { + console.error('Error connecting to MongoDB:', error.message); + throw new Error('Error connecting to MongoDB'); + }); + } + + cached.conn = await cached.promise; + return cached.conn; +} \ No newline at end of file diff --git a/src/models/User.js b/src/models/User.js index 5f8a7e9..e5a8cba 100644 --- a/src/models/User.js +++ b/src/models/User.js @@ -2,6 +2,11 @@ import { Schema, model, models } from "mongoose"; const UserSchema = new Schema( { + id: { + type: String, + required: true, + unique: true, + }, name: { type: String, required: true, diff --git a/tsconfig.json b/tsconfig.json index 279409d..b7a29c2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,6 +22,6 @@ "@/*": ["./src/*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "src/app/api/connectDB.js"], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "src/app/api/connectDB.js", "src/lib/utils.js", "src/app/(web)/account/page.jsx"], "exclude": ["node_modules"] }