@fayz-ai/plugin-marketing 0.10.2 → 0.11.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAO/F,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,WAAW,CAAA;AAClB,OAAO,KAAK,EACV,iBAAiB,EACjB,qBAAqB,EACrB,sBAAsB,EACvB,MAAM,cAAc,CAAA;AAIrB,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AAOjE,OAAO,EAAoB,KAAK,iBAAiB,EAAE,MAAM,cAAc,CAAA;AACvE,OAAO,EAAqB,KAAK,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,eAAe,CAAA;AACpG,OAAO,KAAK,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAclE,MAAM,WAAW,sBAAsB;IACrC,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;IACvC,yEAAyE;IACzE,MAAM,CAAC,EAAE,eAAe,CAAA;IACxB,yEAAyE;IACzE,UAAU,CAAC,EAAE,eAAe,CAAA;IAC5B,uEAAuE;IACvE,QAAQ,CAAC,EAAE,kBAAkB,EAAE,CAAA;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAA;IACzC,QAAQ,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAA;IACrC,MAAM,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAA;IACjC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAA;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,YAAY,CAAC,EAAE,qBAAqB,CAAA;IACpC,6EAA6E;IAC7E,cAAc,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,sBAAsB,CAAA;KAAE,CAAA;IAC1D,iFAAiF;IACjF,kBAAkB,CAAC,EAAE,iBAAiB,EAAE,CAAA;IACxC,4DAA4D;IAC5D,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC,oEAAoE;IACpE,WAAW,CAAC,EAAE,sBAAsB,CAAA;IACpC;6DACyD;IACzD,QAAQ,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CACxE;AA+CD,wBAAgB,qBAAqB,CAAC,OAAO,CAAC,EAAE,sBAAsB,GAAG,cAAc,CA0TtF;AAED,YAAY,EACV,qBAAqB,EACrB,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,cAAc,CAAA;AACrB,YAAY,EACV,kBAAkB,EAClB,eAAe,EACf,QAAQ,EACR,kBAAkB,EAClB,eAAe,GAChB,MAAM,SAAS,CAAA;AAChB,OAAO,EAAE,iBAAiB,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAA;AACvE,OAAO,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAA;AACzD,OAAO,EAAE,+BAA+B,EAAE,MAAM,iBAAiB,CAAA;AACjE,YAAY,EACV,sBAAsB,EACtB,WAAW,EACX,WAAW,EACX,aAAa,EACb,UAAU,EACV,UAAU,EACV,oBAAoB,EACpB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,gCAAgC,EAAE,MAAM,oBAAoB,CAAA;AACrE,OAAO,EAAE,oCAAoC,EAAE,MAAM,wBAAwB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAQ/F,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,WAAW,CAAA;AAClB,OAAO,KAAK,EACV,iBAAiB,EACjB,qBAAqB,EACrB,sBAAsB,EACvB,MAAM,cAAc,CAAA;AAIrB,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AAOjE,OAAO,EAAoB,KAAK,iBAAiB,EAAE,MAAM,cAAc,CAAA;AACvE,OAAO,EAAqB,KAAK,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,eAAe,CAAA;AACpG,OAAO,KAAK,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAclE,MAAM,WAAW,sBAAsB;IACrC,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;IACvC,yEAAyE;IACzE,MAAM,CAAC,EAAE,eAAe,CAAA;IACxB,yEAAyE;IACzE,UAAU,CAAC,EAAE,eAAe,CAAA;IAC5B,uEAAuE;IACvE,QAAQ,CAAC,EAAE,kBAAkB,EAAE,CAAA;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAA;IACzC,QAAQ,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAA;IACrC,MAAM,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAA;IACjC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAA;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,YAAY,CAAC,EAAE,qBAAqB,CAAA;IACpC,6EAA6E;IAC7E,cAAc,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,sBAAsB,CAAA;KAAE,CAAA;IAC1D,iFAAiF;IACjF,kBAAkB,CAAC,EAAE,iBAAiB,EAAE,CAAA;IACxC,4DAA4D;IAC5D,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC,oEAAoE;IACpE,WAAW,CAAC,EAAE,sBAAsB,CAAA;IACpC;6DACyD;IACzD,QAAQ,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CACxE;AA+CD,wBAAgB,qBAAqB,CAAC,OAAO,CAAC,EAAE,sBAAsB,GAAG,cAAc,CAoUtF;AAED,YAAY,EACV,qBAAqB,EACrB,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,cAAc,CAAA;AACrB,YAAY,EACV,kBAAkB,EAClB,eAAe,EACf,QAAQ,EACR,kBAAkB,EAClB,eAAe,GAChB,MAAM,SAAS,CAAA;AAChB,OAAO,EAAE,iBAAiB,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAA;AACvE,OAAO,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAA;AACzD,OAAO,EAAE,+BAA+B,EAAE,MAAM,iBAAiB,CAAA;AACjE,YAAY,EACV,sBAAsB,EACtB,WAAW,EACX,WAAW,EACX,aAAa,EACb,UAAU,EACV,UAAU,EACV,oBAAoB,EACpB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,gCAAgC,EAAE,MAAM,oBAAoB,CAAA;AACrE,OAAO,EAAE,oCAAoC,EAAE,MAAM,wBAAwB,CAAA"}
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, PermissionGate, useLimitGuard, useOrganizationStore, CrudFormPage, 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, SegmentedControl, Badge, SelectSeparator, Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetBody, SheetFooter, 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, 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';
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,25 +337,56 @@ 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
  }
