@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.
Files changed (88) 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 +4 -4
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +17 -22
  11. package/dist/index.js.map +1 -1
  12. package/dist/lib/format.d.ts.map +1 -0
  13. package/dist/{presets.d.ts → lib/presets.d.ts} +1 -1
  14. package/dist/lib/presets.d.ts.map +1 -0
  15. package/dist/{MarketingPage.d.ts → views/MarketingPage.d.ts} +5 -5
  16. package/dist/views/MarketingPage.d.ts.map +1 -0
  17. package/dist/views/blog/BlogAdminView.d.ts.map +1 -1
  18. package/dist/views/content/ContentPlannerContext.d.ts +1 -1
  19. package/dist/views/content/ContentPlannerContext.d.ts.map +1 -1
  20. package/dist/views/dashboardWidgets.d.ts +1 -1
  21. package/dist/views/dashboardWidgets.d.ts.map +1 -1
  22. package/package.json +6 -9
  23. package/dist/MarketingContext.d.ts.map +0 -1
  24. package/dist/MarketingPage.d.ts.map +0 -1
  25. package/dist/format.d.ts.map +0 -1
  26. package/dist/presets.d.ts.map +0 -1
  27. package/dist/registries.d.ts.map +0 -1
  28. package/src/MarketingContext.tsx +0 -42
  29. package/src/MarketingPage.tsx +0 -139
  30. package/src/components/MarketingBits.tsx +0 -131
  31. package/src/components/icons.tsx +0 -36
  32. package/src/data/blogEntities.tsx +0 -91
  33. package/src/data/capability.test.ts +0 -92
  34. package/src/data/contentMedia.ts +0 -25
  35. package/src/data/contentMock.ts +0 -253
  36. package/src/data/contentSupabase.ts +0 -263
  37. package/src/data/contentTypes.ts +0 -138
  38. package/src/data/mock.ts +0 -308
  39. package/src/data/supabase.ts +0 -613
  40. package/src/data/tables.ts +0 -33
  41. package/src/data/types.ts +0 -97
  42. package/src/format.ts +0 -40
  43. package/src/index.ts +0 -457
  44. package/src/integrations/ranklayer/RANKLAYER.md +0 -40
  45. package/src/integrations/ranklayer/connectorDef.tsx +0 -102
  46. package/src/integrations/ranklayer/data/supabase.ts +0 -106
  47. package/src/integrations/ranklayer/types.ts +0 -16
  48. package/src/locales/en.ts +0 -192
  49. package/src/locales/index.ts +0 -7
  50. package/src/locales/pt-BR.ts +0 -192
  51. package/src/migrations/000_plg_rename.sql +0 -15
  52. package/src/migrations/001_content_planner.sql +0 -94
  53. package/src/migrations/002_multi_platform.sql +0 -25
  54. package/src/migrations/003_recording_ops.sql +0 -25
  55. package/src/migrations/004_ranklayer.sql +0 -50
  56. package/src/migrations/005_analytics_base.sql +0 -60
  57. package/src/migrations/006_analytics_views.sql +0 -68
  58. package/src/migrations/007_agent_rpcs.sql +0 -119
  59. package/src/migrations/008_landing_pages.sql +0 -112
  60. package/src/migrations/index.ts +0 -601
  61. package/src/presets.ts +0 -119
  62. package/src/registries.ts +0 -62
  63. package/src/schema/index.ts +0 -106
  64. package/src/store.ts +0 -102
  65. package/src/types.ts +0 -153
  66. package/src/views/CampaignComposer.tsx +0 -89
  67. package/src/views/CampaignsView.tsx +0 -59
  68. package/src/views/ChannelDetailView.tsx +0 -59
  69. package/src/views/ChannelsView.tsx +0 -46
  70. package/src/views/FunnelView.tsx +0 -56
  71. package/src/views/LandingPageComposer.tsx +0 -201
  72. package/src/views/LandingPagesView.tsx +0 -172
  73. package/src/views/OverviewView.tsx +0 -24
  74. package/src/views/SettingsView.tsx +0 -72
  75. package/src/views/blog/BlogAdminView.tsx +0 -99
  76. package/src/views/blog/BlogEntityForm.tsx +0 -101
  77. package/src/views/blog/useEntityCrud.ts +0 -50
  78. package/src/views/content/ContentPlannerContext.tsx +0 -13
  79. package/src/views/content/ContentView.tsx +0 -908
  80. package/src/views/content/PlanBriefView.tsx +0 -268
  81. package/src/views/content/PostPage.tsx +0 -370
  82. package/src/views/content/PostSocialPreview.tsx +0 -212
  83. package/src/views/content/RecordingChecklist.tsx +0 -145
  84. package/src/views/content/contentBits.tsx +0 -114
  85. package/src/views/content/contentStore.ts +0 -160
  86. package/src/views/content/templates.ts +0 -172
  87. package/src/views/dashboardWidgets.tsx +0 -173
  88. /package/dist/{format.d.ts → lib/format.d.ts} +0 -0
