@fayz-ai/plugin-marketing 0.11.0 → 0.11.2

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/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import React9, { useState, useEffect, useMemo, useCallback } from 'react';
2
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, askAI, PermissionGate, QuickActionsButton, useLimitGuard, useOrganizationStore, CrudFormPage, RightRailPage, invalidateLimit, CrudListView } from '@fayz-ai/admin';
4
- import { defineKpiWidget, defineCustomWidget, defineTableWidget, ModulePage, PageToolbar, DataTable, SubpageHeader, PageHeaderActions, Button, Skeleton, ConfirmDialog, ContentSplit, ContentSplitTrigger, Input, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, MarkdownEditor, DashboardCanvas, RangeControl, cn, Modal, ModalContent, ModalHeader, ModalTitle, ModalBody, ModalFooter, SheetBody, SegmentedControl, Badge, SheetFooter, SelectSeparator, Checkbox, toast, KpiCard as KpiCard$1, Card, CardHeader, CardTitle, CardContent, TableWidget } from '@fayz-ai/ui';
3
+ import { createPluginContext, fayzConnectorOAuth, PluginSettingsPanel, useModuleNavigation, createViewRouter, ModuleActionBar, SettingsGroup, SelectRow, ToggleRow, askAI, PageActions, useLimitGuard, useOrganizationStore, CrudFormPage, RightRailPage, invalidateLimit, CrudListView } from '@fayz-ai/admin';
4
+ import { defineKpiWidget, defineCustomWidget, defineTableWidget, ModulePage, PageToolbar, DataTable, SubpageHeader, RowActions, PageHeaderActions, Button, Skeleton, ConfirmDialog, ContentSplit, ContentSplitTrigger, Input, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, MarkdownEditor, DashboardCanvas, RangeControl, cn, Modal, ModalContent, ModalHeader, ModalTitle, ModalBody, ModalFooter, SheetBody, SegmentedControl, Badge, SheetFooter, SelectSeparator, 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 { History, CheckCircle2, AlertCircle, ChevronRight, Users, Target, Percent, DollarSign, Trash2, ExternalLink, Sparkles, Pencil, Plus, 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';
6
+ import { History, CheckCircle2, AlertCircle, ChevronRight, Users, Target, Percent, DollarSign, Trash2, WandSparkles, SlidersHorizontal, ExternalLink, Sparkles, Pencil, Plus, 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
@@ -337,15 +337,17 @@ function CampaignsView() {
337
337
  header: "",
338
338
  enableSorting: false,
339
339
  cell: ({ row }) => /* @__PURE__ */ jsx(
340
- "button",
340
+ RowActions,
341
341
  {
342
- onClick: (e) => {
343
- e.stopPropagation();
344
- void deleteCampaign(row.original.id);
345
- },
346
- className: "rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive",
347
- title: t("marketing.campaigns.delete"),
348
- children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
342
+ actions: [{
343
+ id: "delete",
344
+ label: t("marketing.campaigns.delete"),
345
+ icon: Trash2,
346
+ intent: "danger",
347
+ onSelect: () => {
348
+ void deleteCampaign(row.original.id);
349
+ }
350
+ }]
349
351
  }
350
352
  )
351
353
  }
@@ -355,9 +357,13 @@ function CampaignsView() {
355
357
  id: "with-ai",
356
358
  label: t("assistant.askAI"),
357
359
  description: t("marketing.campaigns.aiHint"),
358
- icon: "WandSparkles",
359
- highlight: true,
360
- action: () => askAI({
360
+ icon: WandSparkles,
361
+ intent: "secondary",
362
+ // Both entries end in a new campaign, so the circle is a "+" even though
363
+ // neither icon is one — a menu of ways to create is still a create.
364
+ creates: true,
365
+ permission: { feature: "marketing", action: "create" },
366
+ onSelect: () => askAI({
361
367
  hint: t("marketing.campaigns.askPlaceholder"),
362
368
  intro: {
363
369
  role: "growth",
@@ -372,19 +378,15 @@ function CampaignsView() {
372
378
  id: "by-hand",
373
379
  label: t("marketing.campaigns.newByHand"),
374
380
  description: t("marketing.campaigns.newByHandHint"),
375
- icon: "SlidersHorizontal",
376
- action: () => setComposerOpen(true)
381
+ icon: SlidersHorizontal,
382
+ intent: "primary",
383
+ creates: true,
384
+ permission: { feature: "marketing", action: "create" },
385
+ onSelect: () => setComposerOpen(true)
377
386
  }
378
387
  ], [t]);
379
388
  return /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-6xl space-y-4", children: [
380
- /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsx(PermissionGate, { feature: "marketing", action: "create", children: /* @__PURE__ */ jsx(
381
- QuickActionsButton,
382
- {
383
- label: t("marketing.campaigns.new"),
384
- variant: "default",
385
- actions: createActions
386
- }
387
- ) }) }),
389
+ /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsx(PageActions, { actions: createActions }) }),
388
390
  /* @__PURE__ */ jsx(PageToolbar, { range: /* @__PURE__ */ jsx(RangeTabs, {}) }),
389
391
  /* @__PURE__ */ jsx(DataTable, { columns, data: campaigns, emptyMessage: t("marketing.campaigns.empty") }),
390
392
  /* @__PURE__ */ jsx(CampaignComposer, { open: composerOpen, onClose: () => setComposerOpen(false) })
@@ -697,52 +699,42 @@ function LandingPagesView() {
697
699
  id: "actions",
698
700
  header: "",
699
701
  enableSorting: false,
700
- cell: ({ row }) => /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-1", children: [
701
- row.original.status === "planned" && row.original.editorUrl ? /* @__PURE__ */ jsx(
702
- "button",
703
- {
704
- onClick: (e) => {
705
- e.stopPropagation();
706
- window.open(row.original.editorUrl, "_blank", "noopener,noreferrer");
707
- },
708
- className: "rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
709
- title: t("marketing.landing.buildWithFayz"),
710
- children: /* @__PURE__ */ jsx(Sparkles, { className: "h-4 w-4" })
711
- }
712
- ) : null,
713
- /* @__PURE__ */ jsx(
714
- "button",
715
- {
716
- onClick: (e) => {
717
- e.stopPropagation();
718
- openEdit(row.original);
719
- },
720
- className: "rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
721
- title: t("marketing.landing.edit"),
722
- children: /* @__PURE__ */ jsx(Pencil, { className: "h-4 w-4" })
723
- }
724
- ),
725
- /* @__PURE__ */ jsx(
726
- "button",
727
- {
728
- onClick: (e) => {
729
- e.stopPropagation();
730
- void deleteLandingPage(row.original.id);
731
- },
732
- className: "rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive",
733
- title: t("marketing.landing.delete"),
734
- children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
735
- }
736
- )
737
- ] })
702
+ cell: ({ row }) => /* @__PURE__ */ jsx(
703
+ RowActions,
704
+ {
705
+ actions: [
706
+ ...row.original.status === "planned" && row.original.editorUrl ? [{
707
+ id: "build",
708
+ label: t("marketing.landing.buildWithFayz"),
709
+ icon: Sparkles,
710
+ onSelect: () => window.open(row.original.editorUrl, "_blank", "noopener,noreferrer")
711
+ }] : [],
712
+ { id: "edit", label: t("marketing.landing.edit"), icon: Pencil, onSelect: () => openEdit(row.original) },
713
+ {
714
+ id: "delete",
715
+ label: t("marketing.landing.delete"),
716
+ icon: Trash2,
717
+ intent: "danger",
718
+ onSelect: () => {
719
+ void deleteLandingPage(row.original.id);
720
+ }
721
+ }
722
+ ]
723
+ }
724
+ )
738
725
  }
