206 lines
5.8 KiB
TypeScript
206 lines
5.8 KiB
TypeScript
"use client"
|
|
|
|
import { createContext, useContext, useState, useCallback, useEffect, type ReactNode } from "react"
|
|
import { useSession, signIn, signOut } from "next-auth/react"
|
|
import type { User, Ticket, TicketStatus, Room } from "./types"
|
|
|
|
const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8080/api"
|
|
|
|
interface AuthContextType {
|
|
user: User | null
|
|
tickets: Ticket[]
|
|
rooms: Room[]
|
|
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>
|
|
updateRooms: (roomIds: number[]) => Promise<void>
|
|
}
|
|
|
|
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)
|
|
|
|
// 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])
|
|
|
|
return (
|
|
<AuthContext.Provider
|
|
value={{
|
|
user,
|
|
tickets,
|
|
rooms,
|
|
login,
|
|
register,
|
|
logout,
|
|
createTicket,
|
|
updateTicketStatus,
|
|
updateRooms,
|
|
}}
|
|
>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
)
|
|
}
|
|
|
|
export function useAuth() {
|
|
const context = useContext(AuthContext)
|
|
if (!context) {
|
|
throw new Error("useAuth must be used within an AuthProvider")
|
|
}
|
|
return context
|
|
}
|