ironie-nextjs/src/app/components/SidebarFooter.tsx
2025-09-20 21:28:10 +02:00

199 lines
8.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { useEffect, useState } from 'react'
import { useSession, signIn, signOut } from 'next-auth/react'
import { signOutWithStatus } from '@/app/lib/signOutWithStatus'
import { useRouter, usePathname } from 'next/navigation'
import { AnimatePresence, motion } from 'framer-motion'
import Image from 'next/image'
import LoadingSpinner from '@/app/components/LoadingSpinner'
import Button from './Button'
import UserAvatarWithStatus from './UserAvatarWithStatus'
import PremierRankBadge from './PremierRankBadge'
export default function SidebarFooter() {
const router = useRouter()
const pathname = usePathname()
const { data: session, status } = useSession()
const [isOpen, setIsOpen] = useState(false)
const [teamName, setTeamName] = useState<string | null>(null)
const [premierRank, setPremierRank] = useState<number>(0)
// ➜ Nach Login: User aus DB laden (inkl. premierRank & Teamname)
useEffect(() => {
if (status !== 'authenticated') {
setTeamName(null)
setPremierRank(0) // ← immer 0, nicht null
return
}
(async () => {
try {
const res = await fetch('/api/user', { cache: 'no-store' })
if (!res.ok) return
const user = await res.json()
const rank = typeof user?.premierRank === 'number' ? user.premierRank : 0
setPremierRank(rank)
setTeamName(user?.team?.name ?? null)
} catch (e) {
console.error('[SidebarFooter] /api/user fehlgeschlagen:', e)
setPremierRank(0)
}
})()
}, [status])
if (status === 'loading') return <LoadingSpinner />
if (status === 'unauthenticated') {
return (
<button
onClick={() => signIn('steam')}
className="flex items-center justify-center gap-2 w-full py-4 px-6 bg-green-800 text-white text-md font-medium hover:bg-green-900 transition"
>
<i className="fab fa-steam" />
<span>Mit Steam anmelden</span>
</button>
)
}
const subline = teamName ?? session?.user?.steamId
const userName = session?.user?.name || 'Profil'
const avatarSrc = (session?.user as any)?.avatar || session?.user?.image || '/default-avatar.png'
const linkClass = (active: boolean) =>
`flex items-center gap-x-3.5 py-2 px-2.5 text-sm rounded-lg transition-colors ${
active
? 'bg-gray-100 dark:bg-neutral-700 text-gray-900 dark:text-white'
: 'text-gray-800 hover:bg-gray-100 dark:text-neutral-300 dark:hover:bg-neutral-700'
}`
return (
<div className="relative w-full min-h-[65px]">
{/* Kopf / Toggle */}
<button
onClick={() => setIsOpen(v => !v)}
className={`w-full min-h-[65px] inline-flex items-center gap-x-2 px-4 py-3 text-sm text-left text-gray-800 transition-all duration-100
${isOpen ? 'bg-gray-100 dark:bg-neutral-700' : 'hover:bg-gray-100 dark:hover:bg-neutral-700'}
`}
>
{/* Linker Block: Avatar + Name/Subline + Rank */}
<div className="flex items-center flex-1 min-w-0">
<UserAvatarWithStatus
src={avatarSrc}
alt={userName}
size={30}
steamId={session?.user?.steamId}
/>
<div className="ms-3 flex-1 min-w-0">
<h3 className="font-semibold text-gray-800 dark:text-white truncate">
{userName}
</h3>
<p className="text-xs font-medium text-gray-400 dark:text-neutral-500 truncate">
{subline}
</p>
</div>
{/* Badge darf nicht schrumpfen */}
<div className="ml-2 flex-shrink-0">
<PremierRankBadge rank={premierRank} />
</div>
</div>
{/* Pfeil ebenfalls nicht schrumpfen */}
<svg
className={`ms-2 size-4 shrink-0 ${isOpen ? 'rotate-180' : ''} text-gray-600 dark:text-neutral-400`}
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path strokeWidth={2} d="m5 15 7-7 7 7" />
</svg>
</button>
{/* Dropdown */}
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.2 }}
className="overflow-hidden w-full bg-white shadow-lg dark:bg-neutral-800 dark:border-neutral-600 z-20"
>
<div className="p-2 flex flex-col gap-1">
<Button
onClick={() => router.push(`/profile/${session?.user?.steamId}`)}
size="sm"
variant="link"
className={linkClass(pathname === `/profile/${session?.user?.steamId}`)}
>
<svg className="size-4" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M15 9h3m-3 3h3m-3 3h3m-6 1c-.306-.613-.933-1-1.618-1H7.618c-.685 0-1.312.387-1.618 1M4 5h16a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1Zm7 5a2 2 0 1 1-4 0 2 2 0 0 1 4 0Z"/>
</svg>
Profil
</Button>
<Button
onClick={() => router.push(`/team`)}
size="sm"
variant="link"
className={linkClass(pathname === '/team')}
>
<svg className="size-4" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M15 9h3m-3 3h3m-3 3h3m-6 1c-.306-.613-.933-1-1.618-1H7.618c-.685 0-1.312.387-1.618 1M4 5h16a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1Zm7 5a2 2 0 1 1-4 0 2 2 0 0 1 4 0Z"/>
</svg>
Team
</Button>
<Button
onClick={() => router.push('/settings')}
size="sm"
variant="link"
className={linkClass(pathname.startsWith('/settings'))}
>
<svg className="size-4" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M10 19H5a1 1 0 0 1-1-1v-1a3 3 0 0 1 3-3h2m10 1a3 3 0 0 1-3 3m3-3a3 3 0 0 0-3-3m3 3h1m-4 3a3 3 0 0 1-3-3m3 3v1m-3-4a3 3 0 0 1 3-3m-3 3h-1m4-3v-1m-2.121 1.879-.707-.707m5.656 5.656-.707-.707m-4.242 0-.707.707m5.656-5.656-.707.707M12 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"/>
</svg>
Einstellungen
</Button>
{session?.user?.isAdmin && (
<Button
onClick={() => router.push('/admin')}
size="sm"
variant="link"
className={linkClass(pathname.startsWith('/admin'))}
>
<svg className="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
fill="currentColor">
<path transform="scale(0.046875)" d="M78.6 5C69.1-2.4 55.6-1.5 47 7L7 47c-8.5 8.5-9.4 22-2.1 31.6l80 104c4.5 5.9 11.6 9.4 19 9.4l54.1 0 109 109c-14.7 29-10 65.4 14.3 89.6l112 112c12.5 12.5 32.8 12.5 45.3 0l64-64c12.5-12.5 12.5-32.8 0-45.3l-112-112c-24.2-24.2-60.6-29-89.6-14.3l-109-109 0-54.1c0-7.5-3.5-14.5-9.4-19L78.6 5zM19.9 396.1C7.2 408.8 0 426.1 0 444.1C0 481.6 30.4 512 67.9 512c18 0 35.3-7.2 48-19.9L233.7 374.3c-7.8-20.9-9-43.6-3.6-65.1l-61.7-61.7L19.9 396.1zM512 144c0-10.5-1.1-20.7-3.2-30.5c-2.4-11.2-16.1-14.1-24.2-6l-63.9 63.9c-3 3-7.1 4.7-11.3 4.7L352 176c-8.8 0-16-7.2-16-16l0-57.4c0-4.2 1.7-8.3 4.7-11.3l63.9-63.9c8.1-8.1 5.2-21.8-6-24.2C388.7 1.1 378.5 0 368 0C288.5 0 224 64.5 224 144l0 .8 85.3 85.3c36-9.1 75.8 .5 104 28.7L429 274.5c49-23 83-72.8 83-130.5zM56 432a24 24 0 1 1 48 0 24 24 0 1 1 -48 0z"/>
</svg>
Administration
</Button>
)}
<Button
onClick={() => signOutWithStatus()}
size="sm"
variant="link"
color="red"
className="flex items-center gap-x-3.5 py-2 px-2.5 text-sm rounded-lg transition-colors text-gray-800 hover:bg-red-100 dark:text-neutral-300 dark:hover:bg-red-700"
>
<svg className="size-4" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M20 12H8m12 0-4 4m4-4-4-4M9 4H7a3 3 0 0 0-3 3v10a3 3 0 0 0 3 3h2"/>
</svg>
Abmelden
</Button>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
)
}