feat: agency portfolio with per-client integrations hub

Admin home lists tenants, alerts, and connection health; header
selector enables superadmin context; Meta/GA4/GSC/GTM/CRM/email
slots live per tenant (manual status until OAuth).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
4Ventures
2026-09-22 01:31:47 +02:00
parent 9d1609c88b
commit 791976a3d8
19 changed files with 1503 additions and 20 deletions

66
src/lib/agency.ts Normal file
View File

@@ -0,0 +1,66 @@
/** Agency portfolio helpers — providers, role gates, service-role REST. */
export const INTEGRATION_PROVIDERS = [
{ id: 'meta_ads', label: 'Meta Ads', group: 'paid' },
{ id: 'google_ads', label: 'Google Ads (MCC)', group: 'paid' },
{ id: 'ga4', label: 'Google Analytics 4', group: 'analytics' },
{ id: 'search_console', label: 'Search Console', group: 'analytics' },
{ id: 'tag_manager', label: 'Tag Manager', group: 'analytics' },
{ id: 'clientify', label: 'Clientify', group: 'crm' },
{ id: 'klaviyo', label: 'Klaviyo', group: 'email' },
{ id: 'mailchimp', label: 'Mailchimp', group: 'email' },
] as const;
export type IntegrationProviderId =
(typeof INTEGRATION_PROVIDERS)[number]['id'];
export type IntegrationStatus =
| 'disconnected'
| 'pending'
| 'connected'
| 'error';
export function isAgencyRole(role: string | undefined | null): boolean {
return role === 'admin' || role === 'team';
}
export function userRole(user: {
app_metadata?: Record<string, unknown>;
user_metadata?: Record<string, unknown>;
} | null): string {
return (
(user?.app_metadata?.role as string) ||
(user?.user_metadata?.role as string) ||
'client'
);
}
export function homeTenantId(user: {
app_metadata?: Record<string, unknown>;
user_metadata?: Record<string, unknown>;
} | null): string | null {
return (
(user?.app_metadata?.tenant_id as string) ||
(user?.user_metadata?.tenant_id as string) ||
null
);
}
export function serviceHeaders():
| { apikey: string; Authorization: string }
| null {
const key = process.env.SUPABASE_SERVICE_ROLE_KEY?.trim();
if (!key) return null;
return { apikey: key, Authorization: `Bearer ${key}` };
}
export function supabaseUrl(): string {
return (
process.env.NEXT_PUBLIC_SUPABASE_URL?.trim() ||
'https://supabase.4ventures.es'
);
}
export const VIEW_TENANT_COOKIE = '4v_view_tenant_id';
export const VIEW_TENANT_SLUG_COOKIE = '4v_view_tenant_slug';
export const VIEW_TENANT_NAME_COOKIE = '4v_view_tenant_name';