feat: ficha por cliente en portafolio (conexiones, accesos, logo, módulos)

Cada tenant tiene una ficha con plataformas, invitaciones por email,
logo de marca y toggles de módulos visibles en el portal cliente.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
4Ventures
2026-09-24 10:51:21 +02:00
parent 21051afda9
commit dd0e867108
13 changed files with 1612 additions and 84 deletions

View File

@@ -1,5 +1,5 @@
import React from 'react';
import ClientIntegrationsPanel from '@/components/ClientIntegrationsPanel';
import ClientFicha from '@/components/ClientFicha';
import { createClient } from '@/lib/supabase/server';
import { redirect } from 'next/navigation';
import {
@@ -11,7 +11,7 @@ import {
export const dynamic = 'force-dynamic';
export default async function ClienteIntegrationsPage({
export default async function ClienteFichaPage({
params,
}: {
params: Promise<{ id: string }>;
@@ -37,5 +37,5 @@ export default async function ClienteIntegrationsPage({
}
}
return <ClientIntegrationsPanel tenantId={id} tenantName={name} />;
return <ClientFicha tenantId={id} initialName={name} />;
}

View File

@@ -2,8 +2,21 @@ import React from 'react';
import { createClient } from '@/lib/supabase/server';
import { logoutAction } from '@/app/auth/actions';
import { cookies } from 'next/headers';
import { VIEW_TENANT_NAME_COOKIE } from '@/lib/agency';
import {
VIEW_TENANT_COOKIE,
VIEW_TENANT_NAME_COOKIE,
homeTenantId,
isAgencyRole,
serviceHeaders,
supabaseUrl,
userRole,
} from '@/lib/agency';
import DashboardShell from '@/components/DashboardShell';
import {
DashboardModuleId,
TenantSettings,
resolveTenantModules,
} from '@/lib/tenant-modules';
export default async function DashboardLayout({
children,
@@ -15,13 +28,56 @@ export default async function DashboardLayout({
data: { user },
} = await supabase.auth.getUser();
const role =
user?.user_metadata?.role || user?.app_metadata?.role || 'client';
const company = user?.user_metadata?.company || '4Ventures Central';
const role = userRole(user);
const company =
(user?.user_metadata?.company as string) ||
(user?.app_metadata?.company as string) ||
'4Ventures Central';
const email = user?.email || 'usuario@4ventures.es';
const isAgency = role === 'admin' || role === 'team';
const isAgency = isAgencyRole(role);
const cookieStore = await cookies();
const viewName = cookieStore.get(VIEW_TENANT_NAME_COOKIE)?.value;
const viewId = cookieStore.get(VIEW_TENANT_COOKIE)?.value || null;
const ownId = homeTenantId(user);
let enabledModules: Record<DashboardModuleId, boolean> | null = null;
let clientLogoUrl: string | null = null;
let brandLabel = company;
// Client users (and agency zoomed into a client for branding): load ficha settings
const brandingTenantId = isAgency ? viewId : ownId;
if (brandingTenantId) {
const headers = serviceHeaders();
if (headers) {
const [tRes, iRes] = await Promise.all([
fetch(
`${supabaseUrl()}/rest/v1/tenants?id=eq.${encodeURIComponent(brandingTenantId)}&select=name,logo_url,settings`,
{ headers, cache: 'no-store' }
),
fetch(
`${supabaseUrl()}/rest/v1/integrations?tenant_id=eq.${encodeURIComponent(brandingTenantId)}&select=provider,status,enabled`,
{ headers, cache: 'no-store' }
),
]);
if (tRes.ok) {
const rows = await tRes.json();
const t = rows[0];
if (t) {
brandLabel = t.name || brandLabel;
if (t.logo_url) {
clientLogoUrl = `/api/branding/logo?tenantId=${encodeURIComponent(brandingTenantId)}`;
}
const integrations = iRes.ok ? await iRes.json() : [];
if (!isAgency) {
enabledModules = resolveTenantModules(
(t.settings || {}) as TenantSettings,
integrations || []
);
}
}
}
}
}
return (
<DashboardShell
@@ -30,6 +86,11 @@ export default async function DashboardLayout({
role={role}
isAgency={isAgency}
viewName={viewName}
enabledModules={enabledModules}
clientLogoUrl={
isAgency && viewId ? clientLogoUrl : !isAgency ? clientLogoUrl : null
}
brandLabel={brandLabel}
logoutAction={logoutAction}
>
{children}