@fayz-ai/plugin-marketing 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/{MarketingContext.d.ts → context.d.ts} +5 -3
- package/dist/context.d.ts.map +1 -0
- package/dist/data/mock.d.ts +1 -1
- package/dist/data/mock.d.ts.map +1 -1
- package/dist/{registries.d.ts → data/registries.d.ts} +1 -1
- package/dist/data/registries.d.ts.map +1 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +241 -188
- package/dist/index.js.map +1 -1
- package/dist/integrations/ranklayer/connectorDef.d.ts +1 -1
- package/dist/integrations/ranklayer/connectorDef.d.ts.map +1 -1
- package/dist/integrations/ranklayer/data/supabase.d.ts +24 -6
- package/dist/integrations/ranklayer/data/supabase.d.ts.map +1 -1
- package/dist/integrations/ranklayer/outbound.d.ts +16 -0
- package/dist/integrations/ranklayer/outbound.d.ts.map +1 -0
- package/dist/integrations/ranklayer/settings.d.ts +12 -0
- package/dist/integrations/ranklayer/settings.d.ts.map +1 -0
- package/dist/integrations/ranklayer/types.d.ts +0 -1
- package/dist/integrations/ranklayer/types.d.ts.map +1 -1
- package/dist/lib/format.d.ts.map +1 -0
- package/dist/{presets.d.ts → lib/presets.d.ts} +1 -1
- package/dist/lib/presets.d.ts.map +1 -0
- package/dist/migrations/index.d.ts +1 -1
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/{MarketingPage.d.ts → views/MarketingPage.d.ts} +5 -5
- package/dist/views/MarketingPage.d.ts.map +1 -0
- package/dist/views/blog/BlogAdminView.d.ts.map +1 -1
- package/dist/views/content/ContentPlannerContext.d.ts +1 -1
- package/dist/views/content/ContentPlannerContext.d.ts.map +1 -1
- package/dist/views/dashboardWidgets.d.ts +1 -1
- package/dist/views/dashboardWidgets.d.ts.map +1 -1
- package/package.json +6 -9
- package/dist/MarketingContext.d.ts.map +0 -1
- package/dist/MarketingPage.d.ts.map +0 -1
- package/dist/format.d.ts.map +0 -1
- package/dist/presets.d.ts.map +0 -1
- package/dist/registries.d.ts.map +0 -1
- package/src/MarketingContext.tsx +0 -42
- package/src/MarketingPage.tsx +0 -139
- package/src/components/MarketingBits.tsx +0 -131
- package/src/components/icons.tsx +0 -36
- package/src/data/blogEntities.tsx +0 -91
- package/src/data/capability.test.ts +0 -92
- package/src/data/contentMedia.ts +0 -25
- package/src/data/contentMock.ts +0 -253
- package/src/data/contentSupabase.ts +0 -263
- package/src/data/contentTypes.ts +0 -138
- package/src/data/mock.ts +0 -308
- package/src/data/supabase.ts +0 -613
- package/src/data/tables.ts +0 -33
- package/src/data/types.ts +0 -97
- package/src/format.ts +0 -40
- package/src/index.ts +0 -457
- package/src/integrations/ranklayer/RANKLAYER.md +0 -40
- package/src/integrations/ranklayer/connectorDef.tsx +0 -102
- package/src/integrations/ranklayer/data/supabase.ts +0 -106
- package/src/integrations/ranklayer/types.ts +0 -16
- package/src/locales/en.ts +0 -192
- package/src/locales/index.ts +0 -7
- package/src/locales/pt-BR.ts +0 -192
- package/src/migrations/000_plg_rename.sql +0 -15
- package/src/migrations/001_content_planner.sql +0 -94
- package/src/migrations/002_multi_platform.sql +0 -25
- package/src/migrations/003_recording_ops.sql +0 -25
- package/src/migrations/004_ranklayer.sql +0 -50
- package/src/migrations/005_analytics_base.sql +0 -60
- package/src/migrations/006_analytics_views.sql +0 -68
- package/src/migrations/007_agent_rpcs.sql +0 -119
- package/src/migrations/008_landing_pages.sql +0 -112
- package/src/migrations/index.ts +0 -601
- package/src/presets.ts +0 -119
- package/src/registries.ts +0 -62
- package/src/schema/index.ts +0 -106
- package/src/store.ts +0 -102
- package/src/types.ts +0 -153
- package/src/views/CampaignComposer.tsx +0 -89
- package/src/views/CampaignsView.tsx +0 -59
- package/src/views/ChannelDetailView.tsx +0 -59
- package/src/views/ChannelsView.tsx +0 -46
- package/src/views/FunnelView.tsx +0 -56
- package/src/views/LandingPageComposer.tsx +0 -201
- package/src/views/LandingPagesView.tsx +0 -172
- package/src/views/OverviewView.tsx +0 -24
- package/src/views/SettingsView.tsx +0 -72
- package/src/views/blog/BlogAdminView.tsx +0 -99
- package/src/views/blog/BlogEntityForm.tsx +0 -101
- package/src/views/blog/useEntityCrud.ts +0 -50
- package/src/views/content/ContentPlannerContext.tsx +0 -13
- package/src/views/content/ContentView.tsx +0 -908
- package/src/views/content/PlanBriefView.tsx +0 -268
- package/src/views/content/PostPage.tsx +0 -370
- package/src/views/content/PostSocialPreview.tsx +0 -212
- package/src/views/content/RecordingChecklist.tsx +0 -145
- package/src/views/content/contentBits.tsx +0 -114
- package/src/views/content/contentStore.ts +0 -160
- package/src/views/content/templates.ts +0 -172
- package/src/views/dashboardWidgets.tsx +0 -173
- /package/dist/{format.d.ts → lib/format.d.ts} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React9, { useState, useEffect, useMemo, useCallback } from 'react';
|
|
2
|
-
import { getActiveTenantId, registerTranslations, createSafeDataProvider, getSupabaseClientOptional, useTranslation, useDataChanged, resolveDataProvider } from '@fayz-ai/core';
|
|
3
|
-
import { createPluginContext, PluginSettingsPanel, useModuleNavigation, createViewRouter, ModuleActionBar, SettingsGroup, SelectRow, ToggleRow, PermissionGate, useLimitGuard, useOrganizationStore, CrudFormPage, invalidateLimit, CrudListView } from '@fayz-ai/
|
|
4
|
-
import { defineKpiWidget, defineCustomWidget, defineTableWidget, ModulePage, DataTable, SubpageHeader, Button, Skeleton, ConfirmDialog, ContentSplit, ContentSplitTrigger, Input, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, MarkdownEditor,
|
|
2
|
+
import { connectionSettings, connectionStatus, settingsStatus, createConnectionStore, getActiveTenantId, registerTranslations, createSafeDataProvider, getSupabaseClientOptional, useTranslation, useDataChanged, resolveDataProvider } from '@fayz-ai/core';
|
|
3
|
+
import { createPluginContext, fayzConnectorOAuth, PluginSettingsPanel, useModuleNavigation, createViewRouter, ModuleActionBar, SettingsGroup, SelectRow, ToggleRow, PermissionGate, useLimitGuard, useOrganizationStore, CrudFormPage, invalidateLimit, CrudListView } from '@fayz-ai/admin';
|
|
4
|
+
import { defineKpiWidget, defineCustomWidget, defineTableWidget, ModulePage, DataTable, SubpageHeader, Button, Skeleton, ConfirmDialog, ContentSplit, ContentSplitTrigger, Input, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, MarkdownEditor, DashboardCanvas, SegmentedControl, cn, Modal, ModalContent, ModalHeader, ModalTitle, ModalBody, ModalFooter, Badge, SelectSeparator, Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetBody, SheetFooter, Checkbox, toast, KpiCard as KpiCard$1, Card, CardHeader, CardTitle, CardContent, TableWidget } from '@fayz-ai/ui';
|
|
5
5
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
6
|
-
import {
|
|
6
|
+
import { History, CheckCircle2, AlertCircle, ChevronRight, Users, Target, Percent, DollarSign, Trash2, Plus, ExternalLink, Sparkles, Pencil, Layers, MousePointerClick, NotebookPen, ArrowUpRight, ArrowDownRight, Settings2, Columns3, CalendarDays, List, ChevronLeft, Loader2, ImagePlus, X, Bike, Utensils, DoorOpen, MessageCircle, Instagram, Mail, Globe, Megaphone, Search, Link2, AtSign, Pin, Twitter, Linkedin, Facebook, Youtube, Music2, Repeat2, Heart, BarChart2, Share2, ThumbsUp, Send, MoreHorizontal, Bookmark, GripVertical, Image, Trophy } from 'lucide-react';
|
|
7
7
|
import { createStore } from 'zustand/vanilla';
|
|
8
8
|
|
|
9
9
|
// src/index.ts
|
|
@@ -31,7 +31,7 @@ function OverviewView() {
|
|
|
31
31
|
);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
// src/format.ts
|
|
34
|
+
// src/lib/format.ts
|
|
35
35
|
var DEFAULT_CURRENCY = { code: "USD", locale: "en-US", symbol: "$" };
|
|
36
36
|
function formatCurrency(value, currency) {
|
|
37
37
|
try {
|
|
@@ -2543,7 +2543,7 @@ function useEntityCrud(entity) {
|
|
|
2543
2543
|
const remove = useCallback((id) => provider2.remove(id), [provider2]);
|
|
2544
2544
|
return { items, total, fetch, getById, create, update, remove };
|
|
2545
2545
|
}
|
|
2546
|
-
function PostsList({ navigate }) {
|
|
2546
|
+
function PostsList({ navigate, navTabs }) {
|
|
2547
2547
|
const entity = useMemo(() => buildBlogPostEntity(), []);
|
|
2548
2548
|
const { items, total, fetch } = useEntityCrud(entity);
|
|
2549
2549
|
const [search, setSearch] = useState("");
|
|
@@ -2577,13 +2577,15 @@ function PostsList({ navigate }) {
|
|
|
2577
2577
|
facets,
|
|
2578
2578
|
activeFilters: { status: statusFilter },
|
|
2579
2579
|
onFacetChange: (_field, value) => setStatusFilter(value),
|
|
2580
|
+
navTabs,
|
|
2581
|
+
activeNavTab: "posts",
|
|
2580
2582
|
onNew: () => navigate("blog-new"),
|
|
2581
2583
|
addLabel: "New post",
|
|
2582
2584
|
onRowClick: (row) => navigate(`blog-post:${row.id}`)
|
|
2583
2585
|
}
|
|
2584
2586
|
);
|
|
2585
2587
|
}
|
|
2586
|
-
function CategoriesList({ navigate }) {
|
|
2588
|
+
function CategoriesList({ navigate, navTabs }) {
|
|
2587
2589
|
const entity = useMemo(() => buildBlogCategoryEntity(), []);
|
|
2588
2590
|
const { items, total, fetch } = useEntityCrud(entity);
|
|
2589
2591
|
const [search, setSearch] = useState("");
|
|
@@ -2606,6 +2608,8 @@ function CategoriesList({ navigate }) {
|
|
|
2606
2608
|
search,
|
|
2607
2609
|
onSearchChange: setSearch,
|
|
2608
2610
|
searchPlaceholder: "Search categories\u2026",
|
|
2611
|
+
navTabs,
|
|
2612
|
+
activeNavTab: "categories",
|
|
2609
2613
|
onNew: () => navigate("blog-cat-new"),
|
|
2610
2614
|
addLabel: "New category",
|
|
2611
2615
|
onRowClick: (row) => navigate(`blog-cat:${row.id}`)
|
|
@@ -2615,21 +2619,11 @@ function CategoriesList({ navigate }) {
|
|
|
2615
2619
|
function BlogAdminView({ navigate }) {
|
|
2616
2620
|
const { labels } = useMarketingConfig();
|
|
2617
2621
|
const [lens, setLens] = useState("posts");
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
options: [
|
|
2624
|
-
{ value: "posts", label: labels.blog },
|
|
2625
|
-
{ value: "categories", label: labels.blogCategories }
|
|
2626
|
-
],
|
|
2627
|
-
value: lens,
|
|
2628
|
-
onChange: (v) => setLens(v)
|
|
2629
|
-
}
|
|
2630
|
-
),
|
|
2631
|
-
lens === "posts" ? /* @__PURE__ */ jsx(PostsList, { navigate }) : /* @__PURE__ */ jsx(CategoriesList, { navigate })
|
|
2632
|
-
] });
|
|
2622
|
+
const navTabs = useMemo(() => [
|
|
2623
|
+
{ id: "posts", label: labels.blog, onSelect: () => setLens("posts") },
|
|
2624
|
+
{ id: "categories", label: labels.blogCategories, onSelect: () => setLens("categories") }
|
|
2625
|
+
], [labels.blog, labels.blogCategories]);
|
|
2626
|
+
return lens === "posts" ? /* @__PURE__ */ jsx(PostsList, { navigate, navTabs }) : /* @__PURE__ */ jsx(CategoriesList, { navigate, navTabs });
|
|
2633
2627
|
}
|
|
2634
2628
|
function BlogEntityForm({
|
|
2635
2629
|
entity,
|
|
@@ -2746,7 +2740,7 @@ function buildNav(config, view, navigate) {
|
|
|
2746
2740
|
}
|
|
2747
2741
|
return items;
|
|
2748
2742
|
}
|
|
2749
|
-
function MarketingPage({ config, provider: provider2, store, contentProvider, contentStore }) {
|
|
2743
|
+
function MarketingPage({ config, provider: provider2, store: store3, contentProvider, contentStore }) {
|
|
2750
2744
|
const t = useTranslation();
|
|
2751
2745
|
const { view, direction, navigate } = useModuleNavigation("/marketing", {
|
|
2752
2746
|
overview: 0,
|
|
@@ -2765,11 +2759,11 @@ function MarketingPage({ config, provider: provider2, store, contentProvider, co
|
|
|
2765
2759
|
"blog-cat": 1
|
|
2766
2760
|
}, "overview");
|
|
2767
2761
|
React9.useEffect(() => {
|
|
2768
|
-
void
|
|
2769
|
-
}, [
|
|
2762
|
+
void store3.getState().load();
|
|
2763
|
+
}, [store3]);
|
|
2770
2764
|
useDataChanged({ table: "plg_marketing_campaigns" }, () => {
|
|
2771
|
-
void
|
|
2772
|
-
}, [
|
|
2765
|
+
void store3.getState().refresh();
|
|
2766
|
+
}, [store3]);
|
|
2773
2767
|
const isOverview = view === "overview";
|
|
2774
2768
|
const nav = buildNav(config, view, navigate);
|
|
2775
2769
|
const blogPostEntity = React9.useMemo(() => buildBlogPostEntity(), []);
|
|
@@ -2793,12 +2787,13 @@ function MarketingPage({ config, provider: provider2, store, contentProvider, co
|
|
|
2793
2787
|
{ id: "blog-cat", render: ({ id }) => /* @__PURE__ */ jsx(BlogEntityForm, { entity: blogCategoryEntity, editId: id, parentLabel: config.labels.blogCategories, onDone: () => navigate("blog") }) },
|
|
2794
2788
|
{ id: "overview", render: () => /* @__PURE__ */ jsx(OverviewView, {}) }
|
|
2795
2789
|
], "overview");
|
|
2796
|
-
return /* @__PURE__ */ jsx(MarketingContextProvider, { config, provider: provider2, store, children: /* @__PURE__ */ jsx(ContentPlannerContextProvider, { config, provider: contentProvider, store: contentStore, children: /* @__PURE__ */ jsx(
|
|
2790
|
+
return /* @__PURE__ */ jsx(MarketingContextProvider, { config, provider: provider2, store: store3, children: /* @__PURE__ */ jsx(ContentPlannerContextProvider, { config, provider: contentProvider, store: contentStore, children: /* @__PURE__ */ jsx(
|
|
2797
2791
|
ModulePage,
|
|
2798
2792
|
{
|
|
2799
2793
|
title: config.labels.pageTitle,
|
|
2800
2794
|
subtitle: config.labels.pageSubtitle,
|
|
2801
2795
|
nav,
|
|
2796
|
+
navVariant: config.moduleNav,
|
|
2802
2797
|
showHeader: isOverview,
|
|
2803
2798
|
viewKey: view,
|
|
2804
2799
|
direction,
|
|
@@ -2908,7 +2903,7 @@ var V = {
|
|
|
2908
2903
|
leadTagEvents: "v_marketing_lead_tag_events"
|
|
2909
2904
|
};
|
|
2910
2905
|
|
|
2911
|
-
// src/registries.ts
|
|
2906
|
+
// src/data/registries.ts
|
|
2912
2907
|
var KIND_OPTIONS = [
|
|
2913
2908
|
{ value: "paid", label: "Paid" },
|
|
2914
2909
|
{ value: "organic", label: "Organic" },
|
|
@@ -3090,7 +3085,7 @@ function createMarketingDashboardWidgets(ctx3) {
|
|
|
3090
3085
|
];
|
|
3091
3086
|
}
|
|
3092
3087
|
|
|
3093
|
-
// src/presets.ts
|
|
3088
|
+
// src/lib/presets.ts
|
|
3094
3089
|
var AGENCY = {
|
|
3095
3090
|
conversion: {
|
|
3096
3091
|
id: "won-deal",
|
|
@@ -4598,57 +4593,6 @@ CREATE POLICY plg_marketing_media_insert ON storage.objects FOR INSERT TO authen
|
|
|
4598
4593
|
CREATE POLICY plg_marketing_media_update ON storage.objects FOR UPDATE TO authenticated USING (bucket_id = 'mkt-media');
|
|
4599
4594
|
CREATE POLICY plg_marketing_media_delete ON storage.objects FOR DELETE TO authenticated USING (bucket_id = 'mkt-media');
|
|
4600
4595
|
`;
|
|
4601
|
-
var MIGRATION_004_RANKLAYER = `-- Marketing Plugin: RankLayer integration (SEO content publishing).
|
|
4602
|
-
-- Control-plane state for the RankLayer connector rendered in Marketing \u2192
|
|
4603
|
-
-- Integra\xE7\xF5es. Prefix: plg_marketing_ranklayer_. Scaffold: the real sync
|
|
4604
|
-
-- (edge function + RankLayer API calls) lands via an external PR \u2014 see
|
|
4605
|
-
-- src/integrations/ranklayer/RANKLAYER.md.
|
|
4606
|
-
|
|
4607
|
-
CREATE TABLE IF NOT EXISTS public.plg_marketing_ranklayer_integrations (
|
|
4608
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
4609
|
-
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
4610
|
-
api_key text,
|
|
4611
|
-
site_domain text,
|
|
4612
|
-
active boolean NOT NULL DEFAULT true,
|
|
4613
|
-
last_sync_at timestamptz,
|
|
4614
|
-
created_at timestamptz NOT NULL DEFAULT now(),
|
|
4615
|
-
updated_at timestamptz NOT NULL DEFAULT now()
|
|
4616
|
-
);
|
|
4617
|
-
ALTER TABLE public.plg_marketing_ranklayer_integrations ENABLE ROW LEVEL SECURITY;
|
|
4618
|
-
CREATE UNIQUE INDEX IF NOT EXISTS uq_plg_marketing_ranklayer_integrations_tenant
|
|
4619
|
-
ON public.plg_marketing_ranklayer_integrations(tenant_id);
|
|
4620
|
-
|
|
4621
|
-
CREATE TABLE IF NOT EXISTS public.plg_marketing_ranklayer_sync_log (
|
|
4622
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
4623
|
-
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
4624
|
-
integration_id uuid REFERENCES public.plg_marketing_ranklayer_integrations(id) ON DELETE CASCADE,
|
|
4625
|
-
status text NOT NULL DEFAULT 'success' CHECK (status IN ('success', 'partial', 'error')),
|
|
4626
|
-
message text,
|
|
4627
|
-
fetched integer NOT NULL DEFAULT 0,
|
|
4628
|
-
written integer NOT NULL DEFAULT 0,
|
|
4629
|
-
created_at timestamptz NOT NULL DEFAULT now()
|
|
4630
|
-
);
|
|
4631
|
-
ALTER TABLE public.plg_marketing_ranklayer_sync_log ENABLE ROW LEVEL SECURITY;
|
|
4632
|
-
CREATE INDEX IF NOT EXISTS idx_plg_marketing_ranklayer_sync_log_tenant
|
|
4633
|
-
ON public.plg_marketing_ranklayer_sync_log(tenant_id, created_at DESC);
|
|
4634
|
-
|
|
4635
|
-
-- RLS: authenticated tenant CRUD
|
|
4636
|
-
DROP POLICY IF EXISTS plg_marketing_ranklayer_integrations_select ON public.plg_marketing_ranklayer_integrations;
|
|
4637
|
-
DROP POLICY IF EXISTS plg_marketing_ranklayer_integrations_insert ON public.plg_marketing_ranklayer_integrations;
|
|
4638
|
-
DROP POLICY IF EXISTS plg_marketing_ranklayer_integrations_update ON public.plg_marketing_ranklayer_integrations;
|
|
4639
|
-
DROP POLICY IF EXISTS plg_marketing_ranklayer_integrations_delete ON public.plg_marketing_ranklayer_integrations;
|
|
4640
|
-
CREATE POLICY plg_marketing_ranklayer_integrations_select ON public.plg_marketing_ranklayer_integrations FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
4641
|
-
CREATE POLICY plg_marketing_ranklayer_integrations_insert ON public.plg_marketing_ranklayer_integrations FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
4642
|
-
CREATE POLICY plg_marketing_ranklayer_integrations_update ON public.plg_marketing_ranklayer_integrations FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
4643
|
-
CREATE POLICY plg_marketing_ranklayer_integrations_delete ON public.plg_marketing_ranklayer_integrations FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
4644
|
-
GRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_marketing_ranklayer_integrations TO authenticated;
|
|
4645
|
-
|
|
4646
|
-
DROP POLICY IF EXISTS plg_marketing_ranklayer_sync_log_select ON public.plg_marketing_ranklayer_sync_log;
|
|
4647
|
-
DROP POLICY IF EXISTS plg_marketing_ranklayer_sync_log_insert ON public.plg_marketing_ranklayer_sync_log;
|
|
4648
|
-
CREATE POLICY plg_marketing_ranklayer_sync_log_select ON public.plg_marketing_ranklayer_sync_log FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
4649
|
-
CREATE POLICY plg_marketing_ranklayer_sync_log_insert ON public.plg_marketing_ranklayer_sync_log FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
4650
|
-
GRANT SELECT, INSERT ON public.plg_marketing_ranklayer_sync_log TO authenticated;
|
|
4651
|
-
`;
|
|
4652
4596
|
var MIGRATION_005_ANALYTICS_BASE = `-- Marketing Plugin: analytics base tables (channels + campaigns).
|
|
4653
4597
|
-- Prefix: plg_marketing_
|
|
4654
4598
|
-- Channels are tenant-owned rows lazily seeded from the app's domain preset
|
|
@@ -5012,75 +4956,122 @@ WHERE p.kind = 'lead'
|
|
|
5012
4956
|
AND COALESCE(p.is_active, true);
|
|
5013
4957
|
GRANT SELECT ON public.v_marketing_lead_tag_events TO authenticated;
|
|
5014
4958
|
`;
|
|
5015
|
-
var
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
4959
|
+
var MIGRATION_010_RANKLAYER_ONTO_CONNECTIONS = `-- ============================================================================
|
|
4960
|
+
-- Marketing 010 \u2014 RankLayer stops owning tables.
|
|
4961
|
+
--
|
|
4962
|
+
-- Retires plg_marketing_ranklayer_integrations and plg_marketing_ranklayer_sync_log.
|
|
4963
|
+
-- Both were the generic shape under a private name: tenant, active, one config
|
|
4964
|
+
-- field, last sync \u2014 plus a run history beside it. They are now rows of
|
|
4965
|
+
-- public.plg_connections / public.plg_sync_runs (packages/db 029), reached
|
|
4966
|
+
-- through createConnectionStore('ranklayer'). See FAY-1376.
|
|
4967
|
+
--
|
|
4968
|
+
-- Where each column went:
|
|
4969
|
+
-- integrations.site_domain \u2192 plg_connections.settings->>'siteDomain'
|
|
4970
|
+
-- integrations.active \u2192 plg_connections.active
|
|
4971
|
+
-- integrations.last_sync_at \u2192 plg_connections.last_sync_at
|
|
4972
|
+
-- UNIQUE (tenant_id) \u2192 UNIQUE (tenant_id, connector_id, instance_key),
|
|
4973
|
+
-- with instance_key = '' (RankLayer publishes to
|
|
4974
|
+
-- one site per tenant; a second site is a second
|
|
4975
|
+
-- instance_key the day it is asked for)
|
|
4976
|
+
-- sync_log.created_at \u2192 plg_sync_runs.started_at
|
|
4977
|
+
-- sync_log.status \u2192 plg_sync_runs.status
|
|
4978
|
+
-- sync_log.fetched/written \u2192 plg_sync_runs.fetched/written
|
|
4979
|
+
-- sync_log.message \u2192 plg_sync_runs.error on a failed run, and
|
|
4980
|
+
-- plg_sync_runs.stats->>'message' otherwise
|
|
4981
|
+
-- sync_log.integration_id \u2192 plg_sync_runs.connection_id
|
|
4982
|
+
--
|
|
4983
|
+
-- 004 (which created the pair) and 009 (which dropped its api_key column) are
|
|
4984
|
+
-- deleted rather than edited: their ids stay in the ledger of any pool that ran
|
|
4985
|
+
-- them, and this migration is what removes what they left behind. Nothing is in
|
|
4986
|
+
-- production, so this is a drop, not a data migration.
|
|
4987
|
+
--
|
|
4988
|
+
-- One thing is not merely moved: 004 granted INSERT on the sync log to
|
|
4989
|
+
-- \`authenticated\`, so any member of the tenant could forge publication history.
|
|
4990
|
+
-- plg_sync_runs carries no such grant \u2014 runs are written server-side. The
|
|
4991
|
+
-- exposure closes by moving.
|
|
4992
|
+
-- ============================================================================
|
|
4993
|
+
|
|
4994
|
+
DROP TABLE IF EXISTS public.plg_marketing_ranklayer_sync_log CASCADE;
|
|
4995
|
+
DROP TABLE IF EXISTS public.plg_marketing_ranklayer_integrations CASCADE;
|
|
4996
|
+
`;
|
|
4997
|
+
|
|
4998
|
+
// src/integrations/ranklayer/settings.ts
|
|
4999
|
+
var RANKLAYER_SETTINGS = [
|
|
5000
|
+
{
|
|
5001
|
+
key: "siteDomain",
|
|
5002
|
+
label: "Dom\xEDnio do site",
|
|
5003
|
+
description: "O site em que a RankLayer publica os artigos deste blog.",
|
|
5004
|
+
type: "text",
|
|
5005
|
+
placeholder: "exemplo.com.br",
|
|
5006
|
+
requiredToOperate: true
|
|
5007
|
+
}
|
|
5008
|
+
];
|
|
5009
|
+
|
|
5010
|
+
// src/integrations/ranklayer/data/supabase.ts
|
|
5011
|
+
var RANKLAYER_CONNECTOR_ID = "ranklayer";
|
|
5012
|
+
var INSTANCE_KEY = "";
|
|
5013
|
+
function ranklayerConnectionStore() {
|
|
5014
|
+
return createConnectionStore(RANKLAYER_CONNECTOR_ID, {
|
|
5015
|
+
settings: RANKLAYER_SETTINGS,
|
|
5016
|
+
instanceKey: INSTANCE_KEY
|
|
5017
|
+
});
|
|
5021
5018
|
}
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5019
|
+
var store = ranklayerConnectionStore;
|
|
5020
|
+
function toIntegration(connection) {
|
|
5021
|
+
return {
|
|
5022
|
+
id: connection.id ?? "",
|
|
5023
|
+
siteDomain: connection.settings?.siteDomain || void 0,
|
|
5024
|
+
active: connection.active,
|
|
5025
|
+
lastSyncAt: connection.lastSyncAt ?? void 0
|
|
5026
|
+
};
|
|
5026
5027
|
}
|
|
5027
|
-
function
|
|
5028
|
+
function toLogEntry(run) {
|
|
5028
5029
|
return {
|
|
5029
|
-
id:
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5030
|
+
id: run.id ?? "",
|
|
5031
|
+
createdAt: run.startedAt ?? "",
|
|
5032
|
+
status: run.status === "success" ? "success" : "error",
|
|
5033
|
+
message: run.error ?? run.message ?? void 0,
|
|
5034
|
+
fetched: run.fetched ?? 0,
|
|
5035
|
+
written: run.written ?? 0
|
|
5034
5036
|
};
|
|
5035
5037
|
}
|
|
5036
5038
|
function createRankLayerProvider() {
|
|
5037
5039
|
return {
|
|
5038
5040
|
async getIntegration() {
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5041
|
+
try {
|
|
5042
|
+
const connection = await store().get();
|
|
5043
|
+
return connection ? toIntegration(connection) : null;
|
|
5044
|
+
} catch {
|
|
5045
|
+
return null;
|
|
5046
|
+
}
|
|
5043
5047
|
},
|
|
5044
5048
|
async saveIntegration(values) {
|
|
5045
|
-
const
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
active: true,
|
|
5051
|
-
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
5052
|
-
};
|
|
5053
|
-
const { data, error } = await getClient3().from(INTEGRATIONS).upsert(payload, { onConflict: "tenant_id" }).select("*").single();
|
|
5054
|
-
if (error) throw error;
|
|
5055
|
-
return toIntegration(data);
|
|
5049
|
+
const saved = await store().save({
|
|
5050
|
+
settings: { siteDomain: values.siteDomain ?? null },
|
|
5051
|
+
active: true
|
|
5052
|
+
});
|
|
5053
|
+
return toIntegration(saved);
|
|
5056
5054
|
},
|
|
5055
|
+
/**
|
|
5056
|
+
* Soft, where the old table hard-deleted the row. The publication history
|
|
5057
|
+
* survives a disconnect now — it is attributed to the connection, and a
|
|
5058
|
+
* tenant that reconnects next week should still see what was published.
|
|
5059
|
+
*/
|
|
5057
5060
|
async disconnect() {
|
|
5058
|
-
|
|
5059
|
-
if (!tenantId) return;
|
|
5060
|
-
await getClient3().from(INTEGRATIONS).delete().eq("tenant_id", tenantId);
|
|
5061
|
+
await store().disconnect();
|
|
5061
5062
|
},
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5063
|
+
async getSyncLog() {
|
|
5064
|
+
try {
|
|
5065
|
+
const runs = await store().listRuns({ limit: 20 });
|
|
5066
|
+
return runs.map(toLogEntry);
|
|
5067
|
+
} catch {
|
|
5068
|
+
return [];
|
|
5067
5069
|
}
|
|
5068
|
-
return { ok: true, message: "Chave aceita (valida\xE7\xE3o real pendente \u2014 integra\xE7\xE3o em prepara\xE7\xE3o)." };
|
|
5069
|
-
},
|
|
5070
|
-
async getSyncLog(integrationId) {
|
|
5071
|
-
const { data } = await getClient3().from(SYNC_LOG).select("*").eq("integration_id", integrationId).order("created_at", { ascending: false }).limit(20);
|
|
5072
|
-
return (data ?? []).map((r) => ({
|
|
5073
|
-
id: r.id,
|
|
5074
|
-
createdAt: r.created_at,
|
|
5075
|
-
status: r.status,
|
|
5076
|
-
message: r.message ?? void 0,
|
|
5077
|
-
fetched: r.fetched ?? 0,
|
|
5078
|
-
written: r.written ?? 0
|
|
5079
|
-
}));
|
|
5080
5070
|
}
|
|
5081
5071
|
};
|
|
5082
5072
|
}
|
|
5083
5073
|
var provider = createRankLayerProvider();
|
|
5074
|
+
var store2 = ranklayerConnectionStore();
|
|
5084
5075
|
function RankLayerExtraPanel() {
|
|
5085
5076
|
const [integration, setIntegration] = useState(null);
|
|
5086
5077
|
const [log, setLog] = useState([]);
|
|
@@ -5092,7 +5083,10 @@ function RankLayerExtraPanel() {
|
|
|
5092
5083
|
const found = await provider.getIntegration();
|
|
5093
5084
|
if (cancelled) return;
|
|
5094
5085
|
setIntegration(found);
|
|
5095
|
-
if (found)
|
|
5086
|
+
if (found) {
|
|
5087
|
+
const rows = await provider.getSyncLog();
|
|
5088
|
+
if (!cancelled) setLog(rows);
|
|
5089
|
+
}
|
|
5096
5090
|
} catch {
|
|
5097
5091
|
} finally {
|
|
5098
5092
|
if (!cancelled) setLoaded(true);
|
|
@@ -5102,35 +5096,27 @@ function RankLayerExtraPanel() {
|
|
|
5102
5096
|
cancelled = true;
|
|
5103
5097
|
};
|
|
5104
5098
|
}, []);
|
|
5105
|
-
if (!loaded || !integration) return null;
|
|
5099
|
+
if (!loaded || !integration?.active) return null;
|
|
5106
5100
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4 border-t pt-4", children: [
|
|
5107
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-
|
|
5108
|
-
/* @__PURE__ */ jsx(
|
|
5109
|
-
/* @__PURE__ */
|
|
5110
|
-
/* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold", children: "Sincroniza\xE7\xE3o de conte\xFAdo \u2014 em breve" }),
|
|
5111
|
-
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
5112
|
-
"A publica\xE7\xE3o autom\xE1tica de p\xE1ginas e artigos de SEO com a RankLayer est\xE1 em prepara\xE7\xE3o.",
|
|
5113
|
-
integration.siteDomain ? ` Dom\xEDnio configurado: ${integration.siteDomain}.` : "",
|
|
5114
|
-
" Assim que a integra\xE7\xE3o estiver ativa, o hist\xF3rico de sincroniza\xE7\xF5es aparece aqui."
|
|
5115
|
-
] })
|
|
5116
|
-
] })
|
|
5101
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
5102
|
+
/* @__PURE__ */ jsx(History, { className: "h-4 w-4 text-muted-foreground" }),
|
|
5103
|
+
/* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold", children: "Hist\xF3rico de publica\xE7\xF5es" })
|
|
5117
5104
|
] }),
|
|
5118
|
-
log.length
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5105
|
+
log.length === 0 ? /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
5106
|
+
"Nenhuma publica\xE7\xE3o recebida at\xE9 agora. Quem envia \xE9 a RankLayer",
|
|
5107
|
+
integration.siteDomain ? ` (site ${integration.siteDomain})` : "",
|
|
5108
|
+
": a cada envio, uma linha aparece aqui com a data e quantos artigos foram gravados."
|
|
5109
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "divide-y rounded-md border text-sm", children: log.map((r) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-3 py-2", title: r.message, children: [
|
|
5110
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: new Date(r.createdAt).toLocaleString("pt-BR") }),
|
|
5111
|
+
/* @__PURE__ */ jsxs("span", { className: "ml-auto text-xs", children: [
|
|
5112
|
+
r.written,
|
|
5113
|
+
"/",
|
|
5114
|
+
r.fetched,
|
|
5115
|
+
" publicadas"
|
|
5122
5116
|
] }),
|
|
5123
|
-
/* @__PURE__ */ jsx(
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
r.written,
|
|
5127
|
-
"/",
|
|
5128
|
-
r.fetched,
|
|
5129
|
-
" publicadas"
|
|
5130
|
-
] }),
|
|
5131
|
-
r.status === "success" ? /* @__PURE__ */ jsx(CheckCircle2, { className: "h-3.5 w-3.5 text-success" }) : /* @__PURE__ */ jsx(AlertCircle, { className: "h-3.5 w-3.5 text-warning" })
|
|
5132
|
-
] }, r.id)) })
|
|
5133
|
-
] })
|
|
5117
|
+
r.status === "success" ? /* @__PURE__ */ jsx(CheckCircle2, { className: "h-3.5 w-3.5 text-success" }) : /* @__PURE__ */ jsx(AlertCircle, { className: "h-3.5 w-3.5 text-warning" })
|
|
5118
|
+
] }, r.id)) }),
|
|
5119
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: "A chave que a RankLayer usa para publicar aqui \xE9 emitida na plataforma Fayz e n\xE3o fica guardada neste aplicativo." })
|
|
5134
5120
|
] });
|
|
5135
5121
|
}
|
|
5136
5122
|
var RANKLAYER_MARK = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgdmlld0JveD0iMCAwIDMyIDMyIj48cmVjdCB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHJ4PSI3IiBmaWxsPSIjMUYyNDMxIi8+PHBvbHlnb24gcG9pbnRzPSIxNiw1LjYgMjYuNCwxMS42IDE2LDE3LjYgNS42LDExLjYiIGZpbGw9IiNGRkZGRkYiIHN0cm9rZT0iI0ZGRkZGRiIgc3Ryb2tlLXdpZHRoPSIxLjI1IiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PGcgb3BhY2l0eT0iMC41NSI+PHBvbHlnb24gcG9pbnRzPSI1LjYsMTYuNCAxNiwyMi40IDI2LjQsMTYuNCAyNi40LDE4LjIgMTYsMjQuMiA1LjYsMTguMiIgZmlsbD0iI0ZGRkZGRiIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjEuMjUiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48L2c+PGcgb3BhY2l0eT0iMC4yOCI+PHBvbHlnb24gcG9pbnRzPSI1LjYsMjEuMiAxNiwyNy4yIDI2LjQsMjEuMiAyNi40LDIzIDE2LDI5IDUuNiwyMyIgZmlsbD0iI0ZGRkZGRiIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjEuMjUiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48L2c+PC9zdmc+";
|
|
@@ -5141,22 +5127,84 @@ var ranklayerConnectorDef = {
|
|
|
5141
5127
|
description: "Publica\xE7\xE3o de conte\xFAdo e p\xE1ginas de SEO por servi\xE7o/bairro para ser encontrado no Google e em assistentes de IA.",
|
|
5142
5128
|
icon: "Search",
|
|
5143
5129
|
logoUrl: RANKLAYER_MARK,
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5130
|
+
// OAuth, not a pasted key: what the tenant is doing is linking their RankLayer
|
|
5131
|
+
// account, and a key typed into this app would be a second copy of a secret
|
|
5132
|
+
// the platform already holds — the exact shape migration 009 deleted.
|
|
5133
|
+
authKind: "oauth",
|
|
5134
|
+
// The catalogue files RankLayer under "Analytics & Marketing"
|
|
5135
|
+
// (docs/site/data/integrations.json).
|
|
5136
|
+
category: "analytics",
|
|
5137
|
+
// Inbound because RankLayer reaches us: it writes the articles in. The edit
|
|
5138
|
+
// that goes back is delivered by the Fayz ingress, not by this app.
|
|
5139
|
+
direction: "inbound",
|
|
5140
|
+
docsUrl: "https://ranklayer.app",
|
|
5141
|
+
// The write is grounded: RankLayer's server calls `blog_upsert_external_post`
|
|
5142
|
+
// (plugin-blog migrations §external), which INSERTs/UPDATEs plg_blog_posts and
|
|
5143
|
+
// refuses a credential not scoped to exactly one tenant.
|
|
5144
|
+
//
|
|
5145
|
+
// The read is grounded only as far as the managed automation below — sending
|
|
5146
|
+
// an edit back means reading the article. That push is declared and its
|
|
5147
|
+
// anti-loop guard exists (./outbound.ts `shouldPushToRankLayer`), but nothing
|
|
5148
|
+
// calls it: the ingress that would is the same one that does not exist. Kept
|
|
5149
|
+
// rather than dropped because the automation IS declared, and a permission
|
|
5150
|
+
// list that contradicts the automation next to it is the defect this whole
|
|
5151
|
+
// declaration exists to prevent.
|
|
5152
|
+
permissions: [
|
|
5153
|
+
{
|
|
5154
|
+
kind: "data",
|
|
5155
|
+
resource: "blog_post",
|
|
5156
|
+
actions: ["read", "write"],
|
|
5157
|
+
reason: "Publicar aqui os artigos que a RankLayer escreve, e ler os artigos daqui para mandar de volta as edi\xE7\xF5es que voc\xEA fizer."
|
|
5158
|
+
},
|
|
5159
|
+
{
|
|
5160
|
+
kind: "event",
|
|
5161
|
+
resource: "blog.post.*",
|
|
5162
|
+
actions: ["listen"],
|
|
5163
|
+
reason: "Saber quando um artigo \xE9 criado, editado, publicado ou apagado aqui."
|
|
5155
5164
|
}
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5165
|
+
],
|
|
5166
|
+
/**
|
|
5167
|
+
* No form. The connect step authorizes and nothing else; the site domain is
|
|
5168
|
+
* configuration and lives on the Configurações tab (./settings.ts).
|
|
5169
|
+
*
|
|
5170
|
+
* TODO(ranklayer): the consent screen only opens once the platform has a
|
|
5171
|
+
* `ranklayer` OAuth provider registered — client id/secret and the authorize
|
|
5172
|
+
* and token URLs, plus our redirect URI allow-listed on RankLayer's side. Until
|
|
5173
|
+
* then Conectar answers `config_missing` and the tenant is told, in as many
|
|
5174
|
+
* words, that this is not something they can fix. Nothing in this repository
|
|
5175
|
+
* changes when it lands.
|
|
5176
|
+
*/
|
|
5177
|
+
startOAuth: fayzConnectorOAuth(RANKLAYER_CONNECTOR_ID),
|
|
5178
|
+
settings: RANKLAYER_SETTINGS,
|
|
5179
|
+
subscribesTo: ["blog.post.*"],
|
|
5180
|
+
managedAutomations: [
|
|
5181
|
+
{ eventKey: "blog.post.updated", label: "Enviar para a RankLayer as edi\xE7\xF5es feitas no artigo aqui" }
|
|
5182
|
+
],
|
|
5183
|
+
// Read from the row the list already batched — one request for N connectors.
|
|
5184
|
+
...connectionStatus(
|
|
5185
|
+
RANKLAYER_CONNECTOR_ID,
|
|
5186
|
+
(connection) => {
|
|
5187
|
+
const found = connection ? toIntegration(connection) : null;
|
|
5188
|
+
return {
|
|
5189
|
+
connected: !!found?.active,
|
|
5190
|
+
detail: found?.siteDomain,
|
|
5191
|
+
plane: "sync",
|
|
5192
|
+
// "Conectado, falta configurar" while the domain is empty — which is
|
|
5193
|
+
// true, and is the difference between not ready and broken.
|
|
5194
|
+
...settingsStatus(RANKLAYER_SETTINGS, connection)
|
|
5195
|
+
};
|
|
5196
|
+
},
|
|
5197
|
+
{ store: () => store2 }
|
|
5198
|
+
),
|
|
5199
|
+
...connectionSettings(store2),
|
|
5200
|
+
/**
|
|
5201
|
+
* Called with no values: the credential is not ours to see, and the platform's
|
|
5202
|
+
* redirect route does not write this pool's `plg_connections` (FAY-1388) — the
|
|
5203
|
+
* app writes its own row, or the tenant authorizes, comes back, and reads "not
|
|
5204
|
+
* connected" forever. Bling shipped with the other belief and looped.
|
|
5205
|
+
*/
|
|
5206
|
+
saveConnection: async () => {
|
|
5207
|
+
await store2.save({ active: true });
|
|
5160
5208
|
},
|
|
5161
5209
|
disconnect: () => provider.disconnect(),
|
|
5162
5210
|
ExtraPanel: RankLayerExtraPanel
|
|
@@ -5578,7 +5626,8 @@ function resolveConfig(options) {
|
|
|
5578
5626
|
channels: options?.channels ?? preset.channels,
|
|
5579
5627
|
modules: { ...preset.modules, ...options?.modules },
|
|
5580
5628
|
currency: { ...DEFAULT_CURRENCY, ...options?.currency },
|
|
5581
|
-
labels: { ...DEFAULT_LABELS, ...options?.labels }
|
|
5629
|
+
labels: { ...DEFAULT_LABELS, ...options?.labels },
|
|
5630
|
+
moduleNav: options?.moduleNav
|
|
5582
5631
|
};
|
|
5583
5632
|
return { config, domain };
|
|
5584
5633
|
}
|
|
@@ -5598,8 +5647,8 @@ function createMarketingPlugin(options) {
|
|
|
5598
5647
|
registerTranslations(marketingLocales);
|
|
5599
5648
|
const { config, domain } = resolveConfig(options);
|
|
5600
5649
|
const provider2 = options?.dataProvider ?? createSafeProvider(config, domain, options);
|
|
5601
|
-
const
|
|
5602
|
-
const dashboardWidgets = createMarketingDashboardWidgets({ config, provider: provider2, store });
|
|
5650
|
+
const store3 = createMarketingStore(provider2);
|
|
5651
|
+
const dashboardWidgets = createMarketingDashboardWidgets({ config, provider: provider2, store: store3 });
|
|
5603
5652
|
const contentProvider = options?.contentPlanner?.dataProvider ?? createSafeDataProvider(
|
|
5604
5653
|
() => createSupabaseContentPlannerProvider(),
|
|
5605
5654
|
() => createMockContentPlannerProvider()
|
|
@@ -5607,8 +5656,8 @@ function createMarketingPlugin(options) {
|
|
|
5607
5656
|
const contentStore = createContentPlannerStore(contentProvider);
|
|
5608
5657
|
const PageComponent = () => React9.createElement(
|
|
5609
5658
|
MarketingContextProvider,
|
|
5610
|
-
{ config, provider: provider2, store },
|
|
5611
|
-
React9.createElement(MarketingPage, { config, provider: provider2, store, contentProvider, contentStore })
|
|
5659
|
+
{ config, provider: provider2, store: store3 },
|
|
5660
|
+
React9.createElement(MarketingPage, { config, provider: provider2, store: store3, contentProvider, contentStore })
|
|
5612
5661
|
);
|
|
5613
5662
|
PageComponent.displayName = "MarketingPage";
|
|
5614
5663
|
const marketingRegistries = [
|
|
@@ -5617,7 +5666,7 @@ function createMarketingPlugin(options) {
|
|
|
5617
5666
|
];
|
|
5618
5667
|
const SettingsComponent = () => React9.createElement(
|
|
5619
5668
|
MarketingContextProvider,
|
|
5620
|
-
{ config, provider: provider2, store },
|
|
5669
|
+
{ config, provider: provider2, store: store3 },
|
|
5621
5670
|
React9.createElement(PluginSettingsPanel, {
|
|
5622
5671
|
title: config.labels.settings ?? config.labels.pageTitle,
|
|
5623
5672
|
subtitle: config.labels.pageSubtitle,
|
|
@@ -5788,12 +5837,6 @@ function createMarketingPlugin(options) {
|
|
|
5788
5837
|
sql: MIGRATION_003_RECORDING_OPS,
|
|
5789
5838
|
description: "Recording-day checklist + media asset URL on posts; content-media storage bucket"
|
|
5790
5839
|
},
|
|
5791
|
-
{
|
|
5792
|
-
id: "marketing-004-ranklayer",
|
|
5793
|
-
version: "1.3.0",
|
|
5794
|
-
sql: MIGRATION_004_RANKLAYER,
|
|
5795
|
-
description: "RankLayer connector state: plg_marketing_ranklayer_integrations + _sync_log (tenant RLS)"
|
|
5796
|
-
},
|
|
5797
5840
|
{
|
|
5798
5841
|
id: "marketing-005-analytics-base",
|
|
5799
5842
|
version: "1.4.0",
|
|
@@ -5817,10 +5860,17 @@ function createMarketingPlugin(options) {
|
|
|
5817
5860
|
version: "1.5.0",
|
|
5818
5861
|
sql: MIGRATION_008_LANDING_PAGES,
|
|
5819
5862
|
description: "Sites + landing pages (site + route + lead tag) with tenant RLS, v_marketing_landing_pages and v_marketing_lead_tag_events"
|
|
5863
|
+
},
|
|
5864
|
+
{
|
|
5865
|
+
id: "marketing-010-ranklayer-onto-connections",
|
|
5866
|
+
version: "1.6.0",
|
|
5867
|
+
sql: MIGRATION_010_RANKLAYER_ONTO_CONNECTIONS,
|
|
5868
|
+
description: "Retire plg_marketing_ranklayer_integrations + _sync_log \u2014 the connection and its publication history are rows of plg_connections/plg_sync_runs (packages/db 029)"
|
|
5820
5869
|
}
|
|
5821
5870
|
],
|
|
5822
|
-
// RankLayer SEO connector — renders in the Integrações settings tab.
|
|
5823
|
-
//
|
|
5871
|
+
// RankLayer SEO connector — renders in the Integrações settings tab. A push
|
|
5872
|
+
// connector: RankLayer writes posts in and subscribes to blog.post.* to hear
|
|
5873
|
+
// the edits back. See src/integrations/ranklayer/RANKLAYER.md.
|
|
5824
5874
|
connectors: [ranklayerConnectorDef],
|
|
5825
5875
|
aiTools: [
|
|
5826
5876
|
{
|
|
@@ -5895,6 +5945,9 @@ function createMarketingPlugin(options) {
|
|
|
5895
5945
|
permission: { feature: "marketing", action: "read" }
|
|
5896
5946
|
}
|
|
5897
5947
|
],
|
|
5948
|
+
// Google Meu Negócio sits on the same shelf as RankLayer above: being
|
|
5949
|
+
// found locally, not being measured.
|
|
5950
|
+
integrations: ["google-my-business"],
|
|
5898
5951
|
locales: marketingLocales
|
|
5899
5952
|
};
|
|
5900
5953
|
}
|