@fayz-ai/plugin-marketing 0.2.0 → 0.8.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 (81) hide show
  1. package/README.md +2 -0
  2. package/dist/MarketingContext.d.ts +1 -0
  3. package/dist/MarketingContext.d.ts.map +1 -1
  4. package/dist/MarketingPage.d.ts +5 -1
  5. package/dist/MarketingPage.d.ts.map +1 -1
  6. package/dist/components/icons.d.ts +4 -0
  7. package/dist/components/icons.d.ts.map +1 -1
  8. package/dist/data/contentMedia.d.ts +3 -0
  9. package/dist/data/contentMedia.d.ts.map +1 -0
  10. package/dist/data/contentMock.d.ts +3 -0
  11. package/dist/data/contentMock.d.ts.map +1 -0
  12. package/dist/data/contentSupabase.d.ts +3 -0
  13. package/dist/data/contentSupabase.d.ts.map +1 -0
  14. package/dist/data/contentTypes.d.ts +111 -0
  15. package/dist/data/contentTypes.d.ts.map +1 -0
  16. package/dist/data/tables.d.ts +6 -0
  17. package/dist/data/tables.d.ts.map +1 -0
  18. package/dist/index.cjs +2813 -129
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.ts +8 -0
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +2815 -133
  23. package/dist/index.js.map +1 -1
  24. package/dist/locales/en.d.ts.map +1 -1
  25. package/dist/locales/pt-BR.d.ts.map +1 -1
  26. package/dist/migrations/index.d.ts +9 -0
  27. package/dist/migrations/index.d.ts.map +1 -0
  28. package/dist/presets.d.ts +2 -0
  29. package/dist/presets.d.ts.map +1 -1
  30. package/dist/schema/index.d.ts +717 -0
  31. package/dist/schema/index.d.ts.map +1 -0
  32. package/dist/schema.cjs +65 -0
  33. package/dist/schema.cjs.map +1 -0
  34. package/dist/schema.js +61 -0
  35. package/dist/schema.js.map +1 -0
  36. package/dist/views/content/ContentPlannerContext.d.ts +13 -0
  37. package/dist/views/content/ContentPlannerContext.d.ts.map +1 -0
  38. package/dist/views/content/ContentView.d.ts +5 -0
  39. package/dist/views/content/ContentView.d.ts.map +1 -0
  40. package/dist/views/content/PlanBriefView.d.ts +12 -0
  41. package/dist/views/content/PlanBriefView.d.ts.map +1 -0
  42. package/dist/views/content/PostPage.d.ts +6 -0
  43. package/dist/views/content/PostPage.d.ts.map +1 -0
  44. package/dist/views/content/PostSocialPreview.d.ts +11 -0
  45. package/dist/views/content/PostSocialPreview.d.ts.map +1 -0
  46. package/dist/views/content/RecordingChecklist.d.ts +6 -0
  47. package/dist/views/content/RecordingChecklist.d.ts.map +1 -0
  48. package/dist/views/content/contentBits.d.ts +28 -0
  49. package/dist/views/content/contentBits.d.ts.map +1 -0
  50. package/dist/views/content/contentStore.d.ts +28 -0
  51. package/dist/views/content/contentStore.d.ts.map +1 -0
  52. package/dist/views/content/templates.d.ts +3 -0
  53. package/dist/views/content/templates.d.ts.map +1 -0
  54. package/package.json +17 -4
  55. package/src/MarketingContext.tsx +1 -0
  56. package/src/MarketingPage.tsx +23 -3
  57. package/src/components/icons.tsx +19 -0
  58. package/src/data/contentMedia.ts +25 -0
  59. package/src/data/contentMock.ts +253 -0
  60. package/src/data/contentSupabase.ts +263 -0
  61. package/src/data/contentTypes.ts +138 -0
  62. package/src/data/tables.ts +7 -0
  63. package/src/index.ts +64 -2
  64. package/src/locales/en.ts +95 -0
  65. package/src/locales/pt-BR.ts +95 -0
  66. package/src/migrations/000_plg_rename.sql +15 -0
  67. package/src/migrations/001_content_planner.sql +94 -0
  68. package/src/migrations/002_multi_platform.sql +25 -0
  69. package/src/migrations/003_recording_ops.sql +25 -0
  70. package/src/migrations/index.ts +177 -0
  71. package/src/presets.ts +5 -3
  72. package/src/schema/index.ts +69 -0
  73. package/src/views/content/ContentPlannerContext.tsx +13 -0
  74. package/src/views/content/ContentView.tsx +901 -0
  75. package/src/views/content/PlanBriefView.tsx +268 -0
  76. package/src/views/content/PostPage.tsx +370 -0
  77. package/src/views/content/PostSocialPreview.tsx +212 -0
  78. package/src/views/content/RecordingChecklist.tsx +145 -0
  79. package/src/views/content/contentBits.tsx +114 -0
  80. package/src/views/content/contentStore.ts +160 -0
  81. package/src/views/content/templates.ts +172 -0
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import React from 'react'
2
2
  import type { PluginManifest, PluginRegistryDef, PluginScope, VerticalId } from '@fayz-ai/core'
