accounts page

This commit is contained in:
Joseph J Helfenbein
2025-01-25 06:13:43 -05:00
parent 1db24c5ceb
commit 1f4c828b30
12 changed files with 388 additions and 38 deletions

View File

@@ -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:",

View File

@@ -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 <div>Loading...</div>;
return (
<div className="container mx-auto p-4">
<Card>
<CardHeader>
<h1 className="text-2xl font-bold">Account Page</h1>
</CardHeader>
<CardContent>
<div className="mb-4">
<Label>Name:</Label>
<p>{user.name}</p>
</div>
<div className="mb-4">
<Label>Email:</Label>
<p>{user.email}</p>
</div>
<div className="mb-4">
<Label>Role:</Label>
<p>{user.role}</p>
</div>
<Button onClick={handleRoleChange} className="mb-4">
Change role to {user.role === 'patient' ? 'caregiver' : 'patient'}
</Button>
{user.role === 'caregiver' && (
<div>
<h2 className="text-xl font-bold mb-4">Patients</h2>
<ul className="mb-4">
{patients.map(patient => (
<li
key={patient.email}
onClick={() => handlePatientSelect(patient)}
className="cursor-pointer hover:bg-gray-200 p-2"
>
{patient.name}
</li>
))}
</ul>
{selectedPatient && (
<Card>
<CardHeader>
<h3 className="text-xl font-bold">Edit Patient: {selectedPatient.name}</h3>
</CardHeader>
<CardContent>
<div className="mb-4">
<Label>Medications:</Label>
{medications.map((medication, index) => (
<div key={index} className="mb-2">
<Input
type="text"
placeholder="Name"
value={medication.name}
onChange={(e) => handleMedicationsChange(index, 'name', e.target.value)}
className="mb-2"
/>
<Input
type="text"
placeholder="Dosage"
value={medication.dosage}
onChange={(e) => handleMedicationsChange(index, 'dosage', e.target.value)}
className="mb-2"
/>
<Input
type="text"
placeholder="Frequency"
value={medication.frequency}
onChange={(e) => handleMedicationsChange(index, 'frequency', e.target.value)}
className="mb-2"
/>
</div>
))}
</div>
<div className="mb-4">
<Label>Diagnoses:</Label>
<Input
type="text"
value={diagnoses.join(',')}
onChange={handleDiagnosesChange}
/>
</div>
</CardContent>
<CardFooter>
<Button onClick={handleSave}>Save</Button>
</CardFooter>
</Card>
)}
</div>
)}
</CardContent>
</Card>
</div>
);
};
export default AccountPage;

16
src/app/api/layout.tsx Normal file
View File

@@ -0,0 +1,16 @@
export const metadata = {
title: 'Next.js',
description: 'Generated by Next.js',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}

9
src/app/api/patients.js Normal file
View File

@@ -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);
};

View File

@@ -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);
};

29
src/app/api/user.js Normal file
View File

@@ -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' });
}
};

View File

@@ -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',

View File

@@ -0,0 +1,76 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-xl border bg-card text-card-foreground shadow",
className
)}
{...props}
/>
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("font-semibold leading-none tracking-tight", className)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }

View File

@@ -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<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
))
Label.displayName = LabelPrimitive.Root.displayName
export { Label }

View File

@@ -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<Mongoose> | null;
}
declare global {
var mongoose: Cached;
}
export async function connectDB(): Promise<Mongoose> {
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;
}

View File

@@ -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,

View File

@@ -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"]
}