@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.
Files changed (100) hide show
  1. package/README.md +3 -3
  2. package/dist/{MarketingContext.d.ts → context.d.ts} +5 -3
  3. package/dist/context.d.ts.map +1 -0
  4. package/dist/data/mock.d.ts +1 -1
  5. package/dist/data/mock.d.ts.map +1 -1
  6. package/dist/{registries.d.ts → data/registries.d.ts} +1 -1
  7. package/dist/data/registries.d.ts.map +1 -0
  8. package/dist/index.d.ts +6 -4
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +241 -188
  11. package/dist/index.js.map +1 -1
  12. package/dist/integrations/ranklayer/connectorDef.d.ts +1 -1
  13. package/dist/integrations/ranklayer/connectorDef.d.ts.map +1 -1
  14. package/dist/integrations/ranklayer/data/supabase.d.ts +24 -6
  15. package/dist/integrations/ranklayer/data/supabase.d.ts.map +1 -1
  16. package/dist/integrations/ranklayer/outbound.d.ts +16 -0
  17. package/dist/integrations/ranklayer/outbound.d.ts.map +1 -0
  18. package/dist/integrations/ranklayer/settings.d.ts +12 -0
  19. package/dist/integrations/ranklayer/settings.d.ts.map +1 -0
  20. package/dist/integrations/ranklayer/types.d.ts +0 -1
  21. package/dist/integrations/ranklayer/types.d.ts.map +1 -1
  22. package/dist/lib/format.d.ts.map +1 -0
  23. package/dist/{presets.d.ts → lib/presets.d.ts} +1 -1
  24. package/dist/lib/presets.d.ts.map +1 -0
  25. package/dist/migrations/index.d.ts +1 -1
  26. package/dist/migrations/index.d.ts.map +1 -1
  27. package/dist/{MarketingPage.d.ts → views/MarketingPage.d.ts} +5 -5
  28. package/dist/views/MarketingPage.d.ts.map +1 -0
  29. package/dist/views/blog/BlogAdminView.d.ts.map +1 -1
  30. package/dist/views/content/ContentPlannerContext.d.ts +1 -1
  31. package/dist/views/content/ContentPlannerContext.d.ts.map +1 -1
  32. package/dist/views/dashboardWidgets.d.ts +1 -1
  33. package/dist/views/dashboardWidgets.d.ts.map +1 -1
  34. package/package.json +6 -9
  35. package/dist/MarketingContext.d.ts.map +0 -1
  36. package/dist/MarketingPage.d.ts.map +0 -1
  37. package/dist/format.d.ts.map +0 -1
  38. package/dist/presets.d.ts.map +0 -1
  39. package/dist/registries.d.ts.map +0 -1
  40. package/src/MarketingContext.tsx +0 -42
  41. package/src/MarketingPage.tsx +0 -139
  42. package/src/components/MarketingBits.tsx +0 -131
  43. package/src/components/icons.tsx +0 -36
  44. package/src/data/blogEntities.tsx +0 -91
  45. package/src/data/capability.test.ts +0 -92
  46. package/src/data/contentMedia.ts +0 -25
  47. package/src/data/contentMock.ts +0 -253
  48. package/src/data/contentSupabase.ts +0 -263
  49. package/src/data/contentTypes.ts +0 -138
  50. package/src/data/mock.ts +0 -308
  51. package/src/data/supabase.ts +0 -613
  52. package/src/data/tables.ts +0 -33
  53. package/src/data/types.ts +0 -97
  54. package/src/format.ts +0 -40
  55. package/src/index.ts +0 -457
  56. package/src/integrations/ranklayer/RANKLAYER.md +0 -40
  57. package/src/integrations/ranklayer/connectorDef.tsx +0 -102
  58. package/src/integrations/ranklayer/data/supabase.ts +0 -106
  59. package/src/integrations/ranklayer/types.ts +0 -16
  60. package/src/locales/en.ts +0 -192
  61. package/src/locales/index.ts +0 -7
  62. package/src/locales/pt-BR.ts +0 -192
  63. package/src/migrations/000_plg_rename.sql +0 -15
  64. package/src/migrations/001_content_planner.sql +0 -94
  65. package/src/migrations/002_multi_platform.sql +0 -25
  66. package/src/migrations/003_recording_ops.sql +0 -25
  67. package/src/migrations/004_ranklayer.sql +0 -50
  68. package/src/migrations/005_analytics_base.sql +0 -60
  69. package/src/migrations/006_analytics_views.sql +0 -68
  70. package/src/migrations/007_agent_rpcs.sql +0 -119
  71. package/src/migrations/008_landing_pages.sql +0 -112
  72. package/src/migrations/index.ts +0 -601
  73. package/src/presets.ts +0 -119
  74. package/src/registries.ts +0 -62
  75. package/src/schema/index.ts +0 -106
  76. package/src/store.ts +0 -102
  77. package/src/types.ts +0 -153
  78. package/src/views/CampaignComposer.tsx +0 -89
  79. package/src/views/CampaignsView.tsx +0 -59
  80. package/src/views/ChannelDetailView.tsx +0 -59
  81. package/src/views/ChannelsView.tsx +0 -46
  82. package/src/views/FunnelView.tsx +0 -56
  83. package/src/views/LandingPageComposer.tsx +0 -201
  84. package/src/views/LandingPagesView.tsx +0 -172
  85. package/src/views/OverviewView.tsx +0 -24
  86. package/src/views/SettingsView.tsx +0 -72
  87. package/src/views/blog/BlogAdminView.tsx +0 -99
  88. package/src/views/blog/BlogEntityForm.tsx +0 -101
  89. package/src/views/blog/useEntityCrud.ts +0 -50
  90. package/src/views/content/ContentPlannerContext.tsx +0 -13
  91. package/src/views/content/ContentView.tsx +0 -908
  92. package/src/views/content/PlanBriefView.tsx +0 -268
  93. package/src/views/content/PostPage.tsx +0 -370
  94. package/src/views/content/PostSocialPreview.tsx +0 -212
  95. package/src/views/content/RecordingChecklist.tsx +0 -145
  96. package/src/views/content/contentBits.tsx +0 -114
  97. package/src/views/content/contentStore.ts +0 -160
  98. package/src/views/content/templates.ts +0 -172
  99. package/src/views/dashboardWidgets.tsx +0 -173
  100. /package/dist/{format.d.ts → lib/format.d.ts} +0 -0
