Add user authentication with login/register

- Add users and sessions tables to database schema
- Add bcryptjs for password hashing
- Create auth API routes (login, register, logout, me)
- Add AuthProvider context for client-side auth state
- Update all API routes to require authentication and filter by userId
- Create login and register pages
- Add AppShell component for authenticated layout
- Update all pages to use AppShell and show user info
- Each user now has their own private entries and tags

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Gemini Agent
2026-01-24 06:18:41 +00:00
parent 5555c1e6b5
commit 1455b0acd1
27 changed files with 1039 additions and 75 deletions

View File

@@ -0,0 +1,88 @@
"use client";
import { createContext, useContext, useState, useEffect, useCallback } from "react";
import { useRouter, usePathname } from "next/navigation";
interface User {
id: string;
email: string;
name: string | null;
}
interface AuthContextType {
user: User | null;
isLoading: boolean;
logout: () => Promise<void>;
refresh: () => Promise<void>;
}
const AuthContext = createContext<AuthContextType>({
user: null,
isLoading: true,
logout: async () => {},
refresh: async () => {},
});
export function useAuth() {
return useContext(AuthContext);
}
const PUBLIC_PATHS = ["/login", "/register"];
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [isLoading, setIsLoading] = useState(true);
const router = useRouter();
const pathname = usePathname();
const fetchUser = useCallback(async () => {
try {
const res = await fetch("/api/auth/me");
const data = await res.json();
setUser(data.user);
return data.user;
} catch {
setUser(null);
return null;
} finally {
setIsLoading(false);
}
}, []);
useEffect(() => {
fetchUser().then((user) => {
if (!user && !PUBLIC_PATHS.includes(pathname)) {
router.push("/login");
}
});
}, [fetchUser, pathname, router]);
const logout = async () => {
try {
await fetch("/api/auth/logout", { method: "POST" });
setUser(null);
router.push("/login");
} catch {
// Ignore errors
}
};
const refresh = async () => {
await fetchUser();
};
// Show nothing while checking auth on protected pages
if (isLoading && !PUBLIC_PATHS.includes(pathname)) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="animate-pulse text-muted">Loading...</div>
</div>
);
}
return (
<AuthContext.Provider value={{ user, isLoading, logout, refresh }}>
{children}
</AuthContext.Provider>
);
}