proj/Frontend/lib/auth-context.tsx
2026-01-22 11:55:58 +01:00

351 lines
9.7 KiB
TypeScript

"use client"
import { createContext, useContext, useState, useCallback, useEffect, type ReactNode } from "react"
import { useSession, signIn, signOut } from "next-auth/react"
import { usePathname } from "next/navigation"
import type { User, Ticket, TicketStatus, Room } from "./types"
const API_URL = process.env.NEXT_PUBLIC_API_URL + "/api"
interface AuthContextType {
user: User | null
tickets: Ticket[]
rooms: Room[]
authHeader: string | null
login: (email: string, password: string) => Promise<boolean>
register: (user: Omit<User, "id" | "name"> & { firstname: string; lastname: string; password: string; roomIds?: number[] }) => Promise<boolean>
logout: () => void
createTicket: (ticket: { roomId: number; title: string; description: string }) => Promise<void>
updateTicketStatus: (ticketId: number, status: TicketStatus) => Promise<void>
deleteTicket: (ticketId: number) => Promise<void>
updateRooms: (roomIds: number[]) => Promise<void>
changePassword: (password: string, newPassword: string) => Promise<boolean>
getAllUsers: () => Promise<User[]>
updateUserRole: (userId: number, role: string) => Promise<boolean>
deleteUser: (userId: number) => Promise<boolean>
adminResetPassword: (userId: number, password: string) => Promise<boolean>
validateSession: () => Promise<boolean>
}
const AuthContext = createContext<AuthContextType | null>(null)
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null)
const [tickets, setTickets] = useState<Ticket[]>([])
const [rooms, setRooms] = useState<Room[]>([])
const [authHeader, setAuthHeader] = useState<string | null>(null)
const pathname = usePathname()
// Fetch rooms on mount
useEffect(() => {
fetch(`${API_URL}/rooms`)
.then(res => {
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`)
return res.json()
})
.then(data => setRooms(data))
.catch(err => console.error("Failed to fetch rooms", err))
}, [])
const fetchTickets = useCallback(async (auth: string) => {
try {
const res = await fetch(`${API_URL}/tickets`, {
headers: { "Authorization": auth }
})
if (res.ok) {
const data = await res.json()
setTickets(data)
}
} catch (err) {
console.error("Failed to fetch tickets", err)
}
}, [])
// Sync with NextAuth session
const { data: session, status } = useSession()
useEffect(() => {
if (session?.user && session?.authHeader) {
// @ts-ignore
setUser(session.user)
// @ts-ignore
setAuthHeader(session.authHeader)
// @ts-ignore
fetchTickets(session.authHeader)
} else if (status === "unauthenticated") {
setUser(null)
setAuthHeader(null)
setTickets([])
}
}, [session, status, fetchTickets])
const login = useCallback(async (email: string, password: string) => {
// We use next-auth signIn, which calls our authorize callback
const result = await signIn("credentials", {
email,
password,
redirect: false
})
if (result?.ok) {
// Session update will trigger useEffect
return true
}
return false
}, [])
const register = useCallback(async (newUser: any) => {
const payload = {
firstname: newUser.firstname,
lastname: newUser.lastname,
email: newUser.email,
password: newUser.password,
role: newUser.role, // LEHRKRAFT or RAUMBETREUER
roomIds: newUser.roomIds
}
try {
const res = await fetch(`${API_URL}/auth/register`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
})
if (res.ok) {
// Auto login after register? Or just return true
// Implementation requirement: "Alle Nutzer ... müssen sich registrieren ... Vor der Nutzung ... anmelden"
// So we redirect to login.
return true
}
} catch (e) {
console.error(e)
}
return false
}, [])
const logout = useCallback(() => {
signOut({ redirect: false })
setUser(null)
setAuthHeader(null)
setTickets([])
}, [])
const createTicket = useCallback(async (ticket: { roomId: number; title: string; description: string }) => {
if (!authHeader) return
try {
const res = await fetch(`${API_URL}/tickets`, {
method: "POST",
headers: {
"Authorization": authHeader,
"Content-Type": "application/json"
},
body: JSON.stringify(ticket)
})
if (res.ok) {
fetchTickets(authHeader) // Refresh
}
} catch (e) {
console.error(e)
}
}, [authHeader, fetchTickets])
const updateRooms = useCallback(async (roomIds: number[]) => {
if (!session?.authHeader) return
try {
const res = await fetch(`${API_URL}/auth/profile/rooms`, {
method: "PUT",
headers: {
"Authorization": session.authHeader as string,
"Content-Type": "application/json"
},
body: JSON.stringify({ roomIds })
})
if (res.ok) {
const updatedUser = await res.json()
setUser(updatedUser)
// Note: session.user won't be updated automatically until next session fetch, but local state is updated.
}
} catch (e) {
console.error(e)
}
}, [session])
const updateTicketStatus = useCallback(async (ticketId: number, status: TicketStatus) => {
if (!authHeader) return
try {
const res = await fetch(`${API_URL}/tickets/${ticketId}/status`, {
method: "PATCH",
headers: {
"Authorization": authHeader,
"Content-Type": "application/json"
},
body: JSON.stringify({ status })
})
if (res.ok) {
fetchTickets(authHeader) // Refresh
}
} catch (e) {
console.error(e)
}
}, [authHeader, fetchTickets])
const deleteTicket = useCallback(async (ticketId: number) => {
if (!authHeader) return
try {
const res = await fetch(`${API_URL}/tickets/${ticketId}`, {
method: "DELETE",
headers: { "Authorization": authHeader }
})
if (res.ok) {
fetchTickets(authHeader)
}
} catch (e) {
console.error(e)
}
}, [authHeader, fetchTickets])
const changePassword = useCallback(async (password: string, newPassword: string) => {
if (!authHeader) return false
try {
const res = await fetch(`${API_URL}/users/password`, {
method: "PUT",
headers: {
"Authorization": authHeader,
"Content-Type": "application/json"
},
body: JSON.stringify({ currentPassword: password, newPassword })
})
if (!res.ok) throw new Error("Failed to change password")
return true
} catch (e) {
console.error(e)
return false
}
}, [authHeader])
const getAllUsers = useCallback(async () => {
if (!authHeader) return []
try {
const res = await fetch(`${API_URL}/users`, {
headers: { "Authorization": authHeader }
})
if (res.ok) {
return await res.json()
}
} catch (e) {
console.error(e)
}
return []
}, [authHeader])
const updateUserRole = useCallback(async (userId: number, role: string) => {
if (!authHeader) return false
try {
const res = await fetch(`${API_URL}/users/${userId}/role`, {
method: "PUT",
headers: {
"Authorization": authHeader,
"Content-Type": "application/json"
},
body: JSON.stringify({ role })
})
return res.ok
} catch (e) {
console.error(e)
return false
}
}, [authHeader])
const deleteUser = useCallback(async (userId: number) => {
if (!authHeader) return false
try {
const res = await fetch(`${API_URL}/users/${userId}`, {
method: "DELETE",
headers: { "Authorization": authHeader }
})
return res.ok
} catch (e) {
console.error(e)
return false
}
}, [authHeader])
const adminResetPassword = useCallback(async (userId: number, password: string) => {
if (!authHeader) return false
try {
const res = await fetch(`${API_URL}/users/${userId}/password`, {
method: "PUT",
headers: {
"Authorization": authHeader,
"Content-Type": "application/json"
},
body: JSON.stringify({ password })
})
return res.ok
} catch (e) {
console.error(e)
return false
}
}, [authHeader])
const validateSession = useCallback(async () => {
if (!authHeader) return false
try {
const res = await fetch(`${API_URL}/auth/me`, {
headers: { "Authorization": authHeader }
})
if (res.ok) {
const freshUser = await res.json()
setUser(freshUser)
return true
} else {
// If the checking fails (401/403), log the user out
logout()
return false
}
} catch (e) {
console.error("Session validation failed", e)
return false
}
}, [authHeader, logout])
// Validate session on route change
useEffect(() => {
if (authHeader) {
validateSession()
}
}, [pathname, authHeader, validateSession])
return (
<AuthContext.Provider
value={{
user,
tickets,
rooms,
authHeader,
login,
register,
logout,
createTicket,
updateTicketStatus,
deleteTicket,
updateRooms,
changePassword,
getAllUsers,
updateUserRole,
deleteUser,
adminResetPassword,
validateSession,
}}
>
{children}
</AuthContext.Provider>
)
}
export function useAuth() {
const context = useContext(AuthContext)
if (!context) {
throw new Error("useAuth must be used within an AuthProvider")
}
return context
}