Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 64 additions & 1 deletion pages/admin/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { getSession } from 'next-auth/react';
import prisma from '../../prisma/prisma';
import dynamic from 'next/dynamic';
import redirectUser from '../../util/redirectUser.js';
import { useEffect, useState } from 'react';

export async function getServerSideProps(ctx) {
const userSession = await getSession(ctx);
Expand Down Expand Up @@ -65,6 +66,31 @@ export default function Home(props) {
selector: row => row.adminActions
}
];

const [search, setSearch] = useState('');
const [filteredUsers, setFilteredUsers] = useState(props.users);

useEffect(() => {
const handler = setTimeout(() => {
if (!search) {
setFilteredUsers(props.users);
return;
}
const fetchUsers = async () => {
const res = await fetch(
`/api/search-users?q=${encodeURIComponent(search)}`
);
if (res.ok) {
const data = await res.json();
setFilteredUsers(data);
}
};
fetchUsers();
}, 300); // 300ms debounce

return () => clearTimeout(handler);
}, [search, props.users]);

return (
<>
<div className={styles.container}>
Expand All @@ -86,7 +112,44 @@ export default function Home(props) {
Admin
</h1>
</div>
<AdminTable columns={columns} data={props.users}></AdminTable>
<div className='search-section mb-6 px-4'>
<div className='max-w-md'>
<div className='relative'>
<div className='absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none'>
<svg
className='h-5 w-5 text-gray-400'
fill='none'
stroke='currentColor'
viewBox='0 0 24 24'
>
<path
strokeLinecap='round'
strokeLinejoin='round'
strokeWidth={2}
d='M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z'
/>
</svg>
</div>
<input
type='text'
placeholder='Search users by name or email...'
className='w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 focus:border-1 focus:outline-none transition-all duration-200 text-gray-900 placeholder-gray-500'
value={search}
onChange={e => setSearch(e.target.value)}
/>
</div>
{search && (
<div className='mt-2 text-sm text-gray-600'>
{filteredUsers.length === 0
? 'No users found'
: `Found ${filteredUsers.length} user${
filteredUsers.length !== 1 ? 's' : ''
}`}
</div>
)}
</div>
</div>
<AdminTable columns={columns} data={filteredUsers}></AdminTable>
</div>
</>
);
Expand Down
52 changes: 52 additions & 0 deletions pages/api/search-users.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { getServerSession } from 'next-auth';
import prisma from '../../prisma/prisma';
import { authOptions } from './auth/[...nextauth]';

export default async function handle(req, res) {
const session = await getServerSession(req, res, authOptions);
if (req.method !== 'GET') {
return res.status(405).end();
}

if (!session) {
return res.status(403).end();
}

let user;

try {
user = await prisma.user.findUniqueOrThrow({
where: { email: session.user.email },
select: { role: true }
});
} catch (error) {
return res.status(403).end();
}

if (user.role !== 'ADMIN') {
return res.status(403).json({ error: 'Forbidden' });
}

const { q } = req.query;

if (!q) {
return res.status(400).json({ error: 'Missing search query' });
}

const users = await prisma.user.findMany({
where: {
OR: [
{ name: { contains: q, mode: 'insensitive' } },
{ email: { contains: q, mode: 'insensitive' } },
{ role: { contains: q, mode: 'insensitive' } }
]
},
select: {
id: true,
name: true,
email: true,
role: true
}
});
res.status(200).json(users);
}
Loading