Files
4ventures-client-portal/src/lib/agency.ts
4Ventures 71110915af Ship CRM health, cohort CAC, GHL/Shopify sync, and agency alerts.
Add WooCommerce health monitoring, 60d cohort CAC in CRM and portfolio,
incremental GHL upsert, Shopify orders→deals, and daily health-alert cron.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-08 12:28:38 +02:00

83 lines
3.4 KiB
TypeScript

/** Agency portfolio helpers — providers, role gates, service-role REST. */
export const INTEGRATION_PROVIDERS = [
{ id: 'meta_ads', label: 'Meta Ads', group: 'paid', live: true },
{ id: 'google_ads', label: 'Google Ads (MCC)', group: 'paid', live: true },
{ id: 'ga4', label: 'Google Analytics 4', group: 'analytics', live: true },
{ id: 'search_console', label: 'Search Console', group: 'analytics', live: true },
{ id: 'tag_manager', label: 'Tag Manager', group: 'analytics', live: false },
{ id: 'clientify', label: 'Clientify', group: 'crm', live: true },
{ id: 'gohighlevel', label: 'GoHighLevel', group: 'crm', live: true },
{ id: 'klaviyo', label: 'Klaviyo', group: 'email', live: false },
{ id: 'mailchimp', label: 'Mailchimp', group: 'email', live: false },
{ id: 'google_drive', label: 'Google Drive', group: 'files', live: true },
{ id: 'wordpress', label: 'WordPress', group: 'cms', live: false },
{ id: 'prestashop', label: 'PrestaShop', group: 'cms', live: false },
{ id: 'shopify', label: 'Shopify', group: 'cms', live: true },
{ id: 'woocommerce', label: 'WooCommerce', group: 'cms', live: true },
{ id: 'instagram', label: 'Instagram', group: 'social', live: true },
{ id: 'facebook', label: 'Facebook Page', group: 'social', live: true },
{ id: 'tiktok', label: 'TikTok', group: 'social', live: false },
{ id: 'linkedin', label: 'LinkedIn', group: 'social', live: false },
{ id: 'superhuman_docs', label: 'Superhuman Docs', group: 'ops', live: true },
] 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';
}
/**
* Authorization role. Privileged roles (admin/team) ONLY from app_metadata
* so clients cannot self-elevate via user_metadata.updateUser.
*/
export function userRole(user: {
app_metadata?: Record<string, unknown>;
user_metadata?: Record<string, unknown>;
} | null): string {
const app = (user?.app_metadata?.role as string | undefined)?.trim();
if (app === 'admin' || app === 'team' || app === 'client') return app;
// Legacy clients may only have user_metadata.role=client — never admin/team.
const um = (user?.user_metadata?.role as string | undefined)?.trim();
if (um === 'client') return 'client';
return 'client';
}
export function homeTenantId(user: {
app_metadata?: Record<string, unknown>;
user_metadata?: Record<string, unknown>;
} | null): string | null {
const app = (user?.app_metadata?.tenant_id as string | undefined)?.trim();
if (app) return app;
// Legacy client JWT may still carry tenant only in user_metadata.
const um = (user?.user_metadata?.tenant_id as string | undefined)?.trim();
return um || 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';