@@ -1,138 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // Content planner — domain types + provider seam. Deliberately separate from
3
- // MarketingDataProvider: analytics is mock-until-bridges, while the planner is
4
- // a real CRUD surface (Supabase-backed via createSafeDataProvider). The
5
- // interface must stay methods-only (the safe-provider Proxy turns every
6
- // property access into a call).
7
- // ---------------------------------------------------------------------------
8
-
9
- export type PostFormat = 'reel' | 'static' | 'carousel' | 'story' | 'video' | 'live' | 'article'
10
-
11
- /** Platforms an account can publish to. UI-driven list (the DB stores plain
12
- * text[]), so growing it is a one-line change here. */
13
- export const PLATFORMS = [
14
- 'instagram',
15
- 'tiktok',
16
- 'youtube',
17
- 'facebook',
18
- 'linkedin',
19
- 'x',
20
- 'pinterest',
21
- 'threads',
22
- ] as const
23
- export type Platform = (typeof PLATFORMS)[number]
24
-
25
- export type PostStatus = 'idea' | 'script' | 'recording' | 'editing' | 'scheduled' | 'published'
26
-
27
- export const POST_STATUS_ORDER: PostStatus[] = ['idea', 'script', 'recording', 'editing', 'scheduled', 'published']
28
-
29
- export interface SocialAccount {
30
- id: string
31
- name: string
32
- handle?: string
33
- /** Platforms this account/brand publishes to (one account, many connections). */
34
- platforms: string[]
35
- isActive: boolean
36
- }
37
-
38
- export interface ContentPlan {
39
- id: string
40
- accountId: string
41
- name: string
42
- status: 'draft' | 'active' | 'archived'
43
- weeksCount: number
44
- startDate?: string
45
- /** Structured "what we want" config — feeds chips + future AI drafting. */
46
- objective?: string
47
- tone?: string
48
- pillars: string[]
49
- formats: PostFormat[]
50
- weeklyFrequency: number
51
- /** Free-form brief/strategy document (markdown). */
52
- briefMd: string
53
- }
54
-
55
- /** Recording-day checklist item (on-set companion). */
56
- export interface ChecklistItem {
57
- id: string
58
- text: string
59
- done: boolean
60
- }
61
-
62
- export interface ContentPost {
63
- id: string
64
- planId: string
65
- weekNumber: number
66
- position: number
67
- title: string
68
- format: PostFormat
69
- status: PostStatus
70
- scheduledDate?: string
71
- /** Target platforms for this post; empty = inherit the account's platforms. */
72
- platforms: string[]
73
- /** Recording-day checklist — persisted as the crew checks items off. */
74
- checklist: ChecklistItem[]
75
- /** Uploaded asset URL (static posts: caption + final art instead of a script). */
76
- mediaUrl?: string
77
- hook?: string
78
- cta?: string
79
- /** The Notion-style page body — the script/plan in markdown. */
80
- contentMd: string
81
- updatedAt?: string
82
- }
83
-
84
- export interface SaveSocialAccountInput {
85
- id?: string
86
- name: string
87
- handle?: string
88
- platforms?: string[]
89
- isActive?: boolean
90
- }
91
-
92
- export interface SaveContentPlanInput {
93
- id?: string
94
- accountId?: string
95
- name?: string
96
- status?: ContentPlan['status']
97
- weeksCount?: number
98
- startDate?: string | null
99
- objective?: string
100
- tone?: string
101
- pillars?: string[]
102
- formats?: PostFormat[]
103
- weeklyFrequency?: number
104
- briefMd?: string
105
- }
106
-
107
- export interface SaveContentPostInput {
108
- id?: string
109
- planId?: string
110
- weekNumber?: number
111
- position?: number
112
- title?: string
113
- format?: PostFormat
114
- status?: PostStatus
115
- scheduledDate?: string | null
116
- platforms?: string[]
117
- checklist?: ChecklistItem[]
118
- mediaUrl?: string | null
119
- hook?: string
120
- cta?: string
121
- contentMd?: string
122
- }
123
-
124
- export interface ContentPlannerProvider {
125
- listAccounts(): Promise<SocialAccount[]>
126
- saveAccount(input: SaveSocialAccountInput): Promise<SocialAccount>
127
- /** Hard delete — plans and posts cascade with the account. */
128
- deleteAccount(id: string): Promise<void>
129
- listPlans(accountId: string): Promise<ContentPlan[]>
130
- getPlan(id: string): Promise<ContentPlan | null>
131
- savePlan(input: SaveContentPlanInput): Promise<ContentPlan>
132
- deletePlan(id: string): Promise<void>
133
- /** Ordered by week_number then position. */
134
- listPosts(planId: string): Promise<ContentPost[]>
135
- getPost(id: string): Promise<ContentPost | null>
136
- savePost(input: SaveContentPostInput): Promise<ContentPost>
137
- deletePost(id: string): Promise<void>
138
- }
package/src/data/mock.ts DELETED
@@ -1,308 +0,0 @@
1
- import type { MarketingDataProvider, SaveCampaignInput, SaveLandingPageInput } from './types'
2
- import type {
3
- AcquisitionChannel,
4
- AcquisitionChannelKind,
5
- Campaign,
6
- ChannelPerformance,
7
- ConversionModel,
8
- DateRangeKey,
9
- FunnelStage,
10
- LandingPage,
11
- LandingPagePerf,
12
- MarketingOverview,
13
- MarketingQuery,
14
- MarketingSite,
15
- } from '../types'
16
- import { CAMPAIGN_NAME_SEEDS, type MarketingDomain } from '../presets'
17
-
18
- // ---------------------------------------------------------------------------
19
- // Deterministic seed knobs (no Date.now at module init for stable analytics).
20
- // ---------------------------------------------------------------------------
21
-
22
- const RANGE_FACTOR: Record<DateRangeKey, number> = { '7d': 0.25, '30d': 1, '90d': 3.05 }
23
- const BASE_REACH = [4200, 3100, 2600, 1900, 1500, 1200, 1000]
24
-
25
- const KIND_LEAD_RATE: Record<AcquisitionChannelKind, number> = {
26
- paid: 0.18, social: 0.22, organic: 0.14, referral: 0.3, direct: 0.1, outbound: 0.16,
27
- }
28
- const KIND_CONV_RATE: Record<AcquisitionChannelKind, number> = {
29
- paid: 0.28, social: 0.25, organic: 0.32, referral: 0.45, direct: 0.22, outbound: 0.24,
30
- }
31
- const KIND_SPEND30: Record<AcquisitionChannelKind, number> = {
32
- paid: 2800, social: 900, outbound: 600, organic: 0, referral: 0, direct: 0,
33
- }
34
-
35
- interface ChannelBase {
36
- channelId: string
37
- kind: AcquisitionChannelKind
38
- reach: number
39
- leads: number
40
- conversions: number
41
- spend: number
42
- value: number
43
- }
44
-
45
- export interface MockMarketingConfig {
46
- channels: AcquisitionChannel[]
47
- conversion: ConversionModel
48
- domain: MarketingDomain
49
- }
50
-
51
- function channelBase30(channels: AcquisitionChannel[], conversion: ConversionModel): ChannelBase[] {
52
- return channels.map((ch, i) => {
53
- const reach = BASE_REACH[i % BASE_REACH.length]
54
- const leads = Math.round(reach * KIND_LEAD_RATE[ch.kind])
55
- const conversions = Math.round(leads * KIND_CONV_RATE[ch.kind])
56
- const spend = KIND_SPEND30[ch.kind]
57
- const value = Math.round(conversions * conversion.avgValue)
58
- return { channelId: ch.id, kind: ch.kind, reach, leads, conversions, spend, value }
59
- })
60
- }
61
-
62
- function scale(n: number, range: DateRangeKey): number {
63
- return Math.round(n * RANGE_FACTOR[range])
64
- }
65
-
66
- function toPerformance(base: ChannelBase, range: DateRangeKey): ChannelPerformance {
67
- const reach = scale(base.reach, range)
68
- const leads = scale(base.leads, range)
69
- const conversions = scale(base.conversions, range)
70
- const spend = scale(base.spend, range)
71
- const value = scale(base.value, range)
72
- return {
73
- channelId: base.channelId,
74
- reach, leads, conversions, spend, value,
75
- cvr: reach > 0 ? (conversions / reach) * 100 : 0,
76
- cpa: conversions > 0 && spend > 0 ? spend / conversions : 0,
77
- }
78
- }
79
-
80
- function seedCampaigns(cfg: MockMarketingConfig): Campaign[] {
81
- const names = CAMPAIGN_NAME_SEEDS[cfg.domain]
82
- const base = channelBase30(cfg.channels, cfg.conversion)
83
- const statuses: Campaign['status'][] = ['active', 'active', 'paused', 'ended', 'draft']
84
- const startDays = [12, 26, 40, 8, 3]
85
- const baseDate = new Date('2026-06-16T00:00:00Z').getTime()
86
-
87
- return names.map((name, i) => {
88
- const ch = cfg.channels[i % cfg.channels.length]
89
- const b = base[i % base.length]
90
- const status = statuses[i % statuses.length]
91
- const draft = status === 'draft'
92
- const start = new Date(baseDate - startDays[i % startDays.length] * 86_400_000).toISOString()
93
- // Campaigns carry a slice of their channel's volume so totals stay plausible.
94
- const share = 0.55 + (i % 3) * 0.12
95
- return {
96
- id: `cmp-${i + 1}`,
97
- name,
98
- channelId: ch.id,
99
- status,
100
- start,
101
- end: status === 'ended' ? new Date(baseDate - 2 * 86_400_000).toISOString() : undefined,
102
- spend: draft ? 0 : Math.round(b.spend * share),
103
- reach: draft ? 0 : Math.round(b.reach * share),
104
- leads: draft ? 0 : Math.round(b.leads * share),
105
- conversions: draft ? 0 : Math.round(b.conversions * share),
106
- value: draft ? 0 : Math.round(b.value * share),
107
- }
108
- })
109
- }
110
-
111
- function scaleCampaign(c: Campaign, range: DateRangeKey): Campaign {
112
- if (c.status === 'draft') return c
113
- return {
114
- ...c,
115
- spend: scale(c.spend, range),
116
- reach: scale(c.reach, range),
117
- leads: scale(c.leads, range),
118
- conversions: scale(c.conversions, range),
119
- value: scale(c.value, range),
120
- }
121
- }
122
-
123
- const MOCK_SITE: MarketingSite = {
124
- id: 'site-1',
125
- name: 'Site principal',
126
- url: 'https://exemplo.com.br',
127
- appId: 'app-demo',
128
- }
129
-
130
- const MOCK_LEAD_TAGS = [
131
- { slug: 'lp-consulta', name: 'LP Consulta' },
132
- { slug: 'lp-promo', name: 'LP Promo' },
133
- ]
134
-
135
- interface MockLandingPage extends LandingPage {
136
- /** 30d baseline; scaled per range at read time. */
137
- visits: number
138
- conversions: number
139
- }
140
-
141
- function seedLandingPages(): MockLandingPage[] {
142
- return [
143
- { id: 'lp-1', siteId: MOCK_SITE.id, name: 'Consulta gratuita', route: '/consulta', leadTags: ['lp-consulta'], status: 'live', isActive: true, visits: 3820, conversions: 474 },
144
- { id: 'lp-2', siteId: MOCK_SITE.id, name: 'Promo da estação', route: '/promo', leadTags: ['lp-promo'], status: 'live', isActive: true, visits: 1560, conversions: 126 },
145
- ]
146
- }
147
-
148
- function normalizeRoute(route: string): string {
149
- const trimmed = route.trim().replace(/\/+$/, '')
150
- if (!trimmed || trimmed === '/') return '/'
151
- return trimmed.startsWith('/') ? trimmed : `/${trimmed}`
152
- }
153
-
154
- export function createMockMarketingProvider(cfg: MockMarketingConfig): MarketingDataProvider {
155
- const base = channelBase30(cfg.channels, cfg.conversion)
156
- const campaigns = seedCampaigns(cfg)
157
- const landing = seedLandingPages()
158
- let counter = 100
159
-
160
- async function channelPerformance(query: MarketingQuery): Promise<ChannelPerformance[]> {
161
- const list = base.map((b) => toPerformance(b, query.range))
162
- return query.channelId ? list.filter((c) => c.channelId === query.channelId) : list
163
- }
164
-
165
- return {
166
- channelPerformance,
167
-
168
- async overview(query: MarketingQuery): Promise<MarketingOverview> {
169
- const perf = await channelPerformance(query)
170
- const reach = perf.reduce((s, c) => s + c.reach, 0)
171
- const conversions = perf.reduce((s, c) => s + c.conversions, 0)
172
- const spend = perf.reduce((s, c) => s + c.spend, 0)
173
- const value = perf.reduce((s, c) => s + c.value, 0)
174
- const cvr = reach > 0 ? (conversions / reach) * 100 : 0
175
- const top = [...perf].sort((a, b) => b.conversions - a.conversions)[0]
176
- return {
177
- conversions,
178
- conversionsPrev: Math.round(conversions * 0.86),
179
- cvr,
180
- cvrPrev: cvr * 0.94,
181
- spend,
182
- cpa: conversions > 0 && spend > 0 ? spend / conversions : 0,
183
- value,
184
- topChannelId: top?.channelId ?? null,
185
- channelMix: perf.map((c) => ({ channelId: c.channelId, conversions: c.conversions })),
186
- }
187
- },
188
-
189
- async funnel(query: MarketingQuery): Promise<FunnelStage[]> {
190
- const perf = await channelPerformance(query)
191
- const reach = perf.reduce((s, c) => s + c.reach, 0)
192
- const leads = perf.reduce((s, c) => s + c.leads, 0)
193
- const conversions = perf.reduce((s, c) => s + c.conversions, 0)
194
- const qualified = Math.round(leads * 0.62)
195
- return [
196
- { id: 'reach', label: 'Reach / visits', count: reach, color: '#6366f1' },
197
- { id: 'leads', label: 'Leads', count: leads, color: '#0ea5e9' },
198
- { id: 'qualified', label: 'Qualified', count: qualified, color: '#14b8a6' },
199
- { id: 'converted', label: cfg.conversion.labelPlural, count: conversions, color: '#22c55e' },
200
- ]
201
- },
202
-
203
- async listCampaigns(query: MarketingQuery): Promise<Campaign[]> {
204
- const list = query.channelId ? campaigns.filter((c) => c.channelId === query.channelId) : campaigns
205
- return list.map((c) => scaleCampaign(c, query.range))
206
- },
207
-
208
- async getCampaign(id: string): Promise<Campaign | null> {
209
- return campaigns.find((c) => c.id === id) ?? null
210
- },
211
-
212
- async saveCampaign(input: SaveCampaignInput): Promise<Campaign> {
213
- if (input.id) {
214
- const existing = campaigns.find((c) => c.id === input.id)
215
- if (existing) {
216
- Object.assign(existing, {
217
- name: input.name, channelId: input.channelId, status: input.status,
218
- start: input.start, end: input.end, spend: input.spend,
219
- })
220
- return existing
221
- }
222
- }
223
- const b = base.find((x) => x.channelId === input.channelId) ?? base[0]
224
- const active = input.status === 'active'
225
- const created: Campaign = {
226
- id: `cmp-${++counter}`,
227
- name: input.name,
228
- channelId: input.channelId,
229
- status: input.status,
230
- start: input.start,
231
- end: input.end,
232
- spend: input.spend,
233
- // New campaigns ramp from a small slice of their channel's volume.
234
- reach: active ? Math.round(b.reach * 0.15) : 0,
235
- leads: active ? Math.round(b.leads * 0.15) : 0,
236
- conversions: active ? Math.round(b.conversions * 0.15) : 0,
237
- value: active ? Math.round(b.value * 0.15) : 0,
238
- }
239
- campaigns.unshift(created)
240
- return created
241
- },
242
-
243
- async deleteCampaign(id: string): Promise<void> {
244
- const idx = campaigns.findIndex((c) => c.id === id)
245
- if (idx >= 0) campaigns.splice(idx, 1)
246
- },
247
-
248
- async landingPages(query: MarketingQuery): Promise<LandingPagePerf[]> {
249
- return landing.map((p) => {
250
- const visits = scale(p.visits, query.range)
251
- const conversions = scale(p.conversions, query.range)
252
- return {
253
- ...p,
254
- siteName: MOCK_SITE.name,
255
- siteUrl: MOCK_SITE.url,
256
- appId: MOCK_SITE.appId,
257
- editorUrl: MOCK_SITE.editorUrl,
258
- url: `${MOCK_SITE.url}${p.route}`,
259
- visits,
260
- conversions,
261
- cvr: visits > 0 ? (conversions / visits) * 100 : null,
262
- }
263
- })
264
- },
265
-
266
- async saveLandingPage(input: SaveLandingPageInput): Promise<LandingPage> {
267
- const route = normalizeRoute(input.route)
268
- const existing = input.id ? landing.find((p) => p.id === input.id) : undefined
269
- if (existing) {
270
- Object.assign(existing, {
271
- siteId: input.siteId, name: input.name, route,
272
- leadTags: input.leadTags ?? existing.leadTags,
273
- status: input.status ?? existing.status,
274
- prompt: input.prompt,
275
- isActive: input.isActive ?? existing.isActive,
276
- })
277
- return existing
278
- }
279
- const created: MockLandingPage = {
280
- id: `lp-${++counter}`,
281
- siteId: input.siteId,
282
- name: input.name,
283
- route,
284
- leadTags: input.leadTags ?? [],
285
- status: input.status ?? 'live',
286
- prompt: input.prompt,
287
- isActive: input.isActive ?? true,
288
- visits: 0,
289
- conversions: 0,
290
- }
291
- landing.push(created)
292
- return created
293
- },
294
-
295
- async deleteLandingPage(id: string): Promise<void> {
296
- const idx = landing.findIndex((p) => p.id === id)
297
- if (idx >= 0) landing.splice(idx, 1)
298
- },
299
-
300
- async listSites(): Promise<MarketingSite[]> {
301
- return [MOCK_SITE]
302
- },
303
-
304
- async listLeadTags(): Promise<Array<{ slug: string; name: string }>> {
305
- return MOCK_LEAD_TAGS
306
- },
307
- }
308
- }