mirror of
https://github.com/soconnor0919/hristudio.git
synced 2025-12-11 22:54:45 -05:00
create database connection
This commit is contained in:
31
src/app/api/studies/route.ts
Normal file
31
src/app/api/studies/route.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { db } from "~/server/db";
|
||||
import { studies } from "~/server/db/schema";
|
||||
import { NextResponse } from "next/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
export async function GET() {
|
||||
const allStudies = await db.select().from(studies);
|
||||
return NextResponse.json(allStudies);
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const { title, description } = await request.json();
|
||||
const newStudy = await db.insert(studies).values({ title, description }).returning();
|
||||
return NextResponse.json(newStudy[0]);
|
||||
}
|
||||
|
||||
export async function PUT(request: Request) {
|
||||
const { id, title, description } = await request.json();
|
||||
const updatedStudy = await db
|
||||
.update(studies)
|
||||
.set({ title, description })
|
||||
.where(eq(studies.id, id))
|
||||
.returning();
|
||||
return NextResponse.json(updatedStudy[0]);
|
||||
}
|
||||
|
||||
export async function DELETE(request: Request) {
|
||||
const { id } = await request.json();
|
||||
await db.delete(studies).where(eq(studies.id, id));
|
||||
return NextResponse.json({ message: "Study deleted" });
|
||||
}
|
||||
@@ -1,9 +1,15 @@
|
||||
import { type PropsWithChildren } from "react"
|
||||
import { Sidebar } from "~/components/sidebar"
|
||||
import { inter } from "../layout"
|
||||
import { Inter } from "next/font/google"
|
||||
|
||||
import "~/styles/globals.css"
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ["latin"],
|
||||
display: "swap",
|
||||
variable: "--font-sans",
|
||||
})
|
||||
|
||||
export default function RootLayout({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<html lang="en">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '~/components/ui/card';
|
||||
import { Button } from '~/components/ui/button';
|
||||
import { Studies } from "~/components/Studies";
|
||||
|
||||
const HomePage: React.FC = () => {
|
||||
return (
|
||||
@@ -11,41 +12,7 @@ const HomePage: React.FC = () => {
|
||||
Manage your Human-Robot Interaction projects and experiments
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<Card className="bg-white shadow-lg">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl font-semibold text-blue-700">Projects</CardTitle>
|
||||
<CardDescription>Manage your HRI projects</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="mb-4">Create, edit, and analyze your HRI projects.</p>
|
||||
<Button className="bg-blue-600 hover:bg-blue-700 text-white">View Projects</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-white shadow-lg">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl font-semibold text-blue-700">Experiments</CardTitle>
|
||||
<CardDescription>Design and run experiments</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="mb-4">Set up, conduct, and analyze HRI experiments.</p>
|
||||
<Button className="bg-green-600 hover:bg-green-700 text-white">New Experiment</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-white shadow-lg">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl font-semibold text-blue-700">Data Analysis</CardTitle>
|
||||
<CardDescription>Analyze your research data</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="mb-4">Visualize and interpret your HRI research data.</p>
|
||||
<Button className="bg-purple-600 hover:bg-purple-700 text-white">Analyze Data</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<Studies />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { ClerkProvider } from '@clerk/nextjs'
|
||||
import { Inter } from "next/font/google"
|
||||
import { ThemeProvider } from "next-themes"
|
||||
|
||||
import "~/styles/globals.css"
|
||||
|
||||
export const inter = Inter({
|
||||
const inter = Inter({
|
||||
subsets: ["latin"],
|
||||
display: "swap",
|
||||
variable: "--font-sans",
|
||||
|
||||
@@ -52,7 +52,7 @@ export default function HomePage() {
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl font-semibold mb-4 text-blue-700">Join the HRI Revolution</h2>
|
||||
<p className="text-lg text-gray-700 mb-6">
|
||||
Whether you're a seasoned researcher or just starting in the field of Human-Robot Interaction,
|
||||
Whether you're a seasoned researcher or just starting in the field of Human-Robot Interaction,
|
||||
HRIStudio provides the tools and support you need to succeed.
|
||||
</p>
|
||||
<div className="space-x-4">
|
||||
|
||||
@@ -31,8 +31,9 @@ export default function SignInPage() {
|
||||
await setActive({ session: result.createdSessionId })
|
||||
router.push("/dash")
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error("Error:", err.errors[0].message)
|
||||
} catch (err) {
|
||||
const error = err as { errors?: { message: string }[] };
|
||||
console.error("Error:", error.errors?.[0]?.message ?? "Unknown error")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +43,8 @@ export default function SignInPage() {
|
||||
strategy,
|
||||
redirectUrl: "/sso-callback",
|
||||
redirectUrlComplete: "/dash",
|
||||
}).catch((error) => {
|
||||
console.error("Authentication error:", error); // Handle any potential errors
|
||||
})
|
||||
}
|
||||
|
||||
@@ -90,7 +93,7 @@ export default function SignInPage() {
|
||||
</CardContent>
|
||||
<CardFooter className="flex flex-col">
|
||||
<p className="mt-4 text-sm text-center">
|
||||
Don't have an account?{" "}
|
||||
Don't have an account?{" "}
|
||||
<Link href="/sign-up" className="text-blue-600 hover:underline">
|
||||
Sign up
|
||||
</Link>
|
||||
|
||||
@@ -31,8 +31,9 @@ export default function SignUpPage() {
|
||||
await setActive({ session: result.createdSessionId })
|
||||
router.push("/dash")
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error("Error:", err.errors[0].message)
|
||||
} catch (err) {
|
||||
const error = err as { errors?: { message: string }[] }; // Specify type
|
||||
console.error("Error:", error.errors?.[0]?.message ?? "Unknown error") // Use optional chaining
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +43,8 @@ export default function SignUpPage() {
|
||||
strategy,
|
||||
redirectUrl: "/sso-callback",
|
||||
redirectUrlComplete: "/dash",
|
||||
}).catch((error) => {
|
||||
console.error("Authentication error:", error); // Handle any potential errors
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
122
src/components/Studies.tsx
Normal file
122
src/components/Studies.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { Input } from "~/components/ui/input";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "~/components/ui/dialog";
|
||||
|
||||
interface Study {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export function Studies() {
|
||||
const [studies, setStudies] = useState<Study[]>([]);
|
||||
const [newStudy, setNewStudy] = useState({ title: '', description: '' });
|
||||
const [editingStudy, setEditingStudy] = useState<Study | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchStudies();
|
||||
}, []);
|
||||
|
||||
const fetchStudies = async () => {
|
||||
const response = await fetch('/api/studies');
|
||||
const data = await response.json();
|
||||
setStudies(data);
|
||||
};
|
||||
|
||||
const createStudy = async () => {
|
||||
const response = await fetch('/api/studies', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(newStudy),
|
||||
});
|
||||
const createdStudy = await response.json();
|
||||
setStudies([...studies, createdStudy]);
|
||||
setNewStudy({ title: '', description: '' });
|
||||
};
|
||||
|
||||
const updateStudy = async () => {
|
||||
if (!editingStudy) return;
|
||||
const response = await fetch('/api/studies', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(editingStudy),
|
||||
});
|
||||
const updatedStudy = await response.json();
|
||||
setStudies(studies.map(s => s.id === updatedStudy.id ? updatedStudy : s));
|
||||
setEditingStudy(null);
|
||||
};
|
||||
|
||||
const deleteStudy = async (id: number) => {
|
||||
await fetch('/api/studies', {
|
||||
method: 'DELETE',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id }),
|
||||
});
|
||||
setStudies(studies.filter(s => s.id !== id));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-2xl font-bold">Studies</h2>
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button>Create New Study</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create New Study</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Input
|
||||
placeholder="Title"
|
||||
value={newStudy.title}
|
||||
onChange={(e) => setNewStudy({ ...newStudy, title: e.target.value })}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Description"
|
||||
value={newStudy.description}
|
||||
onChange={(e) => setNewStudy({ ...newStudy, description: e.target.value })}
|
||||
/>
|
||||
<Button onClick={createStudy}>Create</Button>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
{studies.map((study) => (
|
||||
<Card key={study.id}>
|
||||
<CardHeader>
|
||||
<CardTitle>{study.title}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>{study.description}</p>
|
||||
<div className="flex space-x-2 mt-2">
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline" onClick={() => setEditingStudy(study)}>Edit</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit Study</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Input
|
||||
placeholder="Title"
|
||||
value={editingStudy?.title || ''}
|
||||
onChange={(e) => setEditingStudy({ ...editingStudy!, title: e.target.value })}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Description"
|
||||
value={editingStudy?.description || ''}
|
||||
onChange={(e) => setEditingStudy({ ...editingStudy!, description: e.target.value })}
|
||||
/>
|
||||
<Button onClick={updateStudy}>Update</Button>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<Button variant="destructive" onClick={() => deleteStudy(study.id)}>Delete</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -61,8 +61,8 @@ export function Sidebar() {
|
||||
<div className="flex items-center space-x-4">
|
||||
<UserButton />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-blue-800">{user?.fullName || 'User'}</p>
|
||||
<p className="text-xs text-blue-600">{user?.primaryEmailAddress?.emailAddress || 'user@example.com'}</p>
|
||||
<p className="text-sm font-medium text-blue-800">{user?.fullName ?? 'User'}</p>
|
||||
<p className="text-xs text-blue-600">{user?.primaryEmailAddress?.emailAddress ?? 'user@example.com'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,10 +2,8 @@ import * as React from "react"
|
||||
|
||||
import { cn } from "~/lib/utils"
|
||||
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
// Use React.InputHTMLAttributes<HTMLInputElement> directly in the component
|
||||
const Input = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
// Example model schema from the Drizzle docs
|
||||
// https://orm.drizzle.team/docs/sql-schema-declaration
|
||||
|
||||
import { sql } from "drizzle-orm";
|
||||
import { pgTableCreator } from "drizzle-orm/pg-core";
|
||||
import { ColumnBaseConfig, ColumnDataType, SQL, sql } from "drizzle-orm";
|
||||
import {
|
||||
index,
|
||||
pgTableCreator,
|
||||
pgTable,
|
||||
serial,
|
||||
integer,
|
||||
timestamp,
|
||||
varchar,
|
||||
ExtraConfigColumn,
|
||||
} from "drizzle-orm/pg-core";
|
||||
|
||||
/**
|
||||
@@ -30,7 +33,25 @@ export const posts = createTable(
|
||||
() => new Date()
|
||||
),
|
||||
},
|
||||
(example) => ({
|
||||
(example: { name: SQL<unknown> | Partial<ExtraConfigColumn<ColumnBaseConfig<ColumnDataType, string>>>; }) => ({
|
||||
nameIndex: index("name_idx").on(example.name),
|
||||
})
|
||||
);
|
||||
|
||||
export const studies = createTable(
|
||||
"study",
|
||||
{
|
||||
id: serial("id").primaryKey(),
|
||||
title: varchar("title", { length: 256 }).notNull(),
|
||||
description: varchar("description", { length: 1000 }),
|
||||
createdAt: timestamp("created_at", { withTimezone: true })
|
||||
.default(sql`CURRENT_TIMESTAMP`)
|
||||
.notNull(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).$onUpdate(
|
||||
() => new Date()
|
||||
),
|
||||
},
|
||||
(study: { title: SQL<unknown> | Partial<ExtraConfigColumn<ColumnBaseConfig<ColumnDataType, string>>>; }) => ({
|
||||
titleIndex: index("title_idx").on(study.title),
|
||||
})
|
||||
);
|
||||
Reference in New Issue
Block a user