739
726
  ], [t, deleteLandingPage]);
727
+ const createActions = React9.useMemo(() => [{
728
+ id: "new-page",
729
+ label: t("marketing.landing.new"),
730
+ icon: Plus,
731
+ intent: "primary",
732
+ creates: true,
733
+ permission: { feature: "marketing", action: "create" },
734
+ onSelect: openNew
735
+ }], [t]);
740
736
  return /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-6xl space-y-4", children: [
741
- /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsx(PermissionGate, { feature: "marketing", action: "create", children: /* @__PURE__ */ jsxs(Button, { onClick: openNew, children: [
742
- /* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-4 w-4" }),
743
- " ",
744
- t("marketing.landing.new")
745
- ] }) }) }),
737
+ /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsx(PageActions, { actions: createActions }) }),
746
738
  /* @__PURE__ */ jsx(PageToolbar, { range: /* @__PURE__ */ jsx(RangeTabs, {}) }),
747
739
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-4", children: [
748
740
  /* @__PURE__ */ jsx(KpiCard, { icon: Layers, label: t("marketing.metric.pages"), value: String(pages.length) }),
@@ -2842,8 +2834,7 @@ function MarketingPage({ config, provider: provider2, store: store3, contentProv
2842
2834
  ModuleActionBar,
2843
2835
  {
2844
2836
  quickActions,
2845
- settingsPath: "/settings/marketing",
2846
- settingsLabel: config.labels.pageTitle
2837
+ settingsPath: "/settings/marketing"
2847
2838
  }
2848
2839
  ),
2849
2840
  children: renderView(view)
@@ -4901,7 +4892,7 @@ var MIGRATION_008_LANDING_PAGES = `-- Marketing Plugin: landing pages.
4901
4892
  CREATE TABLE IF NOT EXISTS public.plg_marketing_landing_pages (
4902
4893
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
4903
4894
  tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
4904
- site_id uuid NOT NULL REFERENCES public.fayz_projects(id) ON DELETE CASCADE,
4895
+ site_id uuid NOT NULL,
4905
4896
  name text NOT NULL,
4906
4897
  route text NOT NULL,
4907
4898
  lead_tags text[] NOT NULL DEFAULT '{}',
@@ -4912,6 +4903,31 @@ CREATE TABLE IF NOT EXISTS public.plg_marketing_landing_pages (
4912
4903
  updated_at timestamptz NOT NULL DEFAULT now()
4913
4904
  );
4914
4905
 
4906
+ -- The site table was renamed fayz_projects -> published_sites (db 137), and on a
4907
+ -- pool mid-migration the old name is 141's compat VIEW \u2014 which a FOREIGN KEY
4908
+ -- cannot reference. So the constraint is declared against whichever name is the
4909
+ -- real TABLE here, instead of being frozen into the CREATE above.
4910
+ DO $$
4911
+ DECLARE v_target text;
4912
+ BEGIN
4913
+ SELECT c.relname INTO v_target
4914
+ FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
4915
+ WHERE n.nspname = 'public' AND c.relkind = 'r'
4916
+ AND c.relname IN ('published_sites', 'fayz_projects')
4917
+ ORDER BY (c.relname = 'published_sites') DESC
4918
+ LIMIT 1;
4919
+
4920
+ IF v_target IS NOT NULL
4921
+ AND NOT EXISTS (SELECT 1 FROM pg_constraint
4922
+ WHERE conrelid = 'public.plg_marketing_landing_pages'::regclass
4923
+ AND conname = 'plg_marketing_landing_pages_site_fk') THEN
4924
+ EXECUTE format(
4925
+ 'ALTER TABLE public.plg_marketing_landing_pages
4926
+ ADD CONSTRAINT plg_marketing_landing_pages_site_fk
4927
+ FOREIGN KEY (site_id) REFERENCES public.%I(id) ON DELETE CASCADE', v_target);
4928
+ END IF;
4929
+ END $$;
4930
+
4915
4931
  -- Self-healing for pools that ran an earlier cut of this migration (single
4916
4932
  -- lead_tag column, no status/prompt). No-op on a fresh table. The views are
4917
4933
  -- rebuilt at the bottom; dropping them first is what lets the column go.
@@ -4974,7 +4990,7 @@ SELECT lp.id, lp.tenant_id, lp.site_id, lp.name, lp.route, lp.lead_tags, lp.stat
4974
4990
  s.name AS site_name, s.preview_url AS site_url, s.app_id, s.fayz_project_id, s.editor_url,
4975
4991
  lp.created_at, lp.updated_at
4976
4992
  FROM public.plg_marketing_landing_pages lp
4977
- JOIN public.fayz_projects s ON s.id = lp.site_id;
4993
+ JOIN public.fayz_projects s ON s.id = lp.site_id; -- alias or table, both read fine
4978
4994
  GRANT SELECT ON public.v_marketing_landing_pages TO authenticated;
4979
4995
 
4980
4996
  -- Lead events keyed by tag \u2014 the cross that turns "leads tagged lp-capilar"