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>
83 lines
3.4 KiB
TypeScript
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';
|