update
This commit is contained in:
parent
63c6c9f87a
commit
404e577983
@ -8,14 +8,14 @@ import { useSession } from 'next-auth/react'
|
|||||||
|
|
||||||
import LoadingSpinner from '@/app/components/LoadingSpinner'
|
import LoadingSpinner from '@/app/components/LoadingSpinner'
|
||||||
import TeamMemberView from '@/app/components/TeamMemberView'
|
import TeamMemberView from '@/app/components/TeamMemberView'
|
||||||
import { useTeamManager } from '@/app/hooks/useTeamManager'
|
//import { useTeamManager } from '@/app/hooks/useTeamManager'
|
||||||
|
|
||||||
export default function TeamAdminClient({ teamId }: { teamId: string }) {
|
export default function TeamAdminClient({ teamId }: { teamId: string }) {
|
||||||
const [refetchKey, setRefetchKey] = useState<string>()
|
const [refetchKey, setRefetchKey] = useState<string>()
|
||||||
const { data: session } = useSession()
|
const { data: session } = useSession()
|
||||||
|
|
||||||
// jetzt wird die ID korrekt übergeben ➜ /api/team/[id]
|
// jetzt wird die ID korrekt übergeben ➜ /api/team/[id]
|
||||||
const teamManager = useTeamManager({ teamId, refetchKey }, null)
|
//const teamManager = useTeamManager({ teamId, refetchKey }, null)
|
||||||
|
|
||||||
if (teamManager.isLoading) return <LoadingSpinner />
|
if (teamManager.isLoading) return <LoadingSpinner />
|
||||||
|
|
||||||
|
|||||||
@ -141,6 +141,32 @@ export async function POST(
|
|||||||
data: { read: true, actionType: null, actionData: null },
|
data: { read: true, actionType: null, actionData: null },
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// 1. Teamdaten laden (inkl. Leader)
|
||||||
|
const team = await prisma.team.findUnique({
|
||||||
|
where: { id: teamId },
|
||||||
|
select: { leader: true },
|
||||||
|
})
|
||||||
|
|
||||||
|
// 2. Admins holen
|
||||||
|
const admins = await prisma.user.findMany({
|
||||||
|
where: { isAdmin: true },
|
||||||
|
select: { steamId: true },
|
||||||
|
})
|
||||||
|
|
||||||
|
// 3. Zielnutzer: Leader + Admins
|
||||||
|
const targetUserIds = [
|
||||||
|
team?.leader,
|
||||||
|
...admins.map(admin => admin.steamId),
|
||||||
|
].filter(Boolean) // entfernt null/undefined
|
||||||
|
|
||||||
|
// 4. SSE senden
|
||||||
|
await sendServerSSEMessage({
|
||||||
|
type: 'team-updated',
|
||||||
|
teamId,
|
||||||
|
targetUserIds,
|
||||||
|
})
|
||||||
|
|
||||||
return NextResponse.json({ message: 'Einladung gelöscht' })
|
return NextResponse.json({ message: 'Einladung gelöscht' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,7 @@ export default function NotificationCenter() {
|
|||||||
const [notifications, setNotifications] = useState<Notification[]>([])
|
const [notifications, setNotifications] = useState<Notification[]>([])
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const { source, connect } = useSSE()
|
const { source, connect } = useSSE()
|
||||||
const { markAllAsRead, markOneAsRead, handleInviteAction } = useTeamManager({}, null)
|
//const { markAllAsRead, markOneAsRead, handleInviteAction } = useTeamManager({}, null)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [previewText, setPreviewText] = useState<string | null>(null)
|
const [previewText, setPreviewText] = useState<string | null>(null)
|
||||||
const [showPreview, setShowPreview] = useState(false)
|
const [showPreview, setShowPreview] = useState(false)
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { useSession } from 'next-auth/react'
|
|||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useSSE } from '@/app/lib/useSSEStore'
|
import { useSSE } from '@/app/lib/useSSEStore'
|
||||||
|
|
||||||
export default function SSEManager() {
|
export default function SSEListener() {
|
||||||
const { data: session } = useSession()
|
const { data: session } = useSession()
|
||||||
const connect = useSSE((s) => s.connect)
|
const connect = useSSE((s) => s.connect)
|
||||||
const disconnect = useSSE((s) => s.disconnect)
|
const disconnect = useSSE((s) => s.disconnect)
|
||||||
@ -18,6 +18,7 @@ export default function SSEManager() {
|
|||||||
|
|
||||||
eventSource.onmessage = (event) => {
|
eventSource.onmessage = (event) => {
|
||||||
try {
|
try {
|
||||||
|
console.error('[SSE] Nachricht empfangen:', event.data)
|
||||||
const data = JSON.parse(event.data)
|
const data = JSON.parse(event.data)
|
||||||
|
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
@ -25,6 +25,7 @@ import Button from './Button'
|
|||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import TeamPremierRankBadge from './TeamPremierRankBadge'
|
import TeamPremierRankBadge from './TeamPremierRankBadge'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
|
import { useWebSocketListener } from '../hooks/useWebSocketListener'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
team: Team | null
|
team: Team | null
|
||||||
@ -63,7 +64,7 @@ export default function TeamMemberView({
|
|||||||
adminMode = false,
|
adminMode = false,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const { data: session } = useSession()
|
const { data: session } = useSession()
|
||||||
const { source, connect } = useSSE()
|
const { source } = useSSE()
|
||||||
const [kickCandidate, setKickCandidate] = useState<Player | null>(null)
|
const [kickCandidate, setKickCandidate] = useState<Player | null>(null)
|
||||||
const [promoteCandidate, setPromoteCandidate] = useState<Player | null>(null)
|
const [promoteCandidate, setPromoteCandidate] = useState<Player | null>(null)
|
||||||
|
|
||||||
@ -72,7 +73,6 @@ export default function TeamMemberView({
|
|||||||
const canManage = adminMode || isLeader
|
const canManage = adminMode || isLeader
|
||||||
const canInvite = isLeader && !adminMode
|
const canInvite = isLeader && !adminMode
|
||||||
const canAddDirect = adminMode
|
const canAddDirect = adminMode
|
||||||
//const { leaveTeam, reloadTeam, renameTeam, revokeInvitation } = useTeamManager({}, null)
|
|
||||||
const [showRenameModal, setShowRenameModal] = useState(false)
|
const [showRenameModal, setShowRenameModal] = useState(false)
|
||||||
const [showDeleteModal, setShowDeleteModal] = useState(false)
|
const [showDeleteModal, setShowDeleteModal] = useState(false)
|
||||||
const [isEditingName, setIsEditingName] = useState(false)
|
const [isEditingName, setIsEditingName] = useState(false)
|
||||||
@ -84,11 +84,7 @@ export default function TeamMemberView({
|
|||||||
const [saveSuccess, setSaveSuccess] = useState(false)
|
const [saveSuccess, setSaveSuccess] = useState(false)
|
||||||
const [invitedPlayers, setInvitedPlayers] = useState<InvitedPlayer[]>([])
|
const [invitedPlayers, setInvitedPlayers] = useState<InvitedPlayer[]>([])
|
||||||
|
|
||||||
useEffect(() => {
|
useWebSocketListener('ws-team-update', () => console.log("yeah?"))
|
||||||
if (session?.user?.steamId) {
|
|
||||||
connect(session.user.steamId)
|
|
||||||
}
|
|
||||||
}, [session?.user?.steamId])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTeamState(team)
|
setTeamState(team)
|
||||||
@ -105,61 +101,6 @@ export default function TeamMemberView({
|
|||||||
}
|
}
|
||||||
}, [team])
|
}, [team])
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!source || !teamState?.id) return
|
|
||||||
|
|
||||||
const handleMessage = (e: MessageEvent) => {
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(e.data)
|
|
||||||
const relevant = [
|
|
||||||
'team-updated',
|
|
||||||
'team-leader-changed',
|
|
||||||
'team-leader-self',
|
|
||||||
'team-member-joined',
|
|
||||||
'team-member-left',
|
|
||||||
'team-kick',
|
|
||||||
'team-kick-other',
|
|
||||||
'team-renamed',
|
|
||||||
'team-logo-updated',
|
|
||||||
]
|
|
||||||
if (data.teamId !== teamState.id || !relevant.includes(data.type)) return
|
|
||||||
|
|
||||||
/* EIN Aufruf genügt – holt Team + Spieler + setzt States */
|
|
||||||
fetch(`/api/team/${encodeURIComponent(data.teamId)}`)
|
|
||||||
.then(r => r.json())
|
|
||||||
.then(({ team }) => {
|
|
||||||
if (!team) return
|
|
||||||
setTeamState(team)
|
|
||||||
setactivePlayers(team.activePlayers.sort((a: Player, b: Player) => a.name.localeCompare(b.name)))
|
|
||||||
setInactivePlayers(team.inactivePlayers.sort((a: Player, b: Player) => a.name.localeCompare(b.name)))
|
|
||||||
setInvitedPlayers(team.invitedPlayers.sort((a: Player, b: Player) => a.name.localeCompare(b.name)))
|
|
||||||
})
|
|
||||||
} catch (err) {
|
|
||||||
console.error('SSE parse error:', err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const eventNames = [
|
|
||||||
'team-updated',
|
|
||||||
'team-leader-changed',
|
|
||||||
'team-leader-self',
|
|
||||||
'team-member-joined',
|
|
||||||
'team-member-left',
|
|
||||||
'team-kick',
|
|
||||||
'team-kick-other',
|
|
||||||
'team-renamed',
|
|
||||||
'team-logo-updated',
|
|
||||||
]
|
|
||||||
eventNames.forEach(evt => source.addEventListener(evt, handleMessage))
|
|
||||||
source.onmessage = handleMessage
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
eventNames.forEach(evt => source.removeEventListener(evt, handleMessage))
|
|
||||||
source.onmessage = null
|
|
||||||
}
|
|
||||||
}, [source, teamState?.id, reloadTeam])
|
|
||||||
|
|
||||||
const handleDragStart = (event: any) => {
|
const handleDragStart = (event: any) => {
|
||||||
const id = event.active.id
|
const id = event.active.id
|
||||||
const item = activePlayers.find(p => p.steamId === id) || inactivePlayers.find(p => p.steamId === id)
|
const item = activePlayers.find(p => p.steamId === id) || inactivePlayers.find(p => p.steamId === id)
|
||||||
@ -558,7 +499,7 @@ export default function TeamMemberView({
|
|||||||
<div className="w-full rounded-lg p-4 transition-colors min-h-[200px] border border-gray-300 dark:border-neutral-700">
|
<div className="w-full rounded-lg p-4 transition-colors min-h-[200px] border border-gray-300 dark:border-neutral-700">
|
||||||
<div className="grid gap-4 justify-start grid-cols-[repeat(auto-fill,minmax(160px,1fr))]">
|
<div className="grid gap-4 justify-start grid-cols-[repeat(auto-fill,minmax(160px,1fr))]">
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{invitedPlayers.map((player: Player) => (
|
{invitedPlayers.map((player: InvitedPlayer) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={player.steamId}
|
key={player.steamId}
|
||||||
initial={{ opacity: 0, y: 10 }}
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
// useTeamManager.tsx
|
||||||
|
|
||||||
import { useEffect, useState, useImperativeHandle } from 'react'
|
import { useEffect, useState, useImperativeHandle } from 'react'
|
||||||
import { Player, Team } from '../types/team'
|
import { Player, Team } from '../types/team'
|
||||||
import { useSession } from 'next-auth/react'
|
import { useSession } from 'next-auth/react'
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import ThemeProvider from "@/theme/theme-provider";
|
|||||||
import Script from "next/script";
|
import Script from "next/script";
|
||||||
import NotificationCenter from './components/NotificationCenter'
|
import NotificationCenter from './components/NotificationCenter'
|
||||||
import Navbar from "./components/Navbar";
|
import Navbar from "./components/Navbar";
|
||||||
import SSEManager from "./components/SSEManager";
|
import SSEListener from "./components/SSEListener";
|
||||||
|
|
||||||
const geistSans = Geist({
|
const geistSans = Geist({
|
||||||
variable: "--font-geist-sans",
|
variable: "--font-geist-sans",
|
||||||
@ -40,7 +40,7 @@ export default function RootLayout({
|
|||||||
disableTransitionOnChange
|
disableTransitionOnChange
|
||||||
>
|
>
|
||||||
<Providers>
|
<Providers>
|
||||||
<SSEManager />
|
<SSEListener />
|
||||||
{/* Sidebar und Content direkt nebeneinander */}
|
{/* Sidebar und Content direkt nebeneinander */}
|
||||||
<Sidebar>
|
<Sidebar>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
|
// sse-server-client.ts
|
||||||
|
|
||||||
const host = 'localhost'
|
const host = 'localhost'
|
||||||
|
|
||||||
export async function sendServerSSEMessage(message: any) {
|
export async function sendServerSSEMessage(message: any) {
|
||||||
try {
|
try {
|
||||||
console.log('[SSE Client] Nachricht senden:', message)
|
|
||||||
await fetch(`http://${host}:3001/send`, {
|
await fetch(`http://${host}:3001/send`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
|||||||
@ -17,11 +17,22 @@ export const useSSE = create<SSEState>((set, get) => {
|
|||||||
const source = new EventSource(`http://localhost:3001/events?steamId=${steamId}`)
|
const source = new EventSource(`http://localhost:3001/events?steamId=${steamId}`)
|
||||||
|
|
||||||
source.onopen = () => {
|
source.onopen = () => {
|
||||||
|
console.log('[SSE] Verbunden!')
|
||||||
set({ source, isConnected: true })
|
set({ source, isConnected: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
source.onmessage = (event) => {
|
source.onmessage = (event) => {
|
||||||
console.log('[SSE] Nachricht:', event.data)
|
console.log('[SSE] Nachricht:', event.data)
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(event.data)
|
||||||
|
|
||||||
|
// Zentrale Weiterleitung aller Events, die ein "type" haben
|
||||||
|
if (data?.type) {
|
||||||
|
window.dispatchEvent(new CustomEvent(`sse-${data.type}`, { detail: data }))
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[SSE] Ungültige Nachricht:', event.data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
source.addEventListener('notification', (event) => {
|
source.addEventListener('notification', (event) => {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user