### 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)
##
266 lines
6.9 KiB
TypeScript
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
|
|
}
|
|
}
|