3
- import { getSupabaseClientOptional, registerTranslations } from '@fayz-ai/core'
3
+ import { createSafeDataProvider, getSupabaseClientOptional, registerTranslations } from '@fayz-ai/core'
4
4
  import { PluginSettingsPanel } from '@fayz-ai/saas'
5
5
  import { MarketingPage } from './MarketingPage'
6
6
  import { SettingsView } from './views/SettingsView'
@@ -17,7 +17,12 @@ import type {
17
17
  SitesPerformanceBridge,
18
18
  } from './data/types'
19
19
  import { createMockMarketingProvider } from './data/mock'
20
+ import type { ContentPlannerProvider } from './data/contentTypes'
21
+ import { createMockContentPlannerProvider } from './data/contentMock'
22
+ import { createSupabaseContentPlannerProvider } from './data/contentSupabase'
20
23
  import { createMarketingStore } from './store'
24
+ import { createContentPlannerStore } from './views/content/contentStore'
25
+ import { MIGRATION_000_PLG_RENAME, MIGRATION_001_CONTENT_PLANNER, MIGRATION_002_MULTI_PLATFORM, MIGRATION_003_RECORDING_OPS } from './migrations'
21
26
  import { DEFAULT_CURRENCY, type MarketingCurrency } from './format'
22
27
  import { MARKETING_PRESETS, type MarketingDomain, type MarketingDomainModules } from './presets'
23
28
  import type { AcquisitionChannel, ConversionModel } from './types'
@@ -50,6 +55,8 @@ export interface MarketingPluginOptions {
50
55
  scope?: PluginScope
51
56
  verticalId?: VerticalId
52
57
  dataProvider?: MarketingDataProvider
58
+ /** Content planner options (module toggled via `modules.contentPlanner`). */
59
+ contentPlanner?: { dataProvider?: ContentPlannerProvider }
53
60
  /** App/vertical-owned settings registries rendered inside Marketing settings. */
54
61
  settingsRegistries?: PluginRegistryDef[]
55
62
  /** Optional DI to read real conversions (mounted later). */
@@ -66,6 +73,7 @@ const DEFAULT_LABELS: MarketingLabels = {
66
73
  campaigns: 'Campaigns',
67
74
  funnel: 'Funnel',
68
75
  landingPages: 'Landing pages',
76
+ content: 'Content',
69
77
  settings: 'Settings',
70
78
  }
71
79
 
@@ -95,9 +103,17 @@ export function createMarketingPlugin(options?: MarketingPluginOptions): PluginM
95
103
  const store = createMarketingStore(provider)
96
104
  const dashboardWidgets = createMarketingDashboardWidgets({ config, provider, store })
97
105
 
106
+ // Content planner has its own provider seam: real Supabase CRUD (with mock
107
+ // fallback) while the analytics provider stays mock-until-bridges.
108
+ const contentProvider = options?.contentPlanner?.dataProvider ?? createSafeDataProvider(
109
+ () => createSupabaseContentPlannerProvider(),
110
+ () => createMockContentPlannerProvider(),
111
+ )
112
+ const contentStore = createContentPlannerStore(contentProvider)
113
+
98
114
  const PageComponent: React.ComponentType<unknown> = () =>
99
115
  React.createElement(MarketingContextProvider, { config, provider, store },
100
- React.createElement(MarketingPage, { config, provider, store }))
116
+ React.createElement(MarketingPage, { config, provider, store, contentProvider, contentStore }))
101
117
  PageComponent.displayName = 'MarketingPage'
102
118
 
103
119
  // Settings lives in the SDK-core central Settings area (not a module tab).
@@ -125,11 +141,15 @@ export function createMarketingPlugin(options?: MarketingPluginOptions): PluginM
125
141
  version: '1.0.0',
126
142
  scope: options?.scope ?? 'universal',
127
143
  verticalId: options?.verticalId,
144
+ // Admin content-planning surface (campaigns, content calendar) — targets the
145
+ // saas/admin Panel world, not the ecommerce storefront/operator console.
146
+ scaffolds: ['saas'],
128
147
  defaultEnabled: true,
129
148
  dependencies: [],
130
149
  declaredFeatures: [
131
150
  { id: 'marketing', label: config.labels.pageTitle, group: 'Engage' },
132
151
  ...(config.modules.landingPages ? [{ id: 'marketing.landing-pages', label: config.labels.landingPages, group: 'Engage' }] : []),
152
+ ...(config.modules.contentPlanner ? [{ id: 'marketing.content', label: config.labels.content, group: 'Engage' }] : []),
133
153
  ],
