feat(admin): add direct client onboarding modal and management directly in app.4ventures.es
This commit is contained in:
46
src/app/api/admin/create-client/route.ts
Normal file
46
src/app/api/admin/create-client/route.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { createClient } from '@/lib/supabase/server';
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const supabase = await createClient();
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'No autorizado. Inicia sesión.' }, { status: 401 });
|
||||
}
|
||||
|
||||
const userRole = user.app_metadata?.role || user.user_metadata?.role || 'client';
|
||||
if (userRole !== 'admin' && userRole !== 'team') {
|
||||
return NextResponse.json({ error: 'Permisos insuficientes. Solo administradores pueden crear clientes.' }, { status: 403 });
|
||||
}
|
||||
|
||||
const body = await req.json();
|
||||
const { companyName, slug, email, password, role } = body;
|
||||
|
||||
if (!companyName || !slug || !email || !password) {
|
||||
return NextResponse.json({ error: 'Todos los campos son obligatorios.' }, { status: 400 });
|
||||
}
|
||||
|
||||
const sanitizedSlug = slug.toLowerCase().trim().replace(/[^a-z0-9-]/g, '-');
|
||||
const targetRole = role === 'admin' || role === 'team' ? role : 'client';
|
||||
|
||||
const { data, error } = await supabase.rpc('provision_client', {
|
||||
p_company_name: companyName.trim(),
|
||||
p_slug: sanitizedSlug,
|
||||
p_email: email.toLowerCase().trim(),
|
||||
p_password: password,
|
||||
p_role: targetRole,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.error('Error in provision_client RPC:', error);
|
||||
return NextResponse.json({ error: error.message || 'Error al provisionar cliente en base de datos.' }, { status: 500 });
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true, client: data });
|
||||
} catch (err: any) {
|
||||
console.error('Server error creating client:', err);
|
||||
return NextResponse.json({ error: err?.message || 'Error interno del servidor.' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Building2, DollarSign, Users, TrendingUp, Layers } from 'lucide-react';
|
||||
import ClientManagement from '@/components/ClientManagement';
|
||||
|
||||
export const revalidate = 0;
|
||||
|
||||
@@ -101,50 +102,8 @@ export default async function AgencyPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tenants Table */}
|
||||
<div className="bg-[#111827] border border-gray-800 rounded-xl overflow-hidden">
|
||||
<div className="p-5 border-b border-gray-800">
|
||||
<h3 className="text-base font-bold text-white">Cartera de Clientes Activos</h3>
|
||||
<p className="text-xs text-gray-400 mt-0.5">
|
||||
Cada cliente cuenta con aislamiento nativo Row Level Security (RLS) en Postgres.
|
||||
</p>
|
||||
</div>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-left text-sm text-gray-300">
|
||||
<thead className="bg-gray-900/50 text-xs uppercase text-gray-400 border-b border-gray-800">
|
||||
<tr>
|
||||
<th className="px-5 py-3">Nombre del Cliente</th>
|
||||
<th className="px-5 py-3">Tenant Slug</th>
|
||||
<th className="px-5 py-3">UUID</th>
|
||||
<th className="px-5 py-3">Acceso Portal</th>
|
||||
<th className="px-5 py-3">Estado</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-800/60">
|
||||
{tenants.map((t) => (
|
||||
<tr key={t.id} className="hover:bg-gray-800/30 transition-colors">
|
||||
<td className="px-5 py-3.5 font-semibold text-white">{t.name}</td>
|
||||
<td className="px-5 py-3.5 text-xs text-brand-400 font-mono">{t.slug}</td>
|
||||
<td className="px-5 py-3.5 text-xs text-gray-400 font-mono">{t.id}</td>
|
||||
<td className="px-5 py-3.5 text-xs">
|
||||
<a
|
||||
href={`/dashboard/kpis?tenant=${t.slug}`}
|
||||
className="text-brand-400 hover:underline inline-flex items-center gap-1"
|
||||
>
|
||||
Abrir Portal ↗
|
||||
</a>
|
||||
</td>
|
||||
<td className="px-5 py-3.5 text-xs">
|
||||
<span className="bg-emerald-950/80 text-emerald-400 px-2 py-0.5 rounded-full border border-emerald-800/40 text-[10px] font-bold uppercase">
|
||||
Activo
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{/* Interactive Tenants Management with Onboarding Form */}
|
||||
<ClientManagement initialTenants={tenants} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user