'use client' import React, { Suspense } from 'react' // CHANGED: add Suspense import Header from '../../../components/nav/Header' import Footer from '../../../components/Footer' import { UsersIcon, PlusIcon, BanknotesIcon, CalendarDaysIcon, MagnifyingGlassIcon, XMarkIcon } from '@heroicons/react/24/outline' import { useRouter, useSearchParams } from 'next/navigation' import useAuthStore from '../../../store/authStore' import PageTransitionEffect from '../../../components/animation/pageTransitionEffect' import { AdminAPI } from '../../../utils/api' type PoolUser = { id: string name: string email: string contributed: number joinedAt: string // NEW: member since } function PoolManagePageInner() { const router = useRouter() const searchParams = useSearchParams() const user = useAuthStore(s => s.user) const token = useAuthStore(s => s.accessToken) const isAdmin = !!user && ( (user as any)?.role === 'admin' || (user as any)?.userType === 'admin' || (user as any)?.isAdmin === true || ((user as any)?.roles?.includes?.('admin')) ) // Auth gate const [authChecked, setAuthChecked] = React.useState(false) React.useEffect(() => { if (user === null) { router.replace('/login') return } if (user && !isAdmin) { router.replace('/') return } setAuthChecked(true) }, [user, isAdmin, router]) // Read pool data from query params with fallbacks (hooks must be before any return) const poolId = searchParams.get('id') ?? 'pool-unknown' const poolName = searchParams.get('pool_name') ?? 'Unnamed Pool' const poolDescription = searchParams.get('description') ?? '' const poolPrice = parseFloat(searchParams.get('price') ?? '0') const poolType = searchParams.get('pool_type') as 'coffee' | 'other' || 'other' const poolIsActive = searchParams.get('is_active') === 'true' const poolCreatedAt = searchParams.get('createdAt') ?? new Date().toISOString() // Members (no dummy data) const [users, setUsers] = React.useState([]) const [membersLoading, setMembersLoading] = React.useState(false) const [membersError, setMembersError] = React.useState('') // Stats (no dummy data) const [totalAmount, setTotalAmount] = React.useState(0) const [amountThisYear, setAmountThisYear] = React.useState(0) const [amountThisMonth, setAmountThisMonth] = React.useState(0) // Search modal state const [searchOpen, setSearchOpen] = React.useState(false) const [query, setQuery] = React.useState('') const [loading, setLoading] = React.useState(false) const [error, setError] = React.useState('') const [candidates, setCandidates] = React.useState>([]) const [hasSearched, setHasSearched] = React.useState(false) const [selectedCandidates, setSelectedCandidates] = React.useState>(new Set()) const [savingMembers, setSavingMembers] = React.useState(false) const [removingMemberId, setRemovingMemberId] = React.useState(null) const [removeError, setRemoveError] = React.useState('') async function fetchMembers() { if (!token || !poolId || poolId === 'pool-unknown') return setMembersError('') setMembersLoading(true) try { const resp = await AdminAPI.getPoolMembers(token, poolId) const rows = Array.isArray(resp?.members) ? resp.members : [] const mapped: PoolUser[] = rows.map((row: any) => { const name = row.company_name ? String(row.company_name) : [row.first_name, row.last_name].filter(Boolean).join(' ').trim() return { id: String(row.id), name: name || String(row.email || '').trim() || 'Unnamed user', email: String(row.email || '').trim(), contributed: 0, joinedAt: row.joined_at || new Date().toISOString() } }) setUsers(mapped) } catch (e: any) { setMembersError(e?.message || 'Failed to load pool members.') } finally { setMembersLoading(false) } } React.useEffect(() => { void fetchMembers() }, [token, poolId]) // Early return AFTER all hooks are declared to keep consistent order if (!authChecked) return null async function doSearch() { setError('') const q = query.trim().toLowerCase() if (q.length < 3) { setHasSearched(false) setCandidates([]) return } if (!token) { setError('Authentication required.') setHasSearched(true) setCandidates([]) return } setHasSearched(true) setLoading(true) try { const resp = await AdminAPI.getUserList(token) const list = Array.isArray(resp?.users) ? resp.users : [] const existingIds = new Set(users.map(u => String(u.id))) const mapped: Array<{ id: string; name: string; email: string }> = list .filter((u: any) => u && u.role !== 'admin' && u.role !== 'super_admin') .map((u: any) => { const name = u.company_name ? String(u.company_name) : [u.first_name, u.last_name].filter(Boolean).join(' ').trim() return { id: String(u.id), name: name || String(u.email || '').trim() || 'Unnamed user', email: String(u.email || '').trim() } }) .filter((u: { id: string; name: string; email: string }) => !existingIds.has(u.id)) .filter((u: { id: string; name: string; email: string }) => { const hay = `${u.name} ${u.email}`.toLowerCase() return hay.includes(q) }) setCandidates(mapped) } catch (e: any) { setError(e?.message || 'Failed to search users.') setCandidates([]) } finally { setLoading(false) } } async function addUserFromModal(u: { id: string; name: string; email: string }) { if (!token || !poolId || poolId === 'pool-unknown') return setSavingMembers(true) setError('') try { await AdminAPI.addPoolMembers(token, poolId, [u.id]) await fetchMembers() setSearchOpen(false) setQuery('') setCandidates([]) setHasSearched(false) setSelectedCandidates(new Set()) } catch (e: any) { setError(e?.message || 'Failed to add user.') } finally { setLoading(false) setSavingMembers(false) } } function toggleCandidate(id: string) { setSelectedCandidates(prev => { const next = new Set(prev) if (next.has(id)) next.delete(id) else next.add(id) return next }) } async function addSelectedUsers() { if (selectedCandidates.size === 0) return const selectedList = candidates.filter(c => selectedCandidates.has(c.id)) if (selectedList.length === 0) return if (!token || !poolId || poolId === 'pool-unknown') return setSavingMembers(true) setError('') try { const userIds = selectedList.map(u => u.id) await AdminAPI.addPoolMembers(token, poolId, userIds) await fetchMembers() setSearchOpen(false) setQuery('') setCandidates([]) setHasSearched(false) setSelectedCandidates(new Set()) } catch (e: any) { setError(e?.message || 'Failed to add users.') } finally { setLoading(false) setSavingMembers(false) } } async function removeMember(userId: string) { if (!token || !poolId || poolId === 'pool-unknown') return const user = users.find(u => u.id === userId) const label = user?.name || user?.email || 'this user' if (!window.confirm(`Remove ${label} from this pool?`)) return setRemoveError('') setRemovingMemberId(userId) try { await AdminAPI.removePoolMembers(token, poolId, [userId]) await fetchMembers() } catch (e: any) { setRemoveError(e?.message || 'Failed to remove user from pool.') } finally { setRemovingMemberId(null) } } return (
{/* main wrapper: avoid high z-index stacking */}
{/* Header (remove sticky/z-10) */}

{poolName}

{poolDescription ? poolDescription : 'Manage users and track pool funds'}

{!poolIsActive ? 'Inactive' : 'Active'} Created {new Date(poolCreatedAt).toLocaleDateString('de-DE', { year: 'numeric', month: 'short', day: '2-digit' })} ID: {poolId}
{/* Back to Pool Management */}
{/* Stats (now zero until backend wired) */}

Total in Pool

€ {totalAmount.toLocaleString()}

This Year

€ {amountThisYear.toLocaleString()}

Current Month

€ {amountThisMonth.toLocaleString()}

{/* Unified Members card: add button + list */}

Members

{removeError && (
{removeError}
)}
{users.map(u => (

{u.name}

{u.email}

€ {u.contributed.toLocaleString()}
Member since:{' '} {new Date(u.joinedAt).toLocaleDateString('de-DE', { year: 'numeric', month: 'short', day: '2-digit' })}
))} {membersLoading && (
Loading members...
)} {membersError && !membersLoading && (
{membersError}
)} {users.length === 0 && !membersLoading && !membersError && (
No users in this pool yet.
)}