134
154
  navigation: [
135
155
  {
@@ -155,6 +175,35 @@ export function createMarketingPlugin(options?: MarketingPluginOptions): PluginM
155
175
  { name: 'marketing.campaign.created', description: 'A campaign was created' },
156
176
  { name: 'marketing.campaign.updated', description: 'A campaign was updated' },
157
177
  { name: 'marketing.channel.synced', description: 'A channel pulled fresh attribution data' },
178
+ { name: 'marketing.content.plan.updated', description: 'A content plan (brief/config) was updated' },
179
+ { name: 'marketing.content.post.created', description: 'A content post was created' },
180
+ { name: 'marketing.content.post.updated', description: 'A content post was updated' },
181
+ ],
182
+ migrations: [
183
+ {
184
+ id: 'marketing-000-plg-rename',
185
+ version: '1.0.0',
186
+ sql: MIGRATION_000_PLG_RENAME,
187
+ description: 'Rename legacy mkt_* tables to plg_marketing_* in-place for pools provisioned before the industry-pool rename (guarded no-op on fresh pools)',
188
+ },
189
+ {
190
+ id: 'marketing-001-content-planner',
191
+ version: '1.0.0',
192
+ sql: MIGRATION_001_CONTENT_PLANNER,
193
+ description: 'Create plg_marketing_social_accounts, plg_marketing_content_plans and plg_marketing_content_posts',
194
+ },
195
+ {
196
+ id: 'marketing-002-multi-platform',
197
+ version: '1.1.0',
198
+ sql: MIGRATION_002_MULTI_PLATFORM,
199
+ description: 'Accounts publish to many platforms (platforms[]); posts gain optional platform targets',
200
+ },
201
+ {
202
+ id: 'marketing-003-recording-ops',
203
+ version: '1.2.0',
204
+ sql: MIGRATION_003_RECORDING_OPS,
205
+ description: 'Recording-day checklist + media asset URL on posts; content-media storage bucket',
206
+ },
158
207
  ],
159
208
  aiTools: [
160
209
  {
@@ -240,3 +289,16 @@ export type {
240
289
  } from './types'
241
290
  export { MARKETING_PRESETS, type MarketingDomain } from './presets'
242
291
  export { createMockMarketingProvider } from './data/mock'
292
+ export type {
293
+ ContentPlannerProvider,
294
+ ContentPlan,
295
+ ContentPost,
296
+ SocialAccount,
297
+ PostFormat,
298
+ PostStatus,
299
+ SaveContentPlanInput,
300
+ SaveContentPostInput,
301
+ SaveSocialAccountInput,
302
+ } from './data/contentTypes'
303
+ export { createMockContentPlannerProvider } from './data/contentMock'
304
+ export { createSupabaseContentPlannerProvider } from './data/contentSupabase'
package/src/locales/en.ts CHANGED
@@ -71,4 +71,99 @@ export const en: Record<string, string> = {
71
71
  'marketing.settings.assisted': 'Count assisted conversions',
72
72
  'marketing.settings.assistedDesc': 'Credit channels that contributed to a conversion.',
73
73
  'marketing.settings.channelTrackDesc': 'Track this acquisition source.',
74
+ // content planner
75
+ 'marketing.content.title': 'Content',
76
+ 'marketing.content.week': 'Week',
77
+ 'marketing.content.month': 'Month',
78
+ 'marketing.content.newPost': 'New post',
79
+ 'marketing.content.untitled': 'Untitled',
80
+ 'marketing.content.account': 'Account',
81
+ 'marketing.content.addAccount': 'Add account',
82
+ 'marketing.content.previewNow': 'Just now',
83
+ 'marketing.content.previewLike': 'Like',
84
+ 'marketing.content.previewComment': 'Comment',
85
+ 'marketing.content.previewShare': 'Share',
86
+ 'marketing.content.showLess': 'show less',
87
+ 'marketing.content.checklistTitle': 'Recording checklist',
88
+ 'marketing.content.checklistDescription': 'Shooting-day operations',
89
+ 'marketing.content.checklistOpen': 'Checklist',
90
+ 'marketing.content.checklistEmpty': 'No items yet — generate them from the script or add your own below.',
91
+ 'marketing.content.checklistAddPlaceholder': 'New item…',
92
+ 'marketing.content.checklistAdd': 'Add item',
93
+ 'marketing.content.checklistGenerate': 'Generate from script',
94
+ 'marketing.content.scriptLabel': 'Script',
95
+ 'marketing.content.captionLabel': 'Caption',
96
+ 'marketing.content.captionPlaceholder': 'Write the post caption…',
97
+ 'marketing.content.mediaLabel': 'Media asset',
98
+ 'marketing.content.mediaUpload': 'Upload media',
99
+ 'marketing.content.mediaHint': 'Image or video for this post',
100
+ 'marketing.content.mediaReplace': 'Replace',
101
+ 'marketing.content.mediaRemove': 'Remove',
102
+ 'marketing.content.mediaUnavailable': 'Media upload requires a connected Supabase project.',
103
+ 'marketing.content.editAccountTitle': 'Account settings',
104
+ 'marketing.content.accountSettings': 'Account settings',
105
+ 'marketing.content.deleteAccount': 'Delete account',
106
+ 'marketing.content.deleteAccountConfirm': 'The account and all of its content plans and posts will be permanently deleted.',
107
+ 'marketing.content.noAccountsAvailable': 'No accounts available',
108
+ 'marketing.content.addAccountTitle': 'New account',
109
+ 'marketing.content.nameLabel': 'Name',
110
+ 'marketing.content.handleLabel': 'Handle',
111
+ 'marketing.content.handlePlaceholder': '@handle (optional)',
112
+ 'marketing.content.viewBoard': 'Weekly',
113
+ 'marketing.content.viewCalendar': 'Monthly',
114
+ 'marketing.content.viewList': 'List',
115
+ 'marketing.content.unscheduled': 'Unscheduled',
116
+ 'marketing.content.noPosts': 'No posts yet — add your first one from any week.',
117
+ 'marketing.content.listTitle': 'Title',
118
+ 'marketing.content.listWeek': 'Week',
119
+ 'marketing.content.listDate': 'Date',
120
+ 'marketing.content.listFormat': 'Format',
121
+ 'marketing.content.listStatus': 'Status',
122
+ 'marketing.content.noAccountsHint': 'Create the account (e.g. your Instagram profile) to start planning content.',
123
+ 'marketing.content.accountNamePlaceholder': 'e.g. @myclinic',
124
+ 'marketing.content.createAccount': 'Create account',
125
+ 'marketing.content.defaultPlanName': 'Content plan — 8 weeks',
126
+ 'marketing.content.brief': 'Brief',
127
+ 'marketing.content.planName': 'Plan name',
128
+ 'marketing.content.objective': 'Objective',
129
+ 'marketing.content.objectivePlaceholder': 'e.g. generate evaluation bookings',
130
+ 'marketing.content.tone': 'Tone of voice',
131
+ 'marketing.content.tonePlaceholder': 'e.g. approachable authority, plain language',
132
+ 'marketing.content.pillars': 'Content pillars',
133
+ 'marketing.content.pillarPlaceholder': 'Add pillar…',
134
+ 'marketing.content.formatsLabel': 'Formats',
135
+ 'marketing.content.weeklyFrequency': 'Posts per week',
136
+ 'marketing.content.weeksCount': 'Weeks',
137
+ 'marketing.content.briefBody': 'Brief (markdown)',
138
+ 'marketing.content.briefPlaceholder': 'Describe the strategy, references and what the content should achieve…',
139
+ 'marketing.content.formatLabel': 'Format',
140
+ 'marketing.content.statusLabel': 'Status',
141
+ 'marketing.content.scheduledDate': 'Planned date',
142
+ 'marketing.content.hook': 'Hook',
143
+ 'marketing.content.hookPlaceholder': 'The first line that grabs attention…',
144
+ 'marketing.content.cta': 'CTA',
145
+ 'marketing.content.ctaPlaceholder': 'e.g. Book your evaluation',
146
+ 'marketing.content.titlePlaceholder': 'Post title…',
147
+ 'marketing.content.bodyPlaceholder': 'Post script / plan in markdown…',
148
+ 'marketing.content.insertTemplate': 'Insert script template',
149
+ 'marketing.content.save': 'Save',
150
+ 'marketing.content.cancel': 'Cancel',
151
+ 'marketing.content.edit': 'Edit',
152
+ 'marketing.content.preview': 'Preview',
153
+ 'marketing.content.deletePost': 'Delete post',
154
+ 'marketing.content.deletePostConfirm': 'This post and its script will be permanently removed.',
155
+ 'marketing.content.format.reel': 'Reel',
156
+ 'marketing.content.format.static': 'Static',
157
+ 'marketing.content.format.carousel': 'Carousel',
158
+ 'marketing.content.format.story': 'Story',
159
+ 'marketing.content.format.video': 'Video',
160
+ 'marketing.content.format.live': 'Live',
161
+ 'marketing.content.format.article': 'Article',
162
+ 'marketing.content.platformsLabel': 'Platforms',
163
+ 'marketing.content.status.idea': 'idea',
164
+ 'marketing.content.status.script': 'script',
165
+ 'marketing.content.status.recording': 'recording',
166
+ 'marketing.content.status.editing': 'editing',
167
+ 'marketing.content.status.scheduled': 'scheduled',
168
+ 'marketing.content.status.published': 'published',
74
169
  }
@@ -71,4 +71,99 @@ export const ptBR: Record<string, string> = {
71
71
  'marketing.settings.assisted': 'Contar conversões assistidas',
72
72
  'marketing.settings.assistedDesc': 'Creditar canais que contribuíram para uma conversão.',
73
73
  'marketing.settings.channelTrackDesc': 'Monitorar esta fonte de aquisição.',
74
+ // content planner
75
+ 'marketing.content.title': 'Conteúdo',
76
+ 'marketing.content.week': 'Semana',
77
+ 'marketing.content.month': 'Mês',
78
+ 'marketing.content.newPost': 'Novo post',
79
+ 'marketing.content.untitled': 'Sem título',
80
+ 'marketing.content.account': 'Conta',
81
+ 'marketing.content.addAccount': 'Adicionar conta',
82
+ 'marketing.content.previewNow': 'Agora',
83
+ 'marketing.content.previewLike': 'Curtir',
84
+ 'marketing.content.previewComment': 'Comentar',
85
+ 'marketing.content.previewShare': 'Compartilhar',
86
+ 'marketing.content.showLess': 'mostrar menos',
87
+ 'marketing.content.checklistTitle': 'Checklist de gravação',
88
+ 'marketing.content.checklistDescription': 'Operação do dia de gravação',
89
+ 'marketing.content.checklistOpen': 'Checklist',
90
+ 'marketing.content.checklistEmpty': 'Nenhum item ainda — gere a partir do roteiro ou adicione o seu abaixo.',
91
+ 'marketing.content.checklistAddPlaceholder': 'Novo item…',
92
+ 'marketing.content.checklistAdd': 'Adicionar item',
93
+ 'marketing.content.checklistGenerate': 'Gerar do roteiro',
94
+ 'marketing.content.scriptLabel': 'Roteiro',
95
+ 'marketing.content.captionLabel': 'Legenda',
96
+ 'marketing.content.captionPlaceholder': 'Escreva a legenda do post…',
97
+ 'marketing.content.mediaLabel': 'Arte / mídia',
98
+ 'marketing.content.mediaUpload': 'Enviar mídia',
99
+ 'marketing.content.mediaHint': 'Imagem ou vídeo deste post',
100
+ 'marketing.content.mediaReplace': 'Substituir',
101
+ 'marketing.content.mediaRemove': 'Remover',
102
+ 'marketing.content.mediaUnavailable': 'O upload de mídia requer um projeto Supabase conectado.',
103
+ 'marketing.content.editAccountTitle': 'Configurações da conta',
104
+ 'marketing.content.accountSettings': 'Configurações da conta',
105
+ 'marketing.content.deleteAccount': 'Excluir conta',
106
+ 'marketing.content.deleteAccountConfirm': 'A conta e todos os planos e posts de conteúdo dela serão excluídos permanentemente.',
107
+ 'marketing.content.noAccountsAvailable': 'Nenhuma conta disponível',
108
+ 'marketing.content.addAccountTitle': 'Nova conta',
109
+ 'marketing.content.nameLabel': 'Nome',
110
+ 'marketing.content.handleLabel': 'Usuário',
111
+ 'marketing.content.handlePlaceholder': '@usuario (opcional)',
112
+ 'marketing.content.viewBoard': 'Semanal',
113
+ 'marketing.content.viewCalendar': 'Mensal',
114
+ 'marketing.content.viewList': 'Lista',
115
+ 'marketing.content.unscheduled': 'Sem data',
116
+ 'marketing.content.noPosts': 'Nenhum post ainda — adicione o primeiro em qualquer semana.',
117
+ 'marketing.content.listTitle': 'Título',
118
+ 'marketing.content.listWeek': 'Semana',
119
+ 'marketing.content.listDate': 'Data',
120
+ 'marketing.content.listFormat': 'Formato',
121
+ 'marketing.content.listStatus': 'Status',
122
+ 'marketing.content.noAccountsHint': 'Crie a conta (ex.: o perfil do Instagram) para começar a planejar o conteúdo.',
123
+ 'marketing.content.accountNamePlaceholder': 'ex.: @minhaclinica',
124
+ 'marketing.content.createAccount': 'Criar conta',
125
+ 'marketing.content.defaultPlanName': 'Plano de conteúdo — 8 semanas',
126
+ 'marketing.content.brief': 'Briefing',
127
+ 'marketing.content.planName': 'Nome do plano',
128
+ 'marketing.content.objective': 'Objetivo',
129
+ 'marketing.content.objectivePlaceholder': 'ex.: gerar agendamentos de avaliação',
130
+ 'marketing.content.tone': 'Tom de voz',
131
+ 'marketing.content.tonePlaceholder': 'ex.: autoridade acessível, linguagem simples',
132
+ 'marketing.content.pillars': 'Pilares de conteúdo',
133
+ 'marketing.content.pillarPlaceholder': 'Adicionar pilar…',
134
+ 'marketing.content.formatsLabel': 'Formatos',
135
+ 'marketing.content.weeklyFrequency': 'Posts por semana',
136
+ 'marketing.content.weeksCount': 'Semanas',
137
+ 'marketing.content.briefBody': 'Briefing (markdown)',
138
+ 'marketing.content.briefPlaceholder': 'Descreva a estratégia, referências e o que se quer com o conteúdo…',
139
+ 'marketing.content.formatLabel': 'Formato',
140
+ 'marketing.content.statusLabel': 'Status',
141
+ 'marketing.content.scheduledDate': 'Data prevista',
142
+ 'marketing.content.hook': 'Gancho',
143
+ 'marketing.content.hookPlaceholder': 'A primeira frase que segura a atenção…',
144
+ 'marketing.content.cta': 'CTA',
145
+ 'marketing.content.ctaPlaceholder': 'ex.: Agende sua avaliação',
146
+ 'marketing.content.titlePlaceholder': 'Título do post…',
147
+ 'marketing.content.bodyPlaceholder': 'Roteiro / plano do post em markdown…',
148
+ 'marketing.content.insertTemplate': 'Inserir modelo de roteiro',
149
+ 'marketing.content.save': 'Salvar',
150
+ 'marketing.content.cancel': 'Cancelar',
151
+ 'marketing.content.edit': 'Editar',
152
+ 'marketing.content.preview': 'Visualizar',
153
+ 'marketing.content.deletePost': 'Excluir post',
154
+ 'marketing.content.deletePostConfirm': 'Este post e seu roteiro serão removidos permanentemente.',
155
+ 'marketing.content.format.reel': 'Reel',
156
+ 'marketing.content.format.static': 'Estático',
157
+ 'marketing.content.format.carousel': 'Carrossel',
158
+ 'marketing.content.format.story': 'Story',
159
+ 'marketing.content.format.video': 'Vídeo',
160
+ 'marketing.content.format.live': 'Live',
161
+ 'marketing.content.format.article': 'Artigo',
162
+ 'marketing.content.platformsLabel': 'Plataformas',
163
+ 'marketing.content.status.idea': 'ideia',
164
+ 'marketing.content.status.script': 'roteiro',
165
+ 'marketing.content.status.recording': 'gravação',
166
+ 'marketing.content.status.editing': 'edição',
167
+ 'marketing.content.status.scheduled': 'agendado',
168
+ 'marketing.content.status.published': 'publicado',
74
169
  }
@@ -0,0 +1,15 @@
1
+ -- 000_plg_rename.sql — rename legacy marketing tables to plg_marketing_* for pools
2
+ -- provisioned before the industry-pool rename. Guarded: fires only when legacy
3
+ -- name exists and target does not, so fresh pools skip every branch.
4
+ DO $$
5
+ BEGIN
6
+ IF to_regclass('public.mkt_social_accounts') IS NOT NULL AND to_regclass('public.plg_marketing_social_accounts') IS NULL THEN
7
+ ALTER TABLE public.mkt_social_accounts RENAME TO plg_marketing_social_accounts;
8
+ END IF;
9
+ IF to_regclass('public.mkt_content_plans') IS NOT NULL AND to_regclass('public.plg_marketing_content_plans') IS NULL THEN
10
+ ALTER TABLE public.mkt_content_plans RENAME TO plg_marketing_content_plans;
11
+ END IF;
12
+ IF to_regclass('public.mkt_content_posts') IS NOT NULL AND to_regclass('public.plg_marketing_content_posts') IS NULL THEN
13
+ ALTER TABLE public.mkt_content_posts RENAME TO plg_marketing_content_posts;
14
+ END IF;
15
+ END $$;
@@ -0,0 +1,94 @@
1
+ -- Marketing Plugin: Content Planner
2
+ -- Prefix: plg_marketing_
3
+ -- Social accounts + content plans + posts (Notion-style markdown pages).
4
+
5
+ CREATE TABLE IF NOT EXISTS public.plg_marketing_social_accounts (
6
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
7
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
8
+ name text NOT NULL,
9
+ handle text,
10
+ platform text NOT NULL DEFAULT 'instagram'
11
+ CHECK (platform IN ('instagram', 'tiktok', 'youtube', 'facebook', 'linkedin')),
12
+ is_active boolean NOT NULL DEFAULT true,
13
+ created_at timestamptz NOT NULL DEFAULT now()
14
+ );
15
+ ALTER TABLE public.plg_marketing_social_accounts ENABLE ROW LEVEL SECURITY;
16
+ CREATE INDEX IF NOT EXISTS idx_plg_marketing_social_accounts_tenant ON public.plg_marketing_social_accounts(tenant_id);
17
+
18
+ CREATE TABLE IF NOT EXISTS public.plg_marketing_content_plans (
19
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
20
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
21
+ account_id uuid NOT NULL REFERENCES public.plg_marketing_social_accounts(id) ON DELETE CASCADE,
22
+ name text NOT NULL,
23
+ status text NOT NULL DEFAULT 'active'
24
+ CHECK (status IN ('draft', 'active', 'archived')),
25
+ weeks_count integer NOT NULL DEFAULT 8,
26
+ start_date date,
27
+ objective text,
28
+ tone text,
29
+ pillars jsonb NOT NULL DEFAULT '[]'::jsonb,
30
+ formats jsonb NOT NULL DEFAULT '[]'::jsonb,
31
+ weekly_frequency integer NOT NULL DEFAULT 3,
32
+ brief_md text NOT NULL DEFAULT '',
33
+ created_at timestamptz NOT NULL DEFAULT now(),
34
+ updated_at timestamptz NOT NULL DEFAULT now()
35
+ );
36
+ ALTER TABLE public.plg_marketing_content_plans ENABLE ROW LEVEL SECURITY;
37
+ CREATE INDEX IF NOT EXISTS idx_plg_marketing_content_plans_tenant ON public.plg_marketing_content_plans(tenant_id);
38
+ CREATE INDEX IF NOT EXISTS idx_plg_marketing_content_plans_account ON public.plg_marketing_content_plans(account_id);
39
+
40
+ CREATE TABLE IF NOT EXISTS public.plg_marketing_content_posts (
41
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
42
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
43
+ plan_id uuid NOT NULL REFERENCES public.plg_marketing_content_plans(id) ON DELETE CASCADE,
44
+ week_number integer NOT NULL DEFAULT 1,
45
+ position integer NOT NULL DEFAULT 0,
46
+ title text NOT NULL,
47
+ format text NOT NULL DEFAULT 'reel'
48
+ CHECK (format IN ('reel', 'static', 'carousel', 'story')),
49
+ status text NOT NULL DEFAULT 'idea'
50
+ CHECK (status IN ('idea', 'script', 'recording', 'editing', 'scheduled', 'published')),
51
+ scheduled_date date,
52
+ hook text,
53
+ cta text,
54
+ content_md text NOT NULL DEFAULT '',
55
+ created_at timestamptz NOT NULL DEFAULT now(),
56
+ updated_at timestamptz NOT NULL DEFAULT now()
57
+ );
58
+ ALTER TABLE public.plg_marketing_content_posts ENABLE ROW LEVEL SECURITY;
59
+ CREATE INDEX IF NOT EXISTS idx_plg_marketing_content_posts_tenant ON public.plg_marketing_content_posts(tenant_id);
60
+ CREATE INDEX IF NOT EXISTS idx_plg_marketing_content_posts_plan ON public.plg_marketing_content_posts(plan_id, week_number, position);
61
+ CREATE INDEX IF NOT EXISTS idx_plg_marketing_content_posts_status ON public.plg_marketing_content_posts(tenant_id, status);
62
+
63
+ -- RLS policies for plg_marketing_social_accounts
64
+ DROP POLICY IF EXISTS plg_marketing_social_accounts_select ON public.plg_marketing_social_accounts;
65
+ DROP POLICY IF EXISTS plg_marketing_social_accounts_insert ON public.plg_marketing_social_accounts;
66
+ DROP POLICY IF EXISTS plg_marketing_social_accounts_update ON public.plg_marketing_social_accounts;
67
+ DROP POLICY IF EXISTS plg_marketing_social_accounts_delete ON public.plg_marketing_social_accounts;
68
+ CREATE POLICY plg_marketing_social_accounts_select ON public.plg_marketing_social_accounts FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
69
+ CREATE POLICY plg_marketing_social_accounts_insert ON public.plg_marketing_social_accounts FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
70
+ CREATE POLICY plg_marketing_social_accounts_update ON public.plg_marketing_social_accounts FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
71
+ CREATE POLICY plg_marketing_social_accounts_delete ON public.plg_marketing_social_accounts FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
72
+ GRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_marketing_social_accounts TO authenticated;
73
+
74
+ -- RLS policies for plg_marketing_content_plans
75
+ DROP POLICY IF EXISTS plg_marketing_content_plans_select ON public.plg_marketing_content_plans;
76
+ DROP POLICY IF EXISTS plg_marketing_content_plans_insert ON public.plg_marketing_content_plans;
77
+ DROP POLICY IF EXISTS plg_marketing_content_plans_update ON public.plg_marketing_content_plans;
78
+ DROP POLICY IF EXISTS plg_marketing_content_plans_delete ON public.plg_marketing_content_plans;
79
+ CREATE POLICY plg_marketing_content_plans_select ON public.plg_marketing_content_plans FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
80
+ CREATE POLICY plg_marketing_content_plans_insert ON public.plg_marketing_content_plans FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
81
+ CREATE POLICY plg_marketing_content_plans_update ON public.plg_marketing_content_plans FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
82
+ CREATE POLICY plg_marketing_content_plans_delete ON public.plg_marketing_content_plans FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
83
+ GRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_marketing_content_plans TO authenticated;
84
+
85
+ -- RLS policies for plg_marketing_content_posts
86
+ DROP POLICY IF EXISTS plg_marketing_content_posts_select ON public.plg_marketing_content_posts;
87
+ DROP POLICY IF EXISTS plg_marketing_content_posts_insert ON public.plg_marketing_content_posts;
88
+ DROP POLICY IF EXISTS plg_marketing_content_posts_update ON public.plg_marketing_content_posts;
89
+ DROP POLICY IF EXISTS plg_marketing_content_posts_delete ON public.plg_marketing_content_posts;
90
+ CREATE POLICY plg_marketing_content_posts_select ON public.plg_marketing_content_posts FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
91
+ CREATE POLICY plg_marketing_content_posts_insert ON public.plg_marketing_content_posts FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
92
+ CREATE POLICY plg_marketing_content_posts_update ON public.plg_marketing_content_posts FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
93
+ CREATE POLICY plg_marketing_content_posts_delete ON public.plg_marketing_content_posts FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
94
+ GRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_marketing_content_posts TO authenticated;
@@ -0,0 +1,25 @@
1
+ -- Marketing Plugin: multi-platform accounts + per-post targets.
2
+ -- An account is a brand/profile that publishes to MANY platforms (Instagram,
3
+ -- YouTube, TikTok, ...). Posts optionally narrow the target platforms; an
4
+ -- empty array means "inherit the account's platforms".
5
+
6
+ ALTER TABLE public.plg_marketing_social_accounts
7
+ ADD COLUMN IF NOT EXISTS platforms text[] NOT NULL DEFAULT '{instagram}';
8
+
9
+ -- Backfill from the legacy single-platform column, then drop it (also drops
10
+ -- its CHECK constraint — the platform list is UI-driven from here on).
11
+ DO $$
12
+ BEGIN
13
+ IF EXISTS (
14
+ SELECT 1 FROM information_schema.columns
15
+ WHERE table_schema = 'public' AND table_name = 'plg_marketing_social_accounts' AND column_name = 'platform'
16
+ ) THEN
17
+ UPDATE public.plg_marketing_social_accounts
18
+ SET platforms = ARRAY[platform]
19
+ WHERE platform IS NOT NULL AND platforms = '{instagram}';
20
+ ALTER TABLE public.plg_marketing_social_accounts DROP COLUMN platform;
21
+ END IF;
22
+ END $$;
23
+
24
+ ALTER TABLE public.plg_marketing_content_posts
25
+ ADD COLUMN IF NOT EXISTS platforms text[] NOT NULL DEFAULT '{}';
@@ -0,0 +1,25 @@
1
+ -- Marketing Plugin: recording-day ops on posts.
2
+ -- checklist: shooting checklist items [{ id, text, done }] — the on-set
3
+ -- companion for recording day. media_url: uploaded asset (static posts get a
4
+ -- caption + final art instead of a script).
5
+
6
+ ALTER TABLE public.plg_marketing_content_posts
7
+ ADD COLUMN IF NOT EXISTS checklist jsonb NOT NULL DEFAULT '[]';
8
+
9
+ ALTER TABLE public.plg_marketing_content_posts
10
+ ADD COLUMN IF NOT EXISTS media_url text;
11
+
12
+ -- Public media bucket for content assets (mirrors the avatars-bucket pattern:
13
+ -- public read, authenticated write). Path convention: content/{tenantId}/{postId}-{ts}.{ext}
14
+ INSERT INTO storage.buckets (id, name, public)
15
+ VALUES ('mkt-media', 'mkt-media', true)
16
+ ON CONFLICT (id) DO NOTHING;
17
+
18
+ DROP POLICY IF EXISTS plg_marketing_media_read ON storage.objects;
19
+ DROP POLICY IF EXISTS plg_marketing_media_insert ON storage.objects;
20
+ DROP POLICY IF EXISTS plg_marketing_media_update ON storage.objects;
21
+ DROP POLICY IF EXISTS plg_marketing_media_delete ON storage.objects;
22
+ CREATE POLICY plg_marketing_media_read ON storage.objects FOR SELECT TO public USING (bucket_id = 'mkt-media');
23
+ CREATE POLICY plg_marketing_media_insert ON storage.objects FOR INSERT TO authenticated WITH CHECK (bucket_id = 'mkt-media');
24
+ CREATE POLICY plg_marketing_media_update ON storage.objects FOR UPDATE TO authenticated USING (bucket_id = 'mkt-media');
25
+ CREATE POLICY plg_marketing_media_delete ON storage.objects FOR DELETE TO authenticated USING (bucket_id = 'mkt-media');