@fayz-ai/plugin-marketing 0.9.0-next.1 → 0.9.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/README.md +3 -3
- package/dist/{MarketingContext.d.ts → context.d.ts} +5 -3
- package/dist/context.d.ts.map +1 -0
- package/dist/data/mock.d.ts +1 -1
- package/dist/data/mock.d.ts.map +1 -1
- package/dist/{registries.d.ts → data/registries.d.ts} +1 -1
- package/dist/data/registries.d.ts.map +1 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -22
- package/dist/index.js.map +1 -1
- package/dist/lib/format.d.ts.map +1 -0
- package/dist/{presets.d.ts → lib/presets.d.ts} +1 -1
- package/dist/lib/presets.d.ts.map +1 -0
- package/dist/{MarketingPage.d.ts → views/MarketingPage.d.ts} +5 -5
- package/dist/views/MarketingPage.d.ts.map +1 -0
- package/dist/views/blog/BlogAdminView.d.ts.map +1 -1
- package/dist/views/content/ContentPlannerContext.d.ts +1 -1
- package/dist/views/content/ContentPlannerContext.d.ts.map +1 -1
- package/dist/views/dashboardWidgets.d.ts +1 -1
- package/dist/views/dashboardWidgets.d.ts.map +1 -1
- package/package.json +6 -9
- package/dist/MarketingContext.d.ts.map +0 -1
- package/dist/MarketingPage.d.ts.map +0 -1
- package/dist/format.d.ts.map +0 -1
- package/dist/presets.d.ts.map +0 -1
- package/dist/registries.d.ts.map +0 -1
- package/src/MarketingContext.tsx +0 -42
- package/src/MarketingPage.tsx +0 -139
- package/src/components/MarketingBits.tsx +0 -131
- package/src/components/icons.tsx +0 -36
- package/src/data/blogEntities.tsx +0 -91
- package/src/data/capability.test.ts +0 -92
- package/src/data/contentMedia.ts +0 -25
- package/src/data/contentMock.ts +0 -253
- package/src/data/contentSupabase.ts +0 -263
- package/src/data/contentTypes.ts +0 -138
- package/src/data/mock.ts +0 -308
- package/src/data/supabase.ts +0 -613
- package/src/data/tables.ts +0 -33
- package/src/data/types.ts +0 -97
- package/src/format.ts +0 -40
- package/src/index.ts +0 -457
- package/src/integrations/ranklayer/RANKLAYER.md +0 -40
- package/src/integrations/ranklayer/connectorDef.tsx +0 -102
- package/src/integrations/ranklayer/data/supabase.ts +0 -106
- package/src/integrations/ranklayer/types.ts +0 -16
- package/src/locales/en.ts +0 -192
- package/src/locales/index.ts +0 -7
- package/src/locales/pt-BR.ts +0 -192
- package/src/migrations/000_plg_rename.sql +0 -15
- package/src/migrations/001_content_planner.sql +0 -94
- package/src/migrations/002_multi_platform.sql +0 -25
- package/src/migrations/003_recording_ops.sql +0 -25
- package/src/migrations/004_ranklayer.sql +0 -50
- package/src/migrations/005_analytics_base.sql +0 -60
- package/src/migrations/006_analytics_views.sql +0 -68
- package/src/migrations/007_agent_rpcs.sql +0 -119
- package/src/migrations/008_landing_pages.sql +0 -112
- package/src/migrations/index.ts +0 -601
- package/src/presets.ts +0 -119
- package/src/registries.ts +0 -62
- package/src/schema/index.ts +0 -106
- package/src/store.ts +0 -102
- package/src/types.ts +0 -153
- package/src/views/CampaignComposer.tsx +0 -89
- package/src/views/CampaignsView.tsx +0 -59
- package/src/views/ChannelDetailView.tsx +0 -59
- package/src/views/ChannelsView.tsx +0 -46
- package/src/views/FunnelView.tsx +0 -56
- package/src/views/LandingPageComposer.tsx +0 -201
- package/src/views/LandingPagesView.tsx +0 -172
- package/src/views/OverviewView.tsx +0 -24
- package/src/views/SettingsView.tsx +0 -72
- package/src/views/blog/BlogAdminView.tsx +0 -99
- package/src/views/blog/BlogEntityForm.tsx +0 -101
- package/src/views/blog/useEntityCrud.ts +0 -50
- package/src/views/content/ContentPlannerContext.tsx +0 -13
- package/src/views/content/ContentView.tsx +0 -908
- package/src/views/content/PlanBriefView.tsx +0 -268
- package/src/views/content/PostPage.tsx +0 -370
- package/src/views/content/PostSocialPreview.tsx +0 -212
- package/src/views/content/RecordingChecklist.tsx +0 -145
- package/src/views/content/contentBits.tsx +0 -114
- package/src/views/content/contentStore.ts +0 -160
- package/src/views/content/templates.ts +0 -172
- package/src/views/dashboardWidgets.tsx +0 -173
- /package/dist/{format.d.ts → lib/format.d.ts} +0 -0
|
@@ -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
|