From 07d41e1f6be39bb3f5684fe8e5e21132839664cb Mon Sep 17 00:00:00 2001 From: Carlos Narro Date: Sat, 30 May 2026 19:57:10 +0200 Subject: [PATCH] =?UTF-8?q?Add=20gesti=C3=B3n=20de=20usuarios=20en=20el=20?= =?UTF-8?q?admin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/usuarios/CrearReformistaForm.tsx | 22 +++++++++ mvp/b2c/src/app/admin/usuarios/actions.ts | 39 ++++++++++++++++ mvp/b2c/src/app/admin/usuarios/page.tsx | 45 +++++++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 mvp/b2c/src/app/admin/usuarios/CrearReformistaForm.tsx create mode 100644 mvp/b2c/src/app/admin/usuarios/actions.ts create mode 100644 mvp/b2c/src/app/admin/usuarios/page.tsx diff --git a/mvp/b2c/src/app/admin/usuarios/CrearReformistaForm.tsx b/mvp/b2c/src/app/admin/usuarios/CrearReformistaForm.tsx new file mode 100644 index 0000000..2490471 --- /dev/null +++ b/mvp/b2c/src/app/admin/usuarios/CrearReformistaForm.tsx @@ -0,0 +1,22 @@ +'use client'; + +import { useActionState } from 'react'; +import { crearReformista } from './actions'; + +export function CrearReformistaForm() { + const [error, action, pending] = useActionState(crearReformista, null); + return ( +
+

Crear reformista

+ + + + + + {error &&

{error}

} + +
+ ); +} diff --git a/mvp/b2c/src/app/admin/usuarios/actions.ts b/mvp/b2c/src/app/admin/usuarios/actions.ts new file mode 100644 index 0000000..8ac043e --- /dev/null +++ b/mvp/b2c/src/app/admin/usuarios/actions.ts @@ -0,0 +1,39 @@ +'use server'; + +import { revalidatePath } from 'next/cache'; +import { requireAdmin } from '@/lib/auth/current-user'; +import { signupSchema, slugify } from '@/lib/validation/signup'; +import { getUserByEmail, createTenantWithOwner, slugDisponible } from '@/db/auth-queries'; +import { setUserStatus } from '@/db/admin-queries'; +import { hashPassword } from '@/lib/auth/password'; + +export async function crearReformista(_prev: string | null, formData: FormData): Promise { + await requireAdmin(); + const parsed = signupSchema.safeParse(Object.fromEntries(formData)); + if (!parsed.success) return parsed.error.issues[0]?.message ?? 'Datos no válidos.'; + const data = parsed.data; + if (await getUserByEmail(data.email)) return 'Ya existe una cuenta con ese email.'; + + let slug = slugify(data.empresa); + let n = 1; + while (!(await slugDisponible(slug))) slug = `${slugify(data.empresa)}-${++n}`; + + await createTenantWithOwner({ + nombreEmpresa: data.empresa, + slug, + provincia: data.provincia, + email: data.email, + passwordHash: await hashPassword(data.password), + nombre: data.nombre, + }); + revalidatePath('/admin/usuarios'); + return null; +} + +export async function toggleUsuario(formData: FormData) { + await requireAdmin(); + const userId = String(formData.get('userId')); + const next = String(formData.get('next')) as 'activo' | 'deshabilitado'; + await setUserStatus(userId, next); + revalidatePath('/admin/usuarios'); +} diff --git a/mvp/b2c/src/app/admin/usuarios/page.tsx b/mvp/b2c/src/app/admin/usuarios/page.tsx new file mode 100644 index 0000000..1ff6517 --- /dev/null +++ b/mvp/b2c/src/app/admin/usuarios/page.tsx @@ -0,0 +1,45 @@ +import { listUsers, listTenants } from '@/db/admin-queries'; +import { toggleUsuario } from './actions'; +import { CrearReformistaForm } from './CrearReformistaForm'; + +export const dynamic = 'force-dynamic'; + +export default async function UsuariosPage() { + const [users, tenants] = await Promise.all([listUsers(), listTenants()]); + const empresaDe = new Map(tenants.map((t) => [t.id, t.nombreEmpresa])); + return ( +
+

Usuarios

+ +
+ + + + + + + {users.map((u) => ( + + + + + + + + ))} + +
EmailRolEmpresaEstado
{u.email}{u.role}{u.tenantId ? empresaDe.get(u.tenantId) ?? '—' : '—'}{u.status} + {u.role !== 'admin' && ( +
+ + + +
+ )} +
+
+
+ ); +}