300 lines
14 KiB
TypeScript
300 lines
14 KiB
TypeScript
'use client';
|
||
import React, { Fragment, useEffect, useState } from 'react';
|
||
import { Dialog, Transition } from '@headlessui/react';
|
||
import IconX from '@/components/icon/icon-x';
|
||
import IconUserPlus from '@/components/icon/icon-user-plus';
|
||
import IconSearch from '@/components/icon/icon-search';
|
||
import Swal from 'sweetalert2';
|
||
import axios from 'axios';
|
||
|
||
const UserModule = () => {
|
||
const [users, setUsers] = useState<any[]>([]);
|
||
const [filteredUsers, setFilteredUsers] = useState<any[]>([]);
|
||
const [search, setSearch] = useState('');
|
||
const [addUserModal, setAddUserModal] = useState(false);
|
||
|
||
// ✅ NEW: delete modal state (UI only)
|
||
const [deleteUserModal, setDeleteUserModal] = useState(false);
|
||
const [userToDelete, setUserToDelete] = useState<any>(null);
|
||
|
||
const isAdminUser = true;
|
||
|
||
const defaultParams = {
|
||
userid: null,
|
||
name: '',
|
||
email: '',
|
||
mobileNumber: '',
|
||
password: '',
|
||
role: 'customer',
|
||
};
|
||
|
||
const [params, setParams] = useState<any>({ ...defaultParams });
|
||
|
||
|
||
|
||
/* ================= LOGIC ================= */
|
||
|
||
const fetchUsers = async () => {
|
||
try {
|
||
const res = await axios.get('/api/users');
|
||
setUsers(res.data || []);
|
||
setFilteredUsers(res.data || []);
|
||
} catch {
|
||
showMessage('Failed to load users', 'error');
|
||
}
|
||
};
|
||
|
||
useEffect(() => {
|
||
fetchUsers();
|
||
},);
|
||
|
||
useEffect(() => {
|
||
setFilteredUsers(
|
||
search
|
||
? users.filter((u) =>
|
||
(u.name || '').toLowerCase().includes(search.toLowerCase())
|
||
)
|
||
: users
|
||
);
|
||
}, [search, users]);
|
||
|
||
const showMessage = (msg = '', type = 'success') => {
|
||
Swal.mixin({
|
||
toast: true,
|
||
position: 'top',
|
||
showConfirmButton: false,
|
||
timer: 2500,
|
||
}).fire({ icon: type as any, title: msg });
|
||
};
|
||
|
||
const changeValue = (e: any) => {
|
||
const { id, value } = e.target;
|
||
setParams({ ...params, [id]: value });
|
||
};
|
||
|
||
const saveUser = async () => {
|
||
if (!params.name || !params.email || !params.mobileNumber) {
|
||
showMessage('Name, email and mobile number are required', 'error');
|
||
return;
|
||
}
|
||
|
||
const payload = {
|
||
name: params.name,
|
||
email: params.email,
|
||
mobileNumber: params.mobileNumber,
|
||
password: params.password,
|
||
role: params.role,
|
||
};
|
||
|
||
try {
|
||
if (params.userid) {
|
||
await axios.put(`/api/users/${params.userid}`, payload);
|
||
showMessage('User updated successfully');
|
||
} else {
|
||
await axios.post('/api/users', payload);
|
||
showMessage('User created successfully');
|
||
}
|
||
|
||
setAddUserModal(false);
|
||
setParams(defaultParams);
|
||
fetchUsers();
|
||
} catch {
|
||
showMessage('Failed to save user', 'error');
|
||
}
|
||
};
|
||
|
||
const editUser = (user: any) => {
|
||
setParams({
|
||
userid: user.userid,
|
||
name: user.name,
|
||
email: user.email,
|
||
mobileNumber: user.mobileNumber || '',
|
||
role: user.role,
|
||
});
|
||
setAddUserModal(true);
|
||
};
|
||
|
||
return (
|
||
<div className="relative min-h-screen w-full bg-[#111111] p-6">
|
||
|
||
|
||
{/* BACKGROUND GLOWS */}
|
||
<div className="pointer-events-none absolute inset-0">
|
||
<div className="absolute top-[180px] left-52 w-[100px] h-[100px] bg-[#1d8be0] rounded-full blur-2xl opacity-[1.5]" />
|
||
<div className="absolute top-10 left-0 w-[60px] h-[60px] bg-[#6cb655] rounded-full blur-2xl opacity-[1.5]" />
|
||
<div className="absolute -left-[80px] bottom-[140px] w-[100px] h-[200px] bg-[#db21d9] blur-3xl opacity-1" />
|
||
<div className="absolute top-[100px] right-[260px] w-[100px] h-[100px] bg-[#f28f50] rounded-full blur-2xl opacity-80" />
|
||
</div>
|
||
|
||
{/* CONTENT */}
|
||
<div className="relative z-10 text-white w-full min-h-screen">
|
||
|
||
{/* HEADER */}
|
||
<div className="flex justify-between items-center mb-6">
|
||
<h2 className="text-xl font-semibold">Users</h2>
|
||
|
||
<div className="flex gap-3">
|
||
<button
|
||
className="btn btn-primary text-white bg-gradient-to-r from-blue-600 to-pink-500"
|
||
disabled={!isAdminUser}
|
||
onClick={() => {
|
||
setParams(defaultParams);
|
||
setAddUserModal(true);
|
||
}}
|
||
>
|
||
<IconUserPlus className="mr-2" />
|
||
Add User
|
||
</button>
|
||
|
||
<div className="relative">
|
||
<input
|
||
type="text"
|
||
placeholder="Search user"
|
||
value={search}
|
||
onChange={(e) => setSearch(e.target.value)}
|
||
className="px-3 py-2 rounded-lg bg-[rgba(7,13,30,0.7)] border border-white/15 text-white"
|
||
/>
|
||
<span className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400">
|
||
<IconSearch />
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* TABLE */}
|
||
<div className="rounded-2xl overflow-hidden border border-white/10 backdrop-blur-xl">
|
||
<table className="w-full text-sm text-white">
|
||
<thead className="bg-gray text-white">
|
||
<tr className='bg-gray'>
|
||
<th className="px-5 py-3 text-left bg-[#242424]">Name</th>
|
||
<th className='bg-[#242424]'>Email</th>
|
||
<th className='bg-[#242424]'>Mobile Number</th>
|
||
<th className='bg-[#242424]'>Role</th>
|
||
<th className="text-center bg-[#242424]">Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{filteredUsers.map((user) => (
|
||
<tr key={user.userid} className="border-t border-white/10 hover:bg-white/5">
|
||
<td className="px-5 py-4">{user.name}</td>
|
||
<td>{user.email}</td>
|
||
<td>{user.mobileNumber}</td>
|
||
<td>
|
||
<span className="px-3 py-1 rounded-full text-xs bg-blue-500/20 text-blue-400 capitalize">
|
||
{user.role}
|
||
</span>
|
||
</td>
|
||
<td className="text-center">
|
||
<div className="flex justify-center gap-2">
|
||
<button
|
||
onClick={() => editUser(user)}
|
||
className="btn btn-sm btn-outline-primary"
|
||
>
|
||
Edit
|
||
</button>
|
||
<button
|
||
onClick={() => {
|
||
setUserToDelete(user);
|
||
setDeleteUserModal(true);
|
||
}}
|
||
className="btn btn-sm btn-outline-danger"
|
||
>
|
||
Delete
|
||
</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
{/* ADD / EDIT MODAL */}
|
||
<Transition appear show={addUserModal} as={Fragment}>
|
||
<Dialog as="div" open={addUserModal} onClose={() => setAddUserModal(false)} className="relative z-50">
|
||
<div className="fixed inset-0 bg-black/70" />
|
||
<div className="fixed inset-0 flex items-center justify-center px-4">
|
||
<Dialog.Panel className="w-full max-w-lg rounded-xl bg-[#0b0d1c] p-6 border border-white/20 text-white">
|
||
<button onClick={() => setAddUserModal(false)} className="absolute top-4 right-4 text-gray-400">
|
||
<IconX />
|
||
</button>
|
||
|
||
<h3 className="mb-4 text-lg font-semibold">
|
||
{params.userid ? 'Edit User' : 'Add User'}
|
||
</h3>
|
||
|
||
<div className="space-y-3">
|
||
<input id="name" value={params.name} onChange={changeValue} placeholder="Name"
|
||
className="w-full px-3 py-2 rounded-lg bg-black/40 border border-white/15 text-white" />
|
||
<input id="email" value={params.email} onChange={changeValue} placeholder="Email"
|
||
className="w-full px-3 py-2 rounded-lg bg-black/40 border border-white/15 text-white" />
|
||
<input id="mobileNumber" value={params.mobileNumber} onChange={changeValue} placeholder="Mobile"
|
||
className="w-full px-3 py-2 rounded-lg bg-black/40 border border-white/15 text-white" />
|
||
{!params.userid && (
|
||
<input id="password" type="password" value={params.password} onChange={changeValue}
|
||
placeholder="Password"
|
||
className="w-full px-3 py-2 rounded-lg bg-black/40 border border-white/15 text-white" />
|
||
)}
|
||
<select id="role" value={params.role} onChange={changeValue}
|
||
className="w-full px-3 py-2 rounded-lg bg-black/40 border border-white/15 text-white">
|
||
<option value="admin">Admin</option>
|
||
<option value="partner">Partner</option>
|
||
<option value="customer">Customer</option>
|
||
</select>
|
||
|
||
<div className="flex justify-end gap-3 pt-4">
|
||
<button className="btn btn-outline-danger" onClick={() => setAddUserModal(false)}>
|
||
Cancel
|
||
</button>
|
||
<button className="btn btn-primary bg-gradient-to-r from-blue-600 to-pink-500" onClick={saveUser}>
|
||
{params.userid ? 'Update' : 'Add'}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</Dialog.Panel>
|
||
</div>
|
||
</Dialog>
|
||
</Transition>
|
||
|
||
{/* DELETE MODAL – SAME CARD UI */}
|
||
<Transition appear show={deleteUserModal} as={Fragment}>
|
||
<Dialog as="div" open={deleteUserModal} onClose={() => setDeleteUserModal(false)} className="relative z-50">
|
||
<div className="fixed inset-0 bg-black/70" />
|
||
<div className="fixed inset-0 flex items-center justify-center px-4">
|
||
<Dialog.Panel className="w-full max-w-lg rounded-xl bg-[#0b0d1c] p-6 border border-white/20 text-white">
|
||
<button onClick={() => setDeleteUserModal(false)} className="absolute top-4 right-4 text-gray-400">
|
||
<IconX />
|
||
</button>
|
||
|
||
<h3 className="mb-2 text-lg font-semibold">Delete User</h3>
|
||
<p className="text-sm text-gray-400 mb-6">
|
||
Are you sure you want to delete <span className="text-white">{userToDelete?.email}</span>?
|
||
</p>
|
||
|
||
<div className="flex justify-end gap-3">
|
||
<button className="btn btn-outline-danger" onClick={() => setDeleteUserModal(false)}>
|
||
Cancel
|
||
</button>
|
||
<button
|
||
className="btn btn-primary bg-gradient-to-r from-blue-600 to-pink-500"
|
||
onClick={async () => {
|
||
await axios.delete(`/api/users/${userToDelete.userid}`);
|
||
showMessage('User deleted');
|
||
setDeleteUserModal(false);
|
||
fetchUsers();
|
||
}}
|
||
>
|
||
Delete
|
||
</button>
|
||
</div>
|
||
</Dialog.Panel>
|
||
</div>
|
||
</Dialog>
|
||
</Transition>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default UserModule;
|