@@ -1,172 +0,0 @@
1
- import type { PostFormat } from '../../data/contentTypes'
2
-
3
- // ---------------------------------------------------------------------------
4
- // Default markdown scaffolds inserted when a post's page is still empty.
5
- // The section headers follow the script format clinics/creators actually use
6
- // (hook-first reels, on-screen text + spoken line per take). Body text is user
7
- // content, so it ships in pt-BR — the product's first content-planner users.
8
- // ---------------------------------------------------------------------------
9
-
10
- const reel = (title: string) => `# ${title}
11
-
12
- **TEMA:** ${title.toUpperCase()}
13
- **Formato:** Reel · **Duração:** 45 a 60 segundos
14
-
15
- ---
16
-
17
- ## TAKE 1 – GANCHO (0s a 5s)
18
-
19
- *Direção de cena: olhando para a câmera, energia alta.*
20
-
21
- **Texto na tela:** …
22
-
23
- **Fala:** "…"
24
-
25
- ---
26
-
27
- ## TAKE 2 – DESENVOLVIMENTO (5s a 40s)
28
-
29
- *Direção de cena: …*
30
-
31
- **Texto na tela:** …
32
-
33
- **Fala:** "…"
34
-
35
- ---
36
-
37
- ## TAKE 3 – CTA (40s a 60s)
38
-
39
- **Fala:** "…"
40
-
41
- **CTA:** …
42
- `
43
-
44
- const staticPost = (title: string) => `# ${title}
45
-
46
- **TEMA:** ${title.toUpperCase()}
47
- **Formato:** Estático
48
-
49
- ## Texto da arte
50
-
51
- …
52
-
53
- ## Legenda
54
-
55
- …
56
-
57
- **CTA:** …
58
- `
59
-
60
- const carousel = (title: string) => `# ${title}
61
-
62
- **TEMA:** ${title.toUpperCase()}
63
- **Formato:** Carrossel
64
-
65
- ## Slide 1 — Capa (gancho)
66
-
67
- …
68
-
69
- ## Slide 2
70
-
71
- …
72
-
73
- ## Slide 3
74
-
75
- …
76
-
77
- ## Último slide — CTA
78
-
79
- …
80
- `
81
-
82
- const story = (title: string) => `# ${title}
83
-
84
- **TEMA:** ${title.toUpperCase()}
85
- **Formato:** Story
86
-
87
- ## Frame 1
88
-
89
- …
90
-
91
- ## Frame 2
92
-
93
- …
94
-
95
- ## Frame final — CTA
96
-
97
- *Sticker de link / caixa de pergunta / enquete.*
98
-
99
- …
100
- `
101
-
102
- const video = (title: string) => `# ${title}
103
-
104
- **TEMA:** ${title.toUpperCase()}
105
- **Formato:** Vídeo longo (YouTube) · **Duração:** 8 a 12 minutos
106
-
107
- ## Gancho (0s a 30s)
108
-
109
- …
110
-
111
- ## Roteiro por blocos
112
-
113
- ### Bloco 1 — Contexto
114
-
115
- …
116
-
117
- ### Bloco 2 — Desenvolvimento
118
-
119
- …
120
-
121
- ### Bloco 3 — Fechamento
122
-
123
- …
124
-
125
- **CTA:** inscreva-se / comente / link na descrição
126
- `
127
-
128
- const live = (title: string) => `# ${title}
129
-
130
- **TEMA:** ${title.toUpperCase()}
131
- **Formato:** Live
132
-
133
- ## Pauta
134
-
135
- 1. Abertura e aquecimento (5 min)
136
- 2. …
137
- 3. Perguntas e respostas (15 min)
138
-
139
- ## Avisos / CTAs durante a live
140
-
141
- …
142
- `
143
-
144
- const article = (title: string) => `# ${title}
145
-
146
- **TEMA:** ${title.toUpperCase()}
147
- **Formato:** Artigo / Newsletter
148
-
149
- ## Título de trabalho
150
-
151
- …
152
-
153
- ## Estrutura
154
-
155
- - Abertura (dor / promessa)
156
- - Desenvolvimento em 3 pontos
157
- - Conclusão + CTA
158
-
159
- ## Rascunho
160
-
161
- …
162
- `
163
-
164
- export const POST_TEMPLATES: Record<PostFormat, (title: string) => string> = {
165
- reel,
166
- static: staticPost,
167
- carousel,
168
- story,
169
- video,
170
- live,
171
- article,
172
- }
@@ -1,173 +0,0 @@
1
- import React, { useEffect } from 'react'
2
- import { Target, Percent, DollarSign, Trophy } from 'lucide-react'
3
- import type { ColumnDef } from '@tanstack/react-table'
4
- import type { StoreApi } from 'zustand'
5
- import type { DashboardWidgetDef } from '@fayz-ai/core'
6
- import { useTranslation } from '@fayz-ai/core'
7
- import {
8
- KpiCard, TableWidget, Card, CardHeader, CardTitle, CardContent,
9
- defineKpiWidget, defineCustomWidget, defineTableWidget,
10
- } from '@fayz-ai/ui'
11
- import {
12
- MarketingContextProvider, useMarketingConfig, useMarketingStore, useChannelLookup,
13
- type ResolvedMarketingConfig,
14
- } from '../MarketingContext'
15
- import type { MarketingDataProvider } from '../data/types'
16
- import type { MarketingUIState } from '../store'
17
- import { formatCurrency, formatNumber, formatPercent, formatCompact } from '../format'
18
- import { ProportionBar, ChannelCell, StatusBadge } from '../components/MarketingBits'
19
- import type { Campaign } from '../types'
20
-
21
- // Ensure marketing data is loaded — covers the global home, where MarketingPage
22
- // (which normally triggers load) is not mounted. Guarded to avoid refetch storms.
23
- function useEnsureMarketingData() {
24
- const overview = useMarketingStore((s) => s.overview)
25
- const loading = useMarketingStore((s) => s.loading)
26
- const load = useMarketingStore((s) => s.load)
27
- useEffect(() => { if (!overview && !loading) void load() }, [])
28
- }
29
-
30
- // ---------------------------------------------------------------------------
31
- // KPI widgets (shared KpiCard — trend chip from current/previous)
32
- // ---------------------------------------------------------------------------
33
-
34
- function ConversionsKpi() {
35
- const { conversion } = useMarketingConfig()
36
- const overview = useMarketingStore((s) => s.overview)
37
- useEnsureMarketingData()
38
- return (
39
- <KpiCard
40
- icon={Target} label={conversion.labelPlural}
41
- value={formatNumber(overview?.conversions ?? 0)}
42
- current={overview?.conversions} previous={overview?.conversionsPrev}
43
- />
44
- )
45
- }
46
-
47
- function ConversionRateKpi() {
48
- const t = useTranslation()
49
- const overview = useMarketingStore((s) => s.overview)
50
- useEnsureMarketingData()
51
- return (
52
- <KpiCard
53
- icon={Percent} label={t('marketing.metric.conversionRate')}
54
- value={formatPercent(overview?.cvr ?? 0)}
55
- current={overview?.cvr} previous={overview?.cvrPrev}
56
- />
57
- )
58
- }
59
-
60
- function CpaKpi() {
61
- const t = useTranslation()
62
- const { currency } = useMarketingConfig()
63
- const overview = useMarketingStore((s) => s.overview)
64
- useEnsureMarketingData()
65
- return (
66
- <KpiCard
67
- icon={DollarSign} label={t('marketing.metric.cpa')}
68
- value={overview && overview.cpa > 0 ? formatCurrency(overview.cpa, currency) : '—'}
69
- sub={`${formatCurrency(overview?.spend ?? 0, currency)} ${t('marketing.metric.spend')}`}
70
- />
71
- )
72
- }
73
-
74
- function TopChannelKpi() {
75
- const t = useTranslation()
76
- const { conversion, currency } = useMarketingConfig()
77
- const overview = useMarketingStore((s) => s.overview)
78
- const lookup = useChannelLookup()
79
- useEnsureMarketingData()
80
- return (
81
- <KpiCard
82
- icon={Trophy} label={t('marketing.metric.topChannel')}
83
- value={overview?.topChannelId ? lookup.get(overview.topChannelId)?.label ?? '—' : '—'}
84
- sub={`${conversion.valueLabel}: ${formatCurrency(overview?.value ?? 0, currency)}`}
85
- />
86
- )
87
- }
88
-
89
- // ---------------------------------------------------------------------------
90
- // Channel mix (custom — reuses the existing ProportionBar + channel icons)
91
- // ---------------------------------------------------------------------------
92
-
93
- function ChannelMixPanel() {
94
- const t = useTranslation()
95
- const { conversion } = useMarketingConfig()
96
- const overview = useMarketingStore((s) => s.overview)
97
- useEnsureMarketingData()
98
- const mix = overview?.channelMix ?? []
99
- const maxMix = Math.max(...mix.map((m) => m.conversions), 1)
100
- return (
101
- <Card>
102
- <CardHeader>
103
- <CardTitle>{conversion.labelPlural} {t('marketing.overview.byChannelSuffix')}</CardTitle>
104
- </CardHeader>
105
- <CardContent className="space-y-2.5">
106
- {mix.map((m) => (
107
- <ProportionBar
108
- key={m.channelId}
109
- value={m.conversions}
110
- max={maxMix}
111
- color="#6366f1"
112
- label={<ChannelCell channelId={m.channelId} />}
113
- right={<span>{formatNumber(m.conversions)}</span>}
114
- />
115
- ))}
116
- </CardContent>
117
- </Card>
118
- )
119
- }
120
-
121
- // ---------------------------------------------------------------------------
122
- // Recent campaigns table
123
- // ---------------------------------------------------------------------------
124
-
125
- function CampaignsTable() {
126
- const t = useTranslation()
127
- const { conversion } = useMarketingConfig()
128
- const campaigns = useMarketingStore((s) => s.campaigns)
129
- useEnsureMarketingData()
130
- const columns: ColumnDef<Campaign, unknown>[] = [
131
- { accessorKey: 'name', header: t('marketing.col.campaign'), cell: ({ getValue }) => <span className="font-medium text-foreground">{getValue() as string}</span> },
132
- { accessorKey: 'channelId', header: t('marketing.col.channel'), cell: ({ getValue }) => <ChannelCell channelId={getValue() as string} /> },
133
- { accessorKey: 'conversions', header: conversion.labelPlural, cell: ({ getValue }) => <span className="text-muted-foreground">{formatCompact(getValue() as number)}</span> },
134
- { id: 'cvr', accessorFn: (c) => (c.reach > 0 ? (c.conversions / c.reach) * 100 : 0), header: t('marketing.col.cvr'), cell: ({ getValue }) => <span className="text-muted-foreground">{formatPercent(getValue() as number)}</span> },
135
- { accessorKey: 'status', header: t('marketing.col.status'), cell: ({ getValue }) => <StatusBadge status={getValue() as Campaign['status']} /> },
136
- ]
137
- const recent = campaigns.filter((c) => c.status !== 'draft').slice(0, 5)
138
- return <TableWidget title={t('marketing.overview.recent')} icon="Megaphone" columns={columns} data={recent} emptyMessage={t('marketing.campaigns.empty')} />
139
- }
140
-
141
- // ---------------------------------------------------------------------------
142
- // Registry builder — wraps each widget in MarketingContextProvider so it renders
143
- // on the global home (no MarketingPage around it) and the marketing plugin-home.
144
- // ---------------------------------------------------------------------------
145
-
146
- export function createMarketingDashboardWidgets(ctx: {
147
- config: ResolvedMarketingConfig
148
- provider: MarketingDataProvider
149
- store: StoreApi<MarketingUIState>
150
- }): DashboardWidgetDef[] {
151
- const withCtx = (Inner: React.ComponentType): React.ComponentType<unknown> => {
152
- const Wrapped = () => (
153
- <MarketingContextProvider config={ctx.config} provider={ctx.provider} store={ctx.store}>
154
- <Inner />
155
- </MarketingContextProvider>
156
- )
157
- Wrapped.displayName = `MarketingWidget(${Inner.displayName ?? Inner.name})`
158
- return Wrapped
159
- }
160
-
161
- return [
162
- // Conversions is marketing's headline KPI on the global home; the rest are
163
- // home-hidden by default (shown on the marketing plugin-home, addable via Customize).
164
- defineKpiWidget({ id: 'marketing.kpi.conversions', title: 'marketing.metric.conversions', domain: 'marketing', defaultOrder: 0, component: withCtx(ConversionsKpi) }),
165
- defineKpiWidget({ id: 'marketing.kpi.conversion-rate', title: 'marketing.metric.conversionRate', domain: 'marketing', defaultOrder: 1, defaultVisible: false, component: withCtx(ConversionRateKpi) }),
166
- defineKpiWidget({ id: 'marketing.kpi.cpa', title: 'marketing.metric.cpa', domain: 'marketing', defaultOrder: 2, defaultVisible: false, component: withCtx(CpaKpi) }),
167
- defineKpiWidget({ id: 'marketing.kpi.top-channel', title: 'marketing.metric.topChannel', domain: 'marketing', defaultOrder: 3, defaultVisible: false, component: withCtx(TopChannelKpi) }),
168
- // Channel-mix + campaigns default to the marketing plugin-home only; the
169
- // global home stays a clean cross-domain KPI overview.
170
- defineCustomWidget({ id: 'marketing.panel.channel-mix', title: 'marketing.overview.byChannelSuffix', domain: 'marketing', span: 4, defaultOrder: 10, surfaces: ['plugin-home'], component: withCtx(ChannelMixPanel) }),
171
- defineTableWidget({ id: 'marketing.table.recent-campaigns', title: 'marketing.overview.recent', domain: 'marketing', span: 4, defaultOrder: 20, surfaces: ['plugin-home'], component: withCtx(CampaignsTable) }),
172
- ]
173
- }
File without changes