352
354
  ], [t, conversion.labelPlural, currency, deleteCampaign]);
355
+ const createActions = React9.useMemo(() => [
356
+ {
357
+ id: "with-ai",
358
+ label: t("assistant.askAI"),
359
+ description: t("marketing.campaigns.aiHint"),
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({
367
+ hint: t("marketing.campaigns.askPlaceholder"),
368
+ intro: {
369
+ role: "growth",
370
+ persona: t("marketing.campaigns.askPersona"),
371
+ pitch: t("marketing.campaigns.askPitch"),
372
+ description: t("marketing.campaigns.askDescription"),
373
+ suggestions: [0, 1, 2].map((i) => ({ label: t(`marketing.campaigns.askSuggestion.${i}`) }))
374
+ }
375
+ })
376
+ },
377
+ {
378
+ id: "by-hand",
379
+ label: t("marketing.campaigns.newByHand"),
380
+ description: t("marketing.campaigns.newByHandHint"),
381
+ icon: SlidersHorizontal,
382
+ intent: "primary",
383
+ creates: true,
384
+ permission: { feature: "marketing", action: "create" },
385
+ onSelect: () => setComposerOpen(true)
386
+ }
387
+ ], [t]);
353
388
  return /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-6xl space-y-4", children: [
354
- /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsx(PermissionGate, { feature: "marketing", action: "create", children: /* @__PURE__ */ jsxs(Button, { onClick: () => setComposerOpen(true), children: [
355
- /* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-4 w-4" }),
356
- " ",
357
- t("marketing.campaigns.new")
358
- ] }) }) }),
389
+ /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsx(PageActions, { actions: createActions }) }),
359
390
  /* @__PURE__ */ jsx(PageToolbar, { range: /* @__PURE__ */ jsx(RangeTabs, {}) }),
360
391
  /* @__PURE__ */ jsx(DataTable, { columns, data: campaigns, emptyMessage: t("marketing.campaigns.empty") }),
361
392
  /* @__PURE__ */ jsx(CampaignComposer, { open: composerOpen, onClose: () => setComposerOpen(false) })
@@ -464,113 +495,128 @@ function LandingPageComposer({
464
495
  setSaving(false);
465
496
  }
466
497
  }
467
- return /* @__PURE__ */ jsx(Modal, { open, onOpenChange: (o) => {
468
- if (!o) onClose();
469
- }, children: /* @__PURE__ */ jsxs(ModalContent, { size: "md", children: [
470
- /* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsx(ModalTitle, { children: page ? t("marketing.landing.edit") : t("marketing.landing.new") }) }),
471
- /* @__PURE__ */ jsxs(ModalBody, { className: "space-y-4", children: [
472
- /* @__PURE__ */ jsxs("label", { className: "block space-y-1.5", children: [
473
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: t("marketing.landing.site") }),
474
- /* @__PURE__ */ jsxs(Select, { value: siteId, onValueChange: setSiteId, children: [
475
- /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t("marketing.landing.sitePlaceholder") }) }),
476
- /* @__PURE__ */ jsx(SelectContent, { children: sites.map((s) => /* @__PURE__ */ jsx(SelectItem, { value: s.id, children: s.url ? `${s.name} \xB7 ${s.url.replace(/^https?:\/\//, "")}` : s.name }, s.id)) })
477
- ] })
478
- ] }),
479
- /* @__PURE__ */ jsxs("label", { className: "block space-y-1.5", children: [
480
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: t("marketing.landing.name") }),
481
- /* @__PURE__ */ jsx(Input, { value: name, onChange: (e) => setName(e.target.value), placeholder: t("marketing.landing.namePlaceholder") })
482
- ] }),
483
- /* @__PURE__ */ jsxs("label", { className: "block space-y-1.5", children: [
484
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: t("marketing.landing.route") }),
485
- /* @__PURE__ */ jsx(Input, { value: route, onChange: (e) => setRoute(e.target.value), placeholder: "/lp-capilar" }),
486
- site?.url && previewPath ? /* @__PURE__ */ jsxs("span", { className: "block text-xs text-muted-foreground", children: [
487
- site.url.replace(/\/+$/, ""),
488
- previewPath
489
- ] }) : null
490
- ] }),
491
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
492
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: t("marketing.landing.status") }),
493
- /* @__PURE__ */ jsx(
494
- SegmentedControl,
495
- {
496
- value: status,
497
- onChange: (v) => setStatus(v),
498
- options: [
499
- { value: "live", label: t("marketing.landing.statusLive") },
500
- { value: "planned", label: t("marketing.landing.statusPlanned") }
501
- ]
502
- }
503
- )
504
- ] }),
505
- status === "planned" ? /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
506
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
507
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: t("marketing.landing.prompt") }),
508
- site?.editorUrl ? /* @__PURE__ */ jsxs(
509
- "button",
510
- {
511
- type: "button",
512
- onClick: () => window.open(site.editorUrl, "_blank", "noopener,noreferrer"),
513
- className: "inline-flex items-center gap-1 text-xs font-medium text-primary hover:underline",
514
- children: [
515
- /* @__PURE__ */ jsx(Sparkles, { className: "h-3.5 w-3.5" }),
516
- " ",
517
- t("marketing.landing.buildWithFayz")
518
- ]
519
- }
520
- ) : null
521
- ] }),
522
- /* @__PURE__ */ jsx(
523
- "textarea",
524
- {
525
- value: prompt,
526
- onChange: (e) => setPrompt(e.target.value),
527
- rows: 3,
528
- placeholder: t("marketing.landing.promptPlaceholder"),
529
- className: "w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground outline-none focus:ring-2 focus:ring-ring"
530
- }
531
- )
532
- ] }) : null,
533
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
534
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: t("marketing.landing.leadTag") }),
535
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1.5 rounded-md border border-border bg-background px-2 py-2 focus-within:ring-2 focus-within:ring-ring", children: [
536
- options.map((opt) => {
537
- const active = tags.includes(opt.slug);
538
- return /* @__PURE__ */ jsx(
539
- Badge,
540
- {
541
- variant: active ? "default" : "outline",
542
- className: "cursor-pointer text-xs",
543
- onClick: () => toggleTag(opt.slug),
544
- children: opt.label
545
- },
546
- opt.slug
547
- );
548
- }),
549
- /* @__PURE__ */ jsx(
550
- "input",
551
- {
552
- value: newTag,
553
- onChange: (e) => setNewTag(e.target.value),
554
- onKeyDown: (e) => {
555
- if (e.key === "Enter") {
556
- e.preventDefault();
557
- addTag();
498
+ return (
499
+ // Beside the list, not over it: the routes already taken are what you check
500
+ // a new one against, and the dialog covered them.
501
+ /* @__PURE__ */ jsxs(
502
+ RightRailPage,
503
+ {
504
+ title: page ? page.name || t("marketing.landing.edit") : t("marketing.landing.new"),
505
+ open,
506
+ onClose,
507
+ width: "max-w-md",
508
+ children: [
509
+ /* @__PURE__ */ jsxs(SheetBody, { className: "space-y-4", children: [
510
+ /* @__PURE__ */ jsxs("label", { className: "block space-y-1.5", children: [
511
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: t("marketing.landing.site") }),
512
+ /* @__PURE__ */ jsxs(Select, { value: siteId, onValueChange: setSiteId, children: [
513
+ /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t("marketing.landing.sitePlaceholder") }) }),
514
+ /* @__PURE__ */ jsx(SelectContent, { children: sites.map((s) => /* @__PURE__ */ jsx(SelectItem, { value: s.id, children: s.url ? `${s.name} \xB7 ${s.url.replace(/^https?:\/\//, "")}` : s.name }, s.id)) })
515
+ ] })
516
+ ] }),
517
+ /* @__PURE__ */ jsxs("label", { className: "block space-y-1.5", children: [
518
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: t("marketing.landing.name") }),
519
+ /* @__PURE__ */ jsx(Input, { value: name, onChange: (e) => setName(e.target.value), placeholder: t("marketing.landing.namePlaceholder") })
520
+ ] }),
521
+ /* @__PURE__ */ jsxs("label", { className: "block space-y-1.5", children: [
522
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: t("marketing.landing.route") }),
523
+ /* @__PURE__ */ jsx(Input, { value: route, onChange: (e) => setRoute(e.target.value), placeholder: "/lp-capilar" }),
524
+ site?.url && previewPath ? /* @__PURE__ */ jsxs("span", { className: "block text-xs text-muted-foreground", children: [
525
+ site.url.replace(/\/+$/, ""),
526
+ previewPath
527
+ ] }) : null
528
+ ] }),
529
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
530
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: t("marketing.landing.status") }),
531
+ /* @__PURE__ */ jsx(
532
+ SegmentedControl,
533
+ {
534
+ value: status,
535
+ onChange: (v) => setStatus(v),
536
+ options: [
537
+ { value: "live", label: t("marketing.landing.statusLive") },
538
+ { value: "planned", label: t("marketing.landing.statusPlanned") }
539
+ ]
540
+ }
541
+ )
542
+ ] }),
543
+ status === "planned" ? /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
544
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
545
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: t("marketing.landing.prompt") }),
546
+ site?.editorUrl ? /* @__PURE__ */ jsxs(
547
+ "button",
548
+ {
549
+ type: "button",
550
+ onClick: () => window.open(site.editorUrl, "_blank", "noopener,noreferrer"),
551
+ className: "inline-flex items-center gap-1 text-xs font-medium text-primary hover:underline",
552
+ children: [
553
+ /* @__PURE__ */ jsx(Sparkles, { className: "h-3.5 w-3.5" }),
554
+ " ",
555
+ t("marketing.landing.buildWithFayz")
556
+ ]
557
+ }
558
+ ) : null
559
+ ] }),
560
+ /* @__PURE__ */ jsx(
561
+ "textarea",
562
+ {
563
+ value: prompt,
564
+ onChange: (e) => setPrompt(e.target.value),
565
+ rows: 3,
566
+ placeholder: t("marketing.landing.promptPlaceholder"),
567
+ className: "w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground outline-none focus:ring-2 focus:ring-ring"
558
568
  }
559
- },
560
- onBlur: addTag,
561
- placeholder: t("marketing.landing.leadTagPlaceholder"),
562
- className: "min-w-[8rem] flex-1 border-0 bg-transparent px-1 text-xs text-foreground outline-none placeholder:text-muted-foreground"
569
+ )
570
+ ] }) : null,
571
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
572
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: t("marketing.landing.leadTag") }),
573
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1.5 rounded-md border border-border bg-background px-2 py-2 focus-within:ring-2 focus-within:ring-ring", children: [
574
+ options.map((opt) => {
575
+ const active = tags.includes(opt.slug);
576
+ return /* @__PURE__ */ jsx(
577
+ Badge,
578
+ {
579
+ variant: active ? "default" : "outline",
580
+ className: "cursor-pointer text-xs",
581
+ onClick: () => toggleTag(opt.slug),
582
+ children: opt.label
583
+ },
584
+ opt.slug
585
+ );
586
+ }),
587
+ /* @__PURE__ */ jsx(
588
+ "input",
589
+ {
590
+ value: newTag,
591
+ onChange: (e) => setNewTag(e.target.value),
592
+ onKeyDown: (e) => {
593
+ if (e.key === "Enter") {
594
+ e.preventDefault();
595
+ addTag();
596
+ }
597
+ },
598
+ onBlur: addTag,
599
+ placeholder: t("marketing.landing.leadTagPlaceholder"),
600
+ className: "min-w-[8rem] flex-1 border-0 bg-transparent px-1 text-xs text-foreground outline-none placeholder:text-muted-foreground"
601
+ }
602
+ )
603
+ ] }),
604
+ /* @__PURE__ */ jsx("span", { className: "block text-xs text-muted-foreground", children: t("marketing.landing.leadTagHint") })
605
+ ] })
606
+ ] }),
607
+ /* @__PURE__ */ jsx(SheetFooter, { children: /* @__PURE__ */ jsx(
608
+ Button,
609
+ {
610
+ className: "flex-1",
611
+ onClick: handleSave,
612
+ disabled: saving || !siteId || !name.trim() || !route.trim(),
613
+ children: page ? t("marketing.landing.save") : t("marketing.landing.create")
563
614
  }
564
- )
565
- ] }),
566
- /* @__PURE__ */ jsx("span", { className: "block text-xs text-muted-foreground", children: t("marketing.landing.leadTagHint") })
567
- ] })
568
- ] }),
569
- /* @__PURE__ */ jsxs(ModalFooter, { children: [
570
- /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: onClose, children: t("marketing.composer.cancel") }),
571
- /* @__PURE__ */ jsx(Button, { onClick: handleSave, disabled: saving || !siteId || !name.trim() || !route.trim(), children: page ? t("marketing.landing.save") : t("marketing.landing.create") })
572
- ] })
573
- ] }) });
615
+ ) })
616
+ ]
617
+ }
618
+ )
619
+ );
574
620
  }
575
621
  function LandingPagesView() {
576
622
  const t = useTranslation();
@@ -653,52 +699,42 @@ function LandingPagesView() {
653
699
  id: "actions",
654
700
  header: "",
655
701
  enableSorting: false,
656
- cell: ({ row }) => /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-1", children: [
657
- row.original.status === "planned" && row.original.editorUrl ? /* @__PURE__ */ jsx(
658
- "button",
659
- {
660
- onClick: (e) => {
661
- e.stopPropagation();
662
- window.open(row.original.editorUrl, "_blank", "noopener,noreferrer");
663
- },
664
- className: "rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
665
- title: t("marketing.landing.buildWithFayz"),
666
- children: /* @__PURE__ */ jsx(Sparkles, { className: "h-4 w-4" })
667
- }
668
- ) : null,
669
- /* @__PURE__ */ jsx(
670
- "button",
671
- {
672
- onClick: (e) => {
673
- e.stopPropagation();
674
- openEdit(row.original);
675
- },
676
- className: "rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
677
- title: t("marketing.landing.edit"),
678
- children: /* @__PURE__ */ jsx(Pencil, { className: "h-4 w-4" })
679
- }
680
- ),
681
- /* @__PURE__ */ jsx(
682
- "button",
683
- {
684
- onClick: (e) => {
685
- e.stopPropagation();
686
- void deleteLandingPage(row.original.id);
687
- },
688
- className: "rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive",
689
- title: t("marketing.landing.delete"),
690
- children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
691
- }
692
- )
693
- ] })
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
+ )
694
725
  }
695
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]);
696
736
  return /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-6xl space-y-4", children: [
697
- /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsx(PermissionGate, { feature: "marketing", action: "create", children: /* @__PURE__ */ jsxs(Button, { onClick: openNew, children: [
698
- /* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-4 w-4" }),
699
- " ",
700
- t("marketing.landing.new")
701
- ] }) }) }),
737
+ /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsx(PageActions, { actions: createActions }) }),
702
738
  /* @__PURE__ */ jsx(PageToolbar, { range: /* @__PURE__ */ jsx(RangeTabs, {}) }),
703
739
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-4", children: [
704
740
  /* @__PURE__ */ jsx(KpiCard, { icon: Layers, label: t("marketing.metric.pages"), value: String(pages.length) }),
@@ -995,19 +1031,19 @@ function PlanBriefView({ onBack }) {
995
1031
  function PlanBriefSheet({ open, onClose }) {
996
1032
  const t = useTranslation();
997
1033
  const { plan, draft, patch, dirty, saving, save } = useBriefDraft();
998
- return /* @__PURE__ */ jsx(Sheet, { open, onOpenChange: (v) => {
999
- if (!v) onClose();
1000
- }, children: /* @__PURE__ */ jsxs(SheetContent, { width: "max-w-2xl", children: [
1001
- /* @__PURE__ */ jsxs(SheetHeader, { children: [
1002
- /* @__PURE__ */ jsx(SheetTitle, { children: t("marketing.content.brief") }),
1003
- plan && /* @__PURE__ */ jsx(SheetDescription, { children: plan.name })
1004
- ] }),
1005
- /* @__PURE__ */ jsx(SheetBody, { children: draft ? /* @__PURE__ */ jsx(BriefFields, { draft, patch }) : /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "\u2026" }) }),
1006
- /* @__PURE__ */ jsxs(SheetFooter, { children: [
1007
- /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: onClose, disabled: saving, children: t("marketing.content.cancel") }),
1008
- /* @__PURE__ */ jsx(Button, { onClick: () => void save(), disabled: !dirty || saving, children: t("marketing.content.save") })
1009
- ] })
1010
- ] }) });
1034
+ return /* @__PURE__ */ jsxs(
1035
+ RightRailPage,
1036
+ {
1037
+ title: plan?.name ?? t("marketing.content.brief"),
1038
+ open,
1039
+ onClose,
1040
+ width: "max-w-2xl",
1041
+ children: [
1042
+ /* @__PURE__ */ jsx(SheetBody, { children: draft ? /* @__PURE__ */ jsx(BriefFields, { draft, patch }) : /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "\u2026" }) }),
1043
+ /* @__PURE__ */ jsx(SheetFooter, { children: /* @__PURE__ */ jsx(Button, { className: "flex-1", onClick: () => void save(), disabled: !dirty || saving, children: t("marketing.content.save") }) })
1044
+ ]
1045
+ }
1046
+ );
1011
1047
  }
1012
1048
  var DEFAULT_WEEKS = 8;
1013
1049
  var ADD_ACCOUNT_VALUE = "__add-account__";
@@ -2562,6 +2598,21 @@ function PostsList({ navigate, navTabs }) {
2562
2598
  navTabs,
2563
2599
  activeNavTab: "posts",
2564
2600
  onNew: () => navigate("blog-new"),
2601
+ aiCreate: {
2602
+ hint: "Descreva o post\u2026",
2603
+ description: "Descreva o assunto e eu escrevo o rascunho.",
2604
+ intro: {
2605
+ role: "growth",
2606
+ persona: "Redator do blog",
2607
+ pitch: "Diga sobre o que \xE9 o post.",
2608
+ description: "Escrevo t\xEDtulo, resumo e corpo em markdown, com slug e tempo de leitura. Nasce como rascunho \u2014 voc\xEA l\xEA, ajusta e publica quando quiser.",
2609
+ suggestions: [
2610
+ { label: "Um post sobre cuidados p\xF3s-procedimento" },
2611
+ { label: "Cinco d\xFAvidas frequentes dos clientes, respondidas" },
2612
+ { label: "Um post curto para divulgar a promo\xE7\xE3o do m\xEAs" }
2613
+ ]
2614
+ }
2615
+ },
2565
2616
  addLabel: "New post",
2566
2617
  onRowClick: (row) => navigate(`blog-post:${row.id}`)
2567
2618
  }
@@ -2783,8 +2834,7 @@ function MarketingPage({ config, provider: provider2, store: store3, contentProv
2783
2834
  ModuleActionBar,
2784
2835
  {
2785
2836
  quickActions,
2786
- settingsPath: "/settings/marketing",
2787
- settingsLabel: config.labels.pageTitle
2837
+ settingsPath: "/settings/marketing"
2788
2838
  }
2789
2839
  ),
2790
2840
  children: renderView(view)
@@ -4842,7 +4892,7 @@ var MIGRATION_008_LANDING_PAGES = `-- Marketing Plugin: landing pages.
4842
4892
  CREATE TABLE IF NOT EXISTS public.plg_marketing_landing_pages (
4843
4893
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
4844
4894
  tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
4845
- site_id uuid NOT NULL REFERENCES public.fayz_projects(id) ON DELETE CASCADE,
4895
+ site_id uuid NOT NULL,
4846
4896
  name text NOT NULL,
4847
4897
  route text NOT NULL,
4848
4898
  lead_tags text[] NOT NULL DEFAULT '{}',
@@ -4853,6 +4903,31 @@ CREATE TABLE IF NOT EXISTS public.plg_marketing_landing_pages (
4853
4903
  updated_at timestamptz NOT NULL DEFAULT now()
4854
4904
  );
4855
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
+
4856
4931
  -- Self-healing for pools that ran an earlier cut of this migration (single
4857
4932
  -- lead_tag column, no status/prompt). No-op on a fresh table. The views are
4858
4933
  -- rebuilt at the bottom; dropping them first is what lets the column go.
@@ -4915,7 +4990,7 @@ SELECT lp.id, lp.tenant_id, lp.site_id, lp.name, lp.route, lp.lead_tags, lp.stat
4915
4990
  s.name AS site_name, s.preview_url AS site_url, s.app_id, s.fayz_project_id, s.editor_url,
4916
4991
  lp.created_at, lp.updated_at
4917
4992
  FROM public.plg_marketing_landing_pages lp
4918
- 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
4919
4994
  GRANT SELECT ON public.v_marketing_landing_pages TO authenticated;
4920
4995
 
4921
4996
  -- Lead events keyed by tag \u2014 the cross that turns "leads tagged lp-capilar"
@@ -5229,6 +5304,16 @@ var en = {
5229
5304
  "marketing.channels.noCampaigns": "No campaigns yet on this channel.",
5230
5305
  // campaigns
5231
5306
  "marketing.campaigns.subtitle": "All acquisition campaigns and their conversion performance",
5307
+ "marketing.campaigns.aiHint": "Describe the campaign and I set it up.",
5308
+ "marketing.campaigns.newByHand": "Create by hand",
5309
+ "marketing.campaigns.newByHandHint": "Fill in channel, budget and period.",
5310
+ "marketing.campaigns.askPlaceholder": "Describe the campaign\u2026",
5311
+ "marketing.campaigns.askPersona": "Campaign planner",
5312
+ "marketing.campaigns.askPitch": "Tell me the campaign you want to run.",
5313
+ "marketing.campaigns.askDescription": "Goal, channel, how much you mean to spend and for how long \u2014 tell me what you have and I fill the rest sensibly against your history. You confirm before it is created.",
5314
+ "marketing.campaigns.askSuggestion.0": "Instagram campaign for the month, $800",
5315
+ "marketing.campaigns.askSuggestion.1": "Win back clients who have not returned in 90 days",
5316
+ "marketing.campaigns.askSuggestion.2": "Weekend promo on Google",
5232
5317
  "marketing.campaigns.new": "New campaign",
5233
5318
  "marketing.campaigns.empty": "No campaigns yet \u2014 create your first one.",
5234
5319
  "marketing.campaigns.delete": "Delete campaign",
@@ -5423,6 +5508,16 @@ var ptBR = {
5423
5508
  "marketing.channels.noCampaigns": "Nenhuma campanha ainda neste canal.",
5424
5509
  // campaigns
5425
5510
  "marketing.campaigns.subtitle": "Todas as campanhas de aquisi\xE7\xE3o e seu desempenho de convers\xE3o",
5511
+ "marketing.campaigns.aiHint": "Descreva a campanha e eu monto.",
5512
+ "marketing.campaigns.newByHand": "Criar manualmente",
5513
+ "marketing.campaigns.newByHandHint": "Preencher canal, or\xE7amento e per\xEDodo.",
5514
+ "marketing.campaigns.askPlaceholder": "Descreva a campanha\u2026",
5515
+ "marketing.campaigns.askPersona": "Planejador de campanhas",
5516
+ "marketing.campaigns.askPitch": "Diga a campanha que voc\xEA quer rodar.",
5517
+ "marketing.campaigns.askDescription": "Objetivo, canal, quanto pretende gastar e por quanto tempo \u2014 diga o que tiver e eu completo o resto com o que faz sentido para o seu hist\xF3rico. Voc\xEA confirma antes de criar.",
5518
+ "marketing.campaigns.askSuggestion.0": "Campanha de Instagram para o m\xEAs, R$ 800",
5519
+ "marketing.campaigns.askSuggestion.1": "Reativar clientes que n\xE3o voltam h\xE1 90 dias",
5520
+ "marketing.campaigns.askSuggestion.2": "Promo\xE7\xE3o de fim de semana no Google",
5426
5521
  "marketing.campaigns.new": "Nova campanha",
5427
5522
  "marketing.campaigns.empty": "Nenhuma campanha ainda \u2014 crie a primeira.",
5428
5523
  "marketing.campaigns.delete": "Excluir campanha",
@@ -5651,7 +5746,6 @@ function createMarketingPlugin(options) {
5651
5746
  { config, provider: provider2, store: store3 },
5652
5747
  React9.createElement(PluginSettingsPanel, {
5653
5748
  title: config.labels.settings ?? config.labels.pageTitle,
5654
- subtitle: config.labels.pageSubtitle,
5655
5749
  generalSettings: React9.createElement(SettingsView),
5656
5750
  registries: marketingRegistries,
5657
5751
  // Enables the "Integrações" tab (renders manifest connectors for this host).
@@ -5662,6 +5756,7 @@ function createMarketingPlugin(options) {
5662
5756
  SettingsComponent.displayName = "MarketingSettings";
5663
5757
  return {
5664
5758
  id: "marketing",
5759
+ defaultAgentRole: "growth",
5665
5760
  name: options?.navLabel ?? config.labels.pageTitle,
5666
5761
  icon: "Megaphone",
5667
5762
  version: "1.0.0",
@@ -5734,6 +5829,16 @@ function createMarketingPlugin(options) {
5734
5829
  }
5735
5830
  }
5736
5831
  }] : [],
5832
+ // O post do blog é a única entidade daqui que o agente pode ESCREVER: é
5833
+ // um rascunho de texto, o formulário é o mesmo que a pessoa usaria, e a
5834
+ // permissão de create continua sendo cobrada pelas primitivas. Campanha e
5835
+ // post de conteúdo seguem só-leitura — campanha tem RPC própria e o
5836
+ // planner escreve por outro caminho.
5837
+ ...config.modules.blog ? [{
5838
+ key: "marketing:blogPosts",
5839
+ writable: true,
5840
+ entity: buildBlogPostEntity()
5841
+ }] : [],
5737
5842
  ...config.modules.contentPlanner ? [{
5738
5843
  key: "marketing:contentPosts",
5739
5844
  writable: false,