167 lines
5.4 KiB
TypeScript
167 lines
5.4 KiB
TypeScript
// /src/app/components/TeamCard.tsx
|
||
'use client'
|
||
|
||
import { useState, useMemo } from 'react'
|
||
import { useRouter } from 'next/navigation'
|
||
import Button from './Button'
|
||
import TeamPremierRankBadge from './TeamPremierRankBadge'
|
||
import type { Team } from '../types/team'
|
||
|
||
type Props = {
|
||
team: Team
|
||
currentUserSteamId: string
|
||
invitationId?: string
|
||
onUpdateInvitation: (teamId: string, newValue: string | null | 'pending') => void
|
||
adminMode?: boolean
|
||
/** Vom Page-Container gesetzt: ob der Nutzer grundsätzlich Beitritte anfragen darf
|
||
* (false, wenn /api/user ein team liefert). Default: true (abwärtskompatibel). */
|
||
canRequestJoin?: boolean
|
||
}
|
||
|
||
export default function TeamCard({
|
||
team,
|
||
currentUserSteamId,
|
||
invitationId,
|
||
onUpdateInvitation,
|
||
adminMode = false,
|
||
canRequestJoin = true,
|
||
}: Props) {
|
||
const router = useRouter()
|
||
const [joining, setJoining] = useState(false)
|
||
|
||
const isRequested = Boolean(invitationId)
|
||
|
||
// Bin ich bereits in DIESEM Team (Leader, aktiv oder inaktiv)?
|
||
const isMemberOfThisTeam = useMemo(() => {
|
||
const inActive = (team.activePlayers ?? []).some(p => String(p.steamId) === String(currentUserSteamId))
|
||
const inInactive = (team.inactivePlayers ?? []).some(p => String(p.steamId) === String(currentUserSteamId))
|
||
const isLeader = team.leader?.steamId && String(team.leader.steamId) === String(currentUserSteamId)
|
||
return Boolean(inActive || inInactive || isLeader)
|
||
}, [team, currentUserSteamId])
|
||
|
||
// Button sperren, wenn:
|
||
// - gerade Request läuft
|
||
// - bereits Mitglied dieses Teams
|
||
// - global keine Join-Anfragen erlaubt (User hat bereits ein Team)
|
||
const isDisabled = joining || isMemberOfThisTeam || !canRequestJoin
|
||
|
||
const handleClick = async () => {
|
||
if (joining || isDisabled) return
|
||
setJoining(true)
|
||
try {
|
||
if (isRequested) {
|
||
await fetch('/api/user/invitations/reject', {
|
||
method : 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body : JSON.stringify({ invitationId }),
|
||
})
|
||
onUpdateInvitation(team.id, null)
|
||
} else {
|
||
await fetch('/api/team/request-join', {
|
||
method : 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body : JSON.stringify({ teamId: team.id }),
|
||
})
|
||
onUpdateInvitation(team.id, 'pending')
|
||
}
|
||
} catch (err) {
|
||
console.error('[TeamCard] Join/Reject-Fehler:', err)
|
||
} finally {
|
||
setJoining(false)
|
||
}
|
||
}
|
||
|
||
const targetHref = adminMode ? `/admin/teams/${team.id}` : `/team/${team.id}`
|
||
|
||
// Label & Farbe abhängig vom Status
|
||
const buttonLabel = joining
|
||
? (
|
||
<>
|
||
<span
|
||
className="animate-spin inline-block size-4 border-[3px] border-current border-t-transparent rounded-full mr-1"
|
||
role="status"
|
||
aria-label="loading"
|
||
/>
|
||
Lädt
|
||
</>
|
||
)
|
||
: (!canRequestJoin || isMemberOfThisTeam)
|
||
? 'Beitritt nicht möglich'
|
||
: isRequested
|
||
? 'Angefragt (zurückziehen)'
|
||
: 'Beitritt anfragen'
|
||
|
||
const buttonColor =
|
||
isDisabled ? 'gray' : (isRequested ? 'gray' : 'blue')
|
||
|
||
return (
|
||
<div
|
||
role="button"
|
||
tabIndex={0}
|
||
onClick={() => router.push(targetHref)}
|
||
onKeyDown={e => (e.key === 'Enter') && router.push(targetHref)}
|
||
className="p-4 border rounded-lg bg-white dark:bg-neutral-800
|
||
dark:border-neutral-700 shadow-sm hover:shadow-md
|
||
transition cursor-pointer focus:outline-none
|
||
hover:scale-105 hover:bg-neutral-200 hover:dark:bg-neutral-700"
|
||
>
|
||
<div className="flex items-center justify-between gap-3 mb-3">
|
||
<div className="flex items-center gap-3">
|
||
<img
|
||
src={team.logo ? `/assets/img/logos/${team.logo}` : `/assets/img/logos/cs2.webp`}
|
||
alt={team.name ?? 'Teamlogo'}
|
||
className="w-12 h-12 rounded-full object-cover border
|
||
border-gray-200 dark:border-neutral-600"
|
||
/>
|
||
<div className="flex items-center gap-2">
|
||
<span className="font-medium truncate text-gray-800 dark:text-neutral-200">
|
||
{team.name ?? 'Team'}
|
||
</span>
|
||
<TeamPremierRankBadge players={team.activePlayers} />
|
||
</div>
|
||
</div>
|
||
|
||
{adminMode ? (
|
||
<Button
|
||
title="Verwalten"
|
||
size="md"
|
||
color="blue"
|
||
variant="solid"
|
||
onClick={e => {
|
||
e.stopPropagation()
|
||
router.push(`/admin/teams/${team.id}`)
|
||
}}
|
||
>
|
||
Verwalten
|
||
</Button>
|
||
) : (
|
||
// 👉 Button immer zeigen – falls nicht möglich: disabled + anderes Label
|
||
<Button
|
||
title={typeof buttonLabel === 'string' ? buttonLabel : undefined}
|
||
size="sm"
|
||
color={buttonColor as any}
|
||
disabled={isDisabled}
|
||
onClick={e => { e.stopPropagation(); handleClick() }}
|
||
aria-disabled={isDisabled ? 'true' : undefined}
|
||
>
|
||
{buttonLabel}
|
||
</Button>
|
||
)}
|
||
</div>
|
||
|
||
<div className="flex -space-x-3">
|
||
{[...team.activePlayers, ...team.inactivePlayers].map(p => (
|
||
<img
|
||
key={p.steamId}
|
||
src={p.avatar}
|
||
alt={p.name}
|
||
title={p.name}
|
||
className="w-8 h-8 rounded-full border-2 border-white
|
||
dark:border-neutral-800 object-cover"
|
||
/>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|