diff --git a/backend/src/routes/users.ts b/backend/src/routes/users.ts index ebeeb43..bc558b1 100644 --- a/backend/src/routes/users.ts +++ b/backend/src/routes/users.ts @@ -17,9 +17,11 @@ const usersRouter = new Hono<{ Variables: { user: UserContext } }>(); const updateUserSchema = z.object({ name: z.string().min(2).optional(), + email: z.string().email().optional(), phone: z.string().optional(), role: z.enum(['admin', 'organizer', 'staff', 'marketing', 'user']).optional(), languagePreference: z.enum(['en', 'es']).optional(), + accountStatus: z.enum(['active', 'unclaimed', 'suspended']).optional(), }); // Get all users (admin only) @@ -33,6 +35,9 @@ usersRouter.get('/', requireAuth(['admin']), async (c) => { phone: (users as any).phone, role: (users as any).role, languagePreference: (users as any).languagePreference, + isClaimed: (users as any).isClaimed, + rucNumber: (users as any).rucNumber, + accountStatus: (users as any).accountStatus, createdAt: (users as any).createdAt, }).from(users); @@ -64,6 +69,9 @@ usersRouter.get('/:id', requireAuth(['admin', 'organizer', 'staff', 'marketing', phone: (users as any).phone, role: (users as any).role, languagePreference: (users as any).languagePreference, + isClaimed: (users as any).isClaimed, + rucNumber: (users as any).rucNumber, + accountStatus: (users as any).accountStatus, createdAt: (users as any).createdAt, }) .from(users) @@ -88,10 +96,16 @@ usersRouter.put('/:id', requireAuth(['admin', 'organizer', 'staff', 'marketing', return c.json({ error: 'Forbidden' }, 403); } - // Only admin can change roles + // Only admin can change roles, email, and account status if (data.role && currentUser.role !== 'admin') { delete data.role; } + if (data.email && currentUser.role !== 'admin') { + delete data.email; + } + if (data.accountStatus && currentUser.role !== 'admin') { + delete data.accountStatus; + } const existing = await dbGet( (db as any).select().from(users).where(eq((users as any).id, id)) @@ -114,6 +128,10 @@ usersRouter.put('/:id', requireAuth(['admin', 'organizer', 'staff', 'marketing', phone: (users as any).phone, role: (users as any).role, languagePreference: (users as any).languagePreference, + isClaimed: (users as any).isClaimed, + rucNumber: (users as any).rucNumber, + accountStatus: (users as any).accountStatus, + createdAt: (users as any).createdAt, }) .from(users) .where(eq((users as any).id, id)) diff --git a/frontend/src/app/admin/users/page.tsx b/frontend/src/app/admin/users/page.tsx index 249efe0..b1599a7 100644 --- a/frontend/src/app/admin/users/page.tsx +++ b/frontend/src/app/admin/users/page.tsx @@ -5,7 +5,8 @@ import { useLanguage } from '@/context/LanguageContext'; import { usersApi, User } from '@/lib/api'; import Card from '@/components/ui/Card'; import Button from '@/components/ui/Button'; -import { TrashIcon } from '@heroicons/react/24/outline'; +import Input from '@/components/ui/Input'; +import { TrashIcon, PencilSquareIcon } from '@heroicons/react/24/outline'; import toast from 'react-hot-toast'; export default function AdminUsersPage() { @@ -13,6 +14,16 @@ export default function AdminUsersPage() { const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); const [roleFilter, setRoleFilter] = useState(''); + const [editingUser, setEditingUser] = useState(null); + const [editForm, setEditForm] = useState({ + name: '', + email: '', + phone: '', + role: '' as User['role'], + languagePreference: '' as string, + accountStatus: '' as string, + }); + const [saving, setSaving] = useState(false); useEffect(() => { loadUsers(); @@ -51,6 +62,51 @@ export default function AdminUsersPage() { } }; + const openEditModal = (user: User) => { + setEditingUser(user); + setEditForm({ + name: user.name, + email: user.email, + phone: user.phone || '', + role: user.role, + languagePreference: user.languagePreference || '', + accountStatus: user.accountStatus || 'active', + }); + }; + + const handleEditSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!editingUser) return; + + if (!editForm.name.trim() || editForm.name.trim().length < 2) { + toast.error('Name must be at least 2 characters'); + return; + } + if (!editForm.email.trim()) { + toast.error('Email is required'); + return; + } + + setSaving(true); + try { + await usersApi.update(editingUser.id, { + name: editForm.name.trim(), + email: editForm.email.trim(), + phone: editForm.phone.trim() || undefined, + role: editForm.role, + languagePreference: editForm.languagePreference || undefined, + accountStatus: editForm.accountStatus || undefined, + } as Partial); + toast.success('User updated successfully'); + setEditingUser(null); + loadUsers(); + } catch (error: any) { + toast.error(error.message || 'Failed to update user'); + } finally { + setSaving(false); + } + }; + const formatDate = (dateStr: string) => { return new Date(dateStr).toLocaleDateString(locale === 'es' ? 'es-ES' : 'en-US', { year: 'numeric', @@ -162,6 +218,13 @@ export default function AdminUsersPage() {
+
+ + {/* Edit User Modal */} + {editingUser && ( +
+ +

Edit User

+ +
+ setEditForm({ ...editForm, name: e.target.value })} + required + minLength={2} + /> + + setEditForm({ ...editForm, email: e.target.value })} + required + /> + + setEditForm({ ...editForm, phone: e.target.value })} + placeholder="Optional" + /> + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+
+ )} ); }