united-tattoo/lib/auth.ts
Nicholai 1378bff909 updated the following components to use the API instead of hardcoded data:
### 1. __artists-grid.tsx__ (Main Artist Browsing)

- Uses  hook from
- Fetches from  endpoint
- Includes loading states, error handling, and filtering
- __Impact:__ Primary artist browsing experience now fully API-driven

### 2. __artist-portfolio.tsx__ (Individual Artist Pages)

- Uses  hook
- Fetches from  endpoint
- Fixed all TypeScript errors (changed image ID from number to string)
- Added loading/error states
- __Impact:__ Artist detail pages now fully API-driven

### 3. __booking-form.tsx__ (Artist Selection Dropdown)

- Uses  hook for artist selection
- Updated to use API data structure ( array, , etc.)
- Added loading state for dropdown
- __Impact:__ Booking flow now uses real artist data

## ⚠️ REMAINING (Decorative/Marketing Components)

Two complex components still use hardcoded :

### 4. __artists-section.tsx__ (Homepage Hero - 348 lines)

- Homepage marketing section with complex parallax scrolling
- Uses hardcoded artist data for visual cards
- __Non-blocking:__ This is a decorative homepage element

### 5. __artists-page-section.tsx__ (Artists Page Section - 413 lines)

- Full-page artists showcase with parallax effects
- Uses hardcoded artist data for visual layout
- __Non-blocking:__ Alternative to artists-grid.tsx (which IS using API)

##
2025-10-06 04:44:08 -06:00

266 lines
6.9 KiB
TypeScript

import { NextAuthOptions } from "next-auth"
import GoogleProvider from "next-auth/providers/google"
import GitHubProvider from "next-auth/providers/github"
import CredentialsProvider from "next-auth/providers/credentials"
import { env } from "./env"
import { UserRole } from "@/types/database"
export const authOptions: NextAuthOptions = {
// Note: Database adapter will be configured via Supabase MCP
// For now, using JWT strategy without database adapter
providers: [
// Credentials provider for email/password login
CredentialsProvider({
name: "credentials",
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" }
},
async authorize(credentials) {
console.log("Authorize called with:", credentials)
if (!credentials?.email || !credentials?.password) {
console.log("Missing email or password")
return null
}
console.log("Email received:", credentials.email)
console.log("Password received:", credentials.password ? "***" : "empty")
// Seed admin user for nicholai@biohazardvfx.com
if (credentials.email === "nicholai@biohazardvfx.com") {
console.log("Admin user recognized!")
return {
id: "admin-nicholai",
email: "nicholai@biohazardvfx.com",
name: "Nicholai",
role: UserRole.SUPER_ADMIN,
}
}
// For development: Accept any other email/password combination
console.log("Using fallback user creation")
const user = {
id: "dev-user-" + Date.now(),
email: credentials.email,
name: credentials.email.split("@")[0],
role: UserRole.SUPER_ADMIN, // Give admin access for testing
}
console.log("Created user:", user)
return user
}
}),
// Google OAuth provider (optional)
...(env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET ? [
GoogleProvider({
clientId: env.GOOGLE_CLIENT_ID,
clientSecret: env.GOOGLE_CLIENT_SECRET,
})
] : []),
// GitHub OAuth provider (optional)
...(env.GITHUB_CLIENT_ID && env.GITHUB_CLIENT_SECRET ? [
GitHubProvider({
clientId: env.GITHUB_CLIENT_ID,
clientSecret: env.GITHUB_CLIENT_SECRET,
})
] : []),
],
session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60, // 30 days
},
callbacks: {
async jwt({ token, user, account }) {
// Add user role to JWT token
if (user) {
// Use the role from the user object (set in authorize function)
token.role = (user as any).role || UserRole.CLIENT
token.userId = user.id
}
return token
},
async session({ session, token }) {
// Add user role and ID to session
if (token) {
session.user.id = token.userId as string
session.user.role = token.role as UserRole
}
return session
},
async signIn({ user, account, profile }) {
// Custom sign-in logic
return true
},
async redirect({ url, baseUrl }) {
// Follows NextAuth.js best practices for redirect
if (url.startsWith("/")) return `${baseUrl}${url}`
else if (new URL(url).origin === baseUrl) return url
return `${baseUrl}/admin`
},
},
pages: {
signIn: "/auth/signin",
error: "/auth/error",
},
events: {
async signIn({ user, account, profile, isNewUser }) {
// Log sign-in events
console.log(`User ${user.email} signed in`)
},
async signOut({ session, token }) {
// Log sign-out events
console.log(`User signed out`)
},
},
debug: env.NODE_ENV === "development",
}
/**
* Utility function to get server-side session
*/
export async function getServerSession() {
const { getServerSession: getNextAuthServerSession } = await import("next-auth/next")
return getNextAuthServerSession(authOptions)
}
/**
* Route protection utility
* @param requiredRole - Minimum role required to access the route
*/
export async function requireAuth(requiredRole?: UserRole) {
const session = await getServerSession()
if (!session) {
throw new Error("Authentication required")
}
if (requiredRole && !hasRole(session.user.role, requiredRole)) {
throw new Error("Insufficient permissions")
}
return session
}
/**
* Check if user has required role or higher
*/
export function hasRole(userRole: UserRole, requiredRole: UserRole): boolean {
const roleHierarchy = {
[UserRole.CLIENT]: 0,
[UserRole.ARTIST]: 1,
[UserRole.SHOP_ADMIN]: 2,
[UserRole.SUPER_ADMIN]: 3,
}
return roleHierarchy[userRole] >= roleHierarchy[requiredRole]
}
/**
* Check if user is admin (SHOP_ADMIN or SUPER_ADMIN)
*/
export function isAdmin(role: UserRole): boolean {
return role === UserRole.SHOP_ADMIN || role === UserRole.SUPER_ADMIN
}
/**
* Check if user is super admin
*/
export function isSuperAdmin(role: UserRole): boolean {
return role === UserRole.SUPER_ADMIN
}
/**
* Get current artist session
* Returns the artist record and user data if the logged-in user is an artist
*/
export async function getArtistSession() {
const session = await getServerSession()
if (!session?.user) {
return null
}
// Check if user has ARTIST role
const userRole = session.user.role
if (userRole !== UserRole.ARTIST && !isAdmin(userRole)) {
return null
}
// Import db function dynamically to avoid circular dependencies
const { getArtistByUserId } = await import('@/lib/db')
const artist = await getArtistByUserId(session.user.id)
if (!artist) {
return null
}
return {
artist,
user: session.user
}
}
/**
* Require artist authentication
* Throws error if user is not an artist, otherwise returns artist and user data
*/
export async function requireArtistAuth() {
const artistSession = await getArtistSession()
if (!artistSession) {
throw new Error("Artist authentication required")
}
return artistSession
}
/**
* Check if a user can edit a specific artist profile
* Returns true if the user is the artist themselves, or has admin privileges
*/
export async function canEditArtist(userId: string, artistId: string): Promise<boolean> {
const session = await getServerSession()
if (!session?.user) {
return false
}
// Admins can edit any artist
if (isAdmin(session.user.role)) {
return true
}
// Check if this user owns the artist profile
const { getArtistByUserId } = await import('@/lib/db')
const artist = await getArtistByUserId(userId)
return artist?.id === artistId
}
// Extend NextAuth types
declare module "next-auth" {
interface Session {
user: {
id: string
email: string
name: string
image?: string
role: UserRole
}
}
interface User {
role: UserRole
}
}
declare module "next-auth/jwt" {
interface JWT {
userId: string
role: UserRole
}
}