import { useState, useEffect, useCallback, useMemo } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { useRouter } from 'next/navigation'
import { toast } from 'sonner'
interface User {
id: string
name: string
email: string
avatar?: string
role: 'admin' | 'user' | 'moderator'
createdAt: Date
lastActive: Date
preferences: UserPreferences
}
interface UserPreferences {
theme: 'light' | 'dark' | 'system'
notifications: boolean
language: string
timezone: string
}
interface ApiResponse<T> {
data: T
success: boolean
message?: string
pagination?: {
page: number
limit: number
total: number
}
}
const UserDashboard = () => {
const router = useRouter()
const [users, setUsers] = useState([])
const [loading, setLoading] = useState(true)
const [searchQuery, setSearchQuery] = useState("")
const [selectedUsers, setSelectedUsers] = useState(new Set())
const [sortBy, setSortBy] = useState("name")
const [sortOrder, setSortOrder] = useState("asc")
const [currentPage, setCurrentPage] = useState(1)
const fetchUsers = useCallback(async (page = 1, search = '') => {
const params = new URLSearchParams({
page: page.toString(),
limit: '10',
search,
sortBy,
sortOrder
})
const response = await fetch(`/api/users?${params}`)
if (!response.ok) {
throw new Error("Failed to fetch users")
}
const result = await response.json()
if (result.success) {
setUsers(result.data)
toast.success("Users loaded successfully")
} else {
throw new Error(result.message || "Unknown error")
}
}, [sortBy, sortOrder])
const filteredUsers = useMemo(() => {
return users.filter(
(user) =>
user.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
user.email.toLowerCase().includes(searchQuery.toLowerCase()),
)
}, [users, searchQuery])
const handleUserSelect = useCallback((userId) => {
setSelectedUsers((prev) => {
const newSet = new Set(prev)
if (newSet.has(userId)) {
newSet.delete(userId)
} else {
newSet.add(userId)
}
return newSet
})
}, [])
const handleBulkAction = useCallback(
async (action) => {
if (selectedUsers.size === 0) {
toast.error("No users selected")
return
}
try {
const response = await fetch("/api/users/bulk", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
action,
userIds: Array.from(selectedUsers),
}),
})
if (response.ok) {
toast.success(`${action} completed successfully`)
setSelectedUsers(new Set())
fetchUsers(currentPage, searchQuery)
} else {
throw new Error("Bulk action failed")
}
} catch (err) {
toast.error("Failed to perform bulk action")
}
},
[selectedUsers, currentPage, searchQuery, fetchUsers],
)
const handleSort = useCallback(
(field) => {
if (sortBy === field) {
setSortOrder((prev) => (prev === "asc" ? "desc" : "asc"))
} else {
setSortBy(field)
setSortOrder("asc")
}
},
[sortBy],
)
useEffect(() => {
fetchUsers(currentPage, searchQuery)
}, [fetchUsers, currentPage, searchQuery])
useEffect(() => {
const handleKeyDown = (e) => {
if (e.key === "Escape") {
setIsModalOpen(false)
setSelectedUsers(new Set())
}
}
document.addEventListener("keydown", handleKeyDown)
return () => document.removeEventListener("keydown", handleKeyDown)
}, [])
const renderUserCard = (user) => (
<div key={user.id} className="bg-white rounded-lg shadow-md p-4">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<img
src={user.avatar || "/default-avatar.png"}
alt={user.name}
className="w-10 h-10 rounded-full object-cover"
/>
<div>
<h3 className="font-semibold text-gray-900">{user.name}</h3>
<p className="text-sm text-gray-600">{user.email}</p>
</div>
</div>
<div className="flex items-center space-x-2">
<span className="px-2 py-1 text-xs rounded-full bg-green-100">
{user.role}
</span>
<input
type="checkbox"
checked={selectedUsers.has(user.id)}
onChange={() => handleUserSelect(user.id)}
className="rounded border-gray-300"
/>
</div>
</div>
</div>
)
if (loading && users.length === 0) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-32 w-32 border-b-2"></div>
</div>
)
}
return (
<div className="container mx-auto px-4 py-8">
<div className="mb-6">
<h1 className="text-3xl font-bold mb-2">User Dashboard</h1>
<p className="text-gray-600">Manage users and permissions</p>
</div>
<div className="mb-6 flex flex-col sm:flex-row gap-4">
<input
type="text"
placeholder="Search users..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="flex-1 px-4 py-2 border rounded-lg"
/>
{selectedUsers.size > 0 && (
<div className="flex gap-2">
<button
onClick={() => handleBulkAction('activate')}
className="px-4 py-2 bg-green-600 text-white rounded-lg"
>
Activate ({selectedUsers.size})
</button>
<button
onClick={() => handleBulkAction('deactivate')}
className="px-4 py-2 bg-red-600 text-white rounded-lg"
>
Deactivate ({selectedUsers.size})
</button>
</div>
)}
</div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{filteredUsers.map(renderUserCard)}
</div>
{filteredUsers.length === 0 && !loading && (
<div className="text-center py-12">
<p className="text-gray-500 text-lg">No users found</p>
</div>
)}
</div>
)
}
export default UserDashboard