@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.
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +287 -182
- package/dist/index.js.map +1 -1
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/pt-BR.d.ts.map +1 -1
- package/dist/migrations/index.d.ts +2 -1
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/views/CampaignsView.d.ts.map +1 -1
- package/dist/views/LandingPageComposer.d.ts.map +1 -1
- package/dist/views/LandingPagesView.d.ts.map +1 -1
- package/dist/views/MarketingPage.d.ts.map +1 -1
- package/dist/views/blog/BlogAdminView.d.ts.map +1 -1
- package/dist/views/content/PlanBriefView.d.ts +2 -2
- package/dist/views/content/PlanBriefView.d.ts.map +1 -1
- package/package.json +7 -6
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
340
|
+
RowActions,
|
|
341
341
|
{
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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(
|
|
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
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
/* @__PURE__ */
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
{
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
className: "
|
|
514
|
-
|
|
515
|
-
/* @__PURE__ */
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
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
|
-
|
|
561
|
-
|
|
562
|
-
|
|
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
|
-
|
|
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__ */
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
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(
|
|
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__ */
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
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
|
|
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,
|