import { useState } from 'react' import { Head, Link, useForm } from '@inertiajs/react' import Layout from '../../Layout' import * as Dialog from '@radix-ui/react-dialog' import * as Form from '@radix-ui/react-form' import * as Switch from '@radix-ui/react-switch' export default function AdminUsersIndex({ users, auth, errors = {} }) { const [isCreateModalOpen, setIsCreateModalOpen] = useState(false) const [isEditModalOpen, setIsEditModalOpen] = useState(false) const [editingUser, setEditingUser] = useState(null) const { data: createData, setData: setCreateData, post: createUser, processing: createProcessing, reset: resetCreate } = useForm({ name: '', email_address: '', password: '', password_confirmation: '', admin: false, }) const { data: editData, setData: setEditData, patch: updateUser, processing: editProcessing, reset: resetEdit } = useForm({ name: '', email_address: '', password: '', password_confirmation: '', admin: false, }) const handleCreateSubmit = (e) => { e.preventDefault() createUser('/admin/users', { onSuccess: () => { setIsCreateModalOpen(false) resetCreate() }, }) } const handleEditSubmit = (e) => { e.preventDefault() updateUser(`/admin/users/${editingUser.id}`, { onSuccess: () => { setIsEditModalOpen(false) setEditingUser(null) resetEdit() }, }) } const openEditModal = (user) => { setEditingUser(user) setEditData({ name: user.name, email_address: user.email_address, password: '', password_confirmation: '', admin: user.admin, }) setIsEditModalOpen(true) } return ( <Layout user={auth.user}> <Head title="Admin - Manage Users" /> <div className="bg-white shadow overflow-hidden sm:rounded-lg"> <div className="px-4 py-5 sm:px-6 flex justify-between items-center"> <div> <h1 className="text-2xl font-bold text-gray-900">Manage Users</h1> <p className="mt-1 max-w-2xl text-sm text-gray-500"> Add, edit, and manage user accounts </p> </div> <button onClick={() => setIsCreateModalOpen(true)} className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" > Create New User </button> </div> <div className="border-t border-gray-200"> {users.length > 0 ? ( <div className="overflow-x-auto"> <table className="min-w-full divide-y divide-gray-200"> <thead className="bg-gray-50"> <tr> <th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider" > Name </th> <th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider" > Email </th> <th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider" > Role </th> <th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider" > Images </th> <th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider" > Created At </th> <th scope="col" className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider" > Actions </th> </tr> </thead> <tbody className="bg-white divide-y divide-gray-200"> {users.map((user) => ( <tr key={user.id}> <td className="px-6 py-4 whitespace-nowrap"> <div className="text-sm font-medium text-gray-900"> {user.name} </div> </td> <td className="px-6 py-4 whitespace-nowrap"> <div className="text-sm text-gray-500"> {user.email_address} </div> </td> <td className="px-6 py-4 whitespace-nowrap"> <span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${ user.admin ? 'bg-purple-100 text-purple-800' : 'bg-blue-100 text-blue-800' }`} > {user.admin ? 'Admin' : 'User'} </span> </td> <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> {user.images_count || 0} </td> <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> {new Date(user.created_at).toLocaleDateString()} </td> <td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> <div className="flex space-x-2 justify-end"> <button onClick={() => openEditModal(user)} className="text-indigo-600 hover:text-indigo-900" > Edit </button> {user.id !== auth.user.id && ( <Link href={`/admin/users/${user.id}`} method="delete" as="button" className="text-red-600 hover:text-red-900" onClick={(e) => { if (!confirm('Are you sure you want to delete this user? This will also delete all their images.')) { e.preventDefault() } }} > Delete </Link> )} </div> </td> </tr> ))} </tbody> </table> </div> ) : ( <div className="text-center py-12"> <svg className="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true" > <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /> </svg> <h3 className="mt-2 text-sm font-medium text-gray-900"> No users </h3> <p className="mt-1 text-sm text-gray-500"> Get started by creating a new user. </p> <div className="mt-6"> <button onClick={() => setIsCreateModalOpen(true)} className="inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" > Create New User </button> </div> </div> )} </div> </div> {/* Create User Modal */} <Dialog.Root open={isCreateModalOpen} onOpenChange={setIsCreateModalOpen}> <Dialog.Portal> <Dialog.Overlay className="fixed inset-0 bg-black/50" /> <Dialog.Content className="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-white rounded-lg shadow-lg p-6 w-full max-w-md"> <Dialog.Title className="text-lg font-medium text-gray-900"> Create New User </Dialog.Title> <Dialog.Description className="mt-2 text-sm text-gray-500"> Create a new user account </Dialog.Description> <Form.Root className="mt-4 space-y-4" onSubmit={handleCreateSubmit}> <Form.Field name="name" className="space-y-2"> <Form.Label className="block text-sm font-medium text-gray-700"> Name </Form.Label> <Form.Control asChild> <input type="text" name="name" value={createData.name} onChange={(e) => setCreateData('name', e.target.value)} className="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md" placeholder="Enter user name" /> </Form.Control> {errors.name && ( <Form.Message className="text-sm text-red-600"> {errors.name} </Form.Message> )} </Form.Field> <Form.Field name="email_address" className="space-y-2"> <Form.Label className="block text-sm font-medium text-gray-700"> Email Address </Form.Label> <Form.Control asChild> <input type="email" name="email_address" value={createData.email_address} onChange={(e) => setCreateData('email_address', e.target.value)} className="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md" placeholder="Enter email address" /> </Form.Control> {errors.email_address && ( <Form.Message className="text-sm text-red-600"> {errors.email_address} </Form.Message> )} </Form.Field> <Form.Field name="password" className="space-y-2"> <Form.Label className="block text-sm font-medium text-gray-700"> Password </Form.Label> <Form.Control asChild> <input type="password" name="password" value={createData.password} onChange={(e) => setCreateData('password', e.target.value)} className="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md" placeholder="Enter password" /> </Form.Control> {errors.password && ( <Form.Message className="text-sm text-red-600"> {errors.password} </Form.Message> )} </Form.Field> <Form.Field name="password_confirmation" className="space-y-2"> <Form.Label className="block text-sm font-medium text-gray-700"> Confirm Password </Form.Label> <Form.Control asChild> <input type="password" name="password_confirmation" value={createData.password_confirmation} onChange={(e) => setCreateData('password_confirmation', e.target.value)} className="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md" placeholder="Confirm password" /> </Form.Control> </Form.Field> <div className="flex items-center"> <Form.Field name="admin" className="flex items-center"> <Form.Label className="mr-3 text-sm font-medium text-gray-700"> Admin User </Form.Label> <Form.Control asChild> <Switch.Root checked={createData.admin} onCheckedChange={(checked) => setCreateData('admin', checked)} className="relative inline-flex h-6 w-11 items-center rounded-full bg-gray-200 data-[state=checked]:bg-indigo-600" > <Switch.Thumb className="pointer-events-none block h-4 w-4 rounded-full bg-white shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-1" /> </Switch.Root> </Form.Control> </Form.Field> </div> <div className="mt-5 sm:mt-6 sm:grid sm:grid-cols-2 sm:gap-3 sm:grid-flow-row-dense"> <Form.Submit asChild> <button type="submit" disabled={createProcessing} className="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-indigo-600 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:col-start-2 sm:text-sm" > {createProcessing ? 'Creating...' : 'Create'} </button> </Form.Submit> <Dialog.Close asChild> <button type="button" className="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:col-start-1 sm:text-sm" > Cancel </button> </Dialog.Close> </div> </Form.Root> </Dialog.Content> </Dialog.Portal> </Dialog.Root> {/* Edit User Modal */} <Dialog.Root open={isEditModalOpen} onOpenChange={setIsEditModalOpen}> <Dialog.Portal> <Dialog.Overlay className="fixed inset-0 bg-black/50" /> <Dialog.Content className="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-white rounded-lg shadow-lg p-6 w-full max-w-md"> <Dialog.Title className="text-lg font-medium text-gray-900"> Edit User </Dialog.Title> <Dialog.Description className="mt-2 text-sm text-gray-500"> Update user account details </Dialog.Description> <Form.Root className="mt-4 space-y-4" onSubmit={handleEditSubmit}> <Form.Field name="name" className="space-y-2"> <Form.Label className="block text-sm font-medium text-gray-700"> Name </Form.Label> <Form.Control asChild> <input type="text" name="name" value={editData.name} onChange={(e) => setEditData('name', e.target.value)} className="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md" placeholder="Enter user name" /> </Form.Control> {errors.name && ( <Form.Message className="text-sm text-red-600"> {errors.name} </Form.Message> )} </Form.Field> <Form.Field name="email_address" className="space-y-2"> <Form.Label className="block text-sm font-medium text-gray-700"> Email Address </Form.Label> <Form.Control asChild> <input type="email" name="email_address" value={editData.email_address} onChange={(e) => setEditData('email_address', e.target.value)} className="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md" placeholder="Enter email address" /> </Form.Control> {errors.email_address && ( <Form.Message className="text-sm text-red-600"> {errors.email_address} </Form.Message> )} </Form.Field> <Form.Field name="password" className="space-y-2"> <Form.Label className="block text-sm font-medium text-gray-700"> New Password (leave blank to keep current) </Form.Label> <Form.Control asChild> <input type="password" name="password" value={editData.password} onChange={(e) => setEditData('password', e.target.value)} className="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md" placeholder="Enter new password" /> </Form.Control> {errors.password && ( <Form.Message className="text-sm text-red-600"> {errors.password} </Form.Message> )} </Form.Field> <Form.Field name="password_confirmation" className="space-y-2"> <Form.Label className="block text-sm font-medium text-gray-700"> Confirm New Password </Form.Label> <Form.Control asChild> <input type="password" name="password_confirmation" value={editData.password_confirmation} onChange={(e) => setEditData('password_confirmation', e.target.value)} className="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md" placeholder="Confirm new password" /> </Form.Control> </Form.Field> <div className="flex items-center"> <Form.Field name="admin" className="flex items-center"> <Form.Label className="mr-3 text-sm font-medium text-gray-700"> Admin User </Form.Label> <Form.Control asChild> <Switch.Root checked={editData.admin} onCheckedChange={(checked) => setEditData('admin', checked)} className="relative inline-flex h-6 w-11 items-center rounded-full bg-gray-200 data-[state=checked]:bg-indigo-600" > <Switch.Thumb className="pointer-events-none block h-4 w-4 rounded-full bg-white shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-1" /> </Switch.Root> </Form.Control> </Form.Field> </div> <div className="mt-5 sm:mt-6 sm:grid sm:grid-cols-2 sm:gap-3 sm:grid-flow-row-dense"> <Form.Submit asChild> <button type="submit" disabled={editProcessing} className="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-indigo-600 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:col-start-2 sm:text-sm" > {editProcessing ? 'Saving...' : 'Save Changes'} </button> </Form.Submit> <Dialog.Close asChild> <button type="button" className="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:col-start-1 sm:text-sm" > Cancel </button> </Dialog.Close> </div> </Form.Root> </Dialog.Content> </Dialog.Portal> </Dialog.Root> </Layout> ) }