migrate: replace NextAuth.js with Better Auth

- Install better-auth and @better-auth/drizzle-adapter
- Create src/lib/auth.ts with Better Auth configuration using bcrypt
- Update database schema: change auth table IDs from uuid to text
- Update route handler from /api/auth/[...nextauth] to /api/auth/[...all]
- Update tRPC context and middleware for Better Auth session handling
- Update client components to use Better Auth APIs (signIn, signOut)
- Update seed script with text-based IDs and correct account schema
- Fix type errors in wizard components (robotId, optional chaining)
- Fix API paths: api.robots.initialize -> api.robots.plugins.initialize
- Update auth router to use text IDs for Better Auth compatibility

Note: Auth tables were reset - users will need to re-register.
This commit is contained in:
Sean O'Connor
2026-03-21 23:03:55 -04:00
parent 4bed537943
commit 20d6d3de1a
37 changed files with 460 additions and 1182 deletions

View File

@@ -1,6 +1,6 @@
"use client";
import { signIn } from "next-auth/react";
import { signIn } from "~/lib/auth-client";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useState } from "react";
@@ -37,22 +37,21 @@ export default function SignInPage() {
}
try {
const result = await signIn("credentials", {
const result = await signIn.email({
email,
password,
redirect: false,
});
if (result?.error) {
setError("Invalid email or password");
if (result.error) {
setError(result.error.message || "Invalid email or password");
} else {
router.push("/");
router.refresh();
}
} catch (error: unknown) {
} catch (err: unknown) {
setError(
error instanceof Error
? error.message
err instanceof Error
? err.message
: "An error occurred. Please try again.",
);
} finally {

View File

@@ -1,6 +1,6 @@
"use client";
import { signOut, useSession } from "next-auth/react";
import { signOut, useSession } from "~/lib/auth-client";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
@@ -14,33 +14,29 @@ import {
} from "~/components/ui/card";
export default function SignOutPage() {
const { data: session, status } = useSession();
const { data: session, isPending } = useSession();
const router = useRouter();
const [isSigningOut, setIsSigningOut] = useState(false);
useEffect(() => {
// If user is not logged in, redirect to home
if (status === "loading") return; // Still loading
if (!session) {
if (!isPending && !session) {
router.push("/");
return;
}
}, [session, status, router]);
}, [session, isPending, router]);
const handleSignOut = async () => {
setIsSigningOut(true);
try {
await signOut({
callbackUrl: "/",
redirect: true,
});
await signOut();
router.push("/");
router.refresh();
} catch (error) {
console.error("Error signing out:", error);
setIsSigningOut(false);
}
};
if (status === "loading") {
if (isPending) {
return (
<div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-slate-50 to-slate-100">
<div className="text-center">
@@ -52,7 +48,7 @@ export default function SignOutPage() {
}
if (!session) {
return null; // Will redirect via useEffect
return null;
}
return (
@@ -80,7 +76,7 @@ export default function SignOutPage() {
<div className="rounded-md bg-blue-50 p-3 text-sm text-blue-700">
<p className="font-medium">
Currently signed in as:{" "}
{session.user.name ?? session.user.email}
{session.user?.name ?? session.user?.email}
</p>
</div>
@@ -103,7 +99,8 @@ export default function SignOutPage() {
{/* Footer */}
<div className="mt-8 text-center text-xs text-slate-500">
<p>
© 2024 HRIStudio. A platform for Human-Robot Interaction research.
© {new Date().getFullYear()} HRIStudio. A platform for Human-Robot
Interaction research.
</p>
</div>
</div>