@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,72 +0,0 @@
1
- import React from 'react'
2
- import { useTranslation } from '@fayz-ai/core'
3
- import { SettingsGroup, ToggleRow, SelectRow } from '@fayz-ai/saas'
4
- import { useMarketingConfig } from '../MarketingContext'
5
-
6
- const SOURCE_OPTIONS = [
7
- { value: 'crm', label: 'CRM' },
8
- { value: 'agenda', label: 'Agenda' },
9
- { value: 'orders', label: 'Orders' },
10
- { value: 'custom', label: 'Custom' },
11
- ]
12
-
13
- export function SettingsView() {
14
- const t = useTranslation()
15
- const { conversion, channels } = useMarketingConfig()
16
-
17
- // Local UI state — mock toggles (no backend yet), mirrors other plugins' settings.
18
- const [source, setSource] = React.useState(conversion.source)
19
- const [trackValue, setTrackValue] = React.useState(true)
20
- const [autoSync, setAutoSync] = React.useState(true)
21
- const [assisted, setAssisted] = React.useState(false)
22
- const [tracked, setTracked] = React.useState<Record<string, boolean>>(
23
- () => Object.fromEntries(channels.map((c) => [c.id, true])),
24
- )
25
-
26
- return (
27
- <div className="space-y-4">
28
- <SettingsGroup title={t('marketing.settings.conversionModel')} description={t('marketing.settings.conversionModelDesc')}>
29
- <SelectRow
30
- label={t('marketing.settings.attributedFrom')}
31
- description={conversion.label}
32
- value={source}
33
- options={SOURCE_OPTIONS}
34
- onChange={(v) => setSource(v as typeof source)}
35
- />
36
- <ToggleRow
37
- label={t('marketing.settings.trackValue')}
38
- description={t('marketing.settings.trackValueDesc')}
39
- checked={trackValue}
40
- onChange={setTrackValue}
41
- />
42
- </SettingsGroup>
43
-
44
- <SettingsGroup title={t('marketing.settings.channels')} description={t('marketing.settings.channelsDesc')}>
45
- {channels.map((c) => (
46
- <ToggleRow
47
- key={c.id}
48
- label={c.label}
49
- description={t('marketing.settings.channelTrackDesc')}
50
- checked={tracked[c.id] ?? true}
51
- onChange={(v) => setTracked((prev) => ({ ...prev, [c.id]: v }))}
52
- />
53
- ))}
54
- </SettingsGroup>
55
-
56
- <SettingsGroup title={t('marketing.settings.attribution')} description={t('marketing.settings.attributionDesc')}>
57
- <ToggleRow
58
- label={t('marketing.settings.autoSync')}
59
- description={t('marketing.settings.autoSyncDesc')}
60
- checked={autoSync}
61
- onChange={setAutoSync}
62
- />
63
- <ToggleRow
64
- label={t('marketing.settings.assisted')}
65
- description={t('marketing.settings.assistedDesc')}
66
- checked={assisted}
67
- onChange={setAssisted}
68
- />
69
- </SettingsGroup>
70
- </div>
71
- )
72
- }
@@ -1,99 +0,0 @@
1
- import React, { useEffect, useMemo, useState } from 'react'
2
- import { CrudListView } from '@fayz-ai/saas'
3
- import { SegmentedControl } from '@fayz-ai/ui'
4
- import { useMarketingConfig } from '../../MarketingContext'
5
- import { buildBlogPostEntity, buildBlogCategoryEntity } from '../../data/blogEntities'
6
- import { useEntityCrud } from './useEntityCrud'
7
-
8
- // ---------------------------------------------------------------------------
9
- // Blog backoffice — an in-page marketing tab with two lenses (Posts /
10
- // Categories), each a generic CrudListView backed by the blog tables. Follows
11
- // the content-planner pattern: this plugin owns the data + routing; create/edit
12
- // deep-link through the marketing module nav (navigate('blog-post:id') etc.).
13
- // ---------------------------------------------------------------------------
14
-
15
- function PostsList({ navigate }: { navigate: (view: string) => void }) {
16
- const entity = useMemo(() => buildBlogPostEntity(), [])
17
- const { items, total, fetch } = useEntityCrud(entity)
18
- const [search, setSearch] = useState('')
19
- const [statusFilter, setStatusFilter] = useState<string | undefined>()
20
- const [loadedOnce, setLoadedOnce] = useState(false)
21
-
22
- useEffect(() => {
23
- let active = true
24
- Promise.resolve(
25
- fetch({ search: search || undefined, filters: statusFilter ? { status: statusFilter } : undefined }),
26
- ).finally(() => { if (active) setLoadedOnce(true) })
27
- return () => { active = false }
28
- }, [search, statusFilter, fetch])
29
-
30
- const facets = useMemo(() => [{
31
- field: 'status',
32
- allLabel: 'All',
33
- options: [{ value: 'draft', label: 'Draft' }, { value: 'published', label: 'Published' }],
34
- }], [])
35
-
36
- return (
37
- <CrudListView
38
- entityDef={entity}
39
- items={loadedOnce ? items : null}
40
- total={total}
41
- search={search}
42
- onSearchChange={setSearch}
43
- searchPlaceholder="Search posts…"
44
- facets={facets}
45
- activeFilters={{ status: statusFilter }}
46
- onFacetChange={(_field, value) => setStatusFilter(value)}
47
- onNew={() => navigate('blog-new')}
48
- addLabel="New post"
49
- onRowClick={(row) => navigate(`blog-post:${(row as { id: string }).id}`)}
50
- />
51
- )
52
- }
53
-
54
- function CategoriesList({ navigate }: { navigate: (view: string) => void }) {
55
- const entity = useMemo(() => buildBlogCategoryEntity(), [])
56
- const { items, total, fetch } = useEntityCrud(entity)
57
- const [search, setSearch] = useState('')
58
- const [loadedOnce, setLoadedOnce] = useState(false)
59
-
60
- useEffect(() => {
61
- let active = true
62
- Promise.resolve(fetch({ search: search || undefined })).finally(() => { if (active) setLoadedOnce(true) })
63
- return () => { active = false }
64
- }, [search, fetch])
65
-
66
- return (
67
- <CrudListView
68
- entityDef={entity}
69
- items={loadedOnce ? items : null}
70
- total={total}
71
- search={search}
72
- onSearchChange={setSearch}
73
- searchPlaceholder="Search categories…"
74
- onNew={() => navigate('blog-cat-new')}
75
- addLabel="New category"
76
- onRowClick={(row) => navigate(`blog-cat:${(row as { id: string }).id}`)}
77
- />
78
- )
79
- }
80
-
81
- export function BlogAdminView({ navigate }: { navigate: (view: string) => void }) {
82
- const { labels } = useMarketingConfig()
83
- const [lens, setLens] = useState<'posts' | 'categories'>('posts')
84
-
85
- return (
86
- <div className="space-y-4">
87
- <SegmentedControl
88
- aria-label="Blog section"
89
- options={[
90
- { value: 'posts', label: labels.blog },
91
- { value: 'categories', label: labels.blogCategories },
92
- ]}
93
- value={lens}
94
- onChange={(v) => setLens(v as 'posts' | 'categories')}
95
- />
96
- {lens === 'posts' ? <PostsList navigate={navigate} /> : <CategoriesList navigate={navigate} />}
97
- </div>
98
- )
99
- }
@@ -1,101 +0,0 @@
1
- import React from 'react'
2
- import { SubpageHeader, Button, toast } from '@fayz-ai/ui'
3
- import { CrudFormPage, useLimitGuard, invalidateLimit } from '@fayz-ai/saas'
4
- import type { EntityDef } from '@fayz-ai/core'
5
- import { useEntityCrud } from './useEntityCrud'
6
- import { BLOG_POSTS_TABLE } from '../../data/blogEntities'
7
-
8
- // ---------------------------------------------------------------------------
9
- // Generic create/edit for a blog entity (post or category), rendered through
10
- // the shared CrudFormPage (declarative EntityDef → fields, incl. the markdown
11
- // body + category relation). Persistence flows through the generic Supabase
12
- // provider; this only wires load / save / delete + the back link.
13
- // ---------------------------------------------------------------------------
14
-
15
- export function BlogEntityForm<T extends { id: string }>({
16
- entity,
17
- editId,
18
- parentLabel,
19
- onDone,
20
- }: {
21
- entity: EntityDef<T>
22
- editId?: string
23
- parentLabel: string
24
- onDone: () => void
25
- }) {
26
- const { getById, create, update, remove } = useEntityCrud(entity)
27
- const isEdit = !!editId
28
- // Plan quantity guard applies to blog POSTS only (categories are unbounded).
29
- const isPost = (entity as { data?: { table?: string } }).data?.table === BLOG_POSTS_TABLE
30
- const guardBlogPosts = useLimitGuard('blog_posts')
31
- const namePlural = entity.namePlural ?? entity.name + 's'
32
- const [initialData, setInitialData] = React.useState<Record<string, any> | null>(isEdit ? null : {})
33
-
34
- React.useEffect(() => {
35
- if (!editId) return
36
- let cancelled = false
37
- ;(async () => {
38
- const item = await getById(editId)
39
- if (!cancelled) setInitialData((item as Record<string, any>) ?? {})
40
- })()
41
- return () => { cancelled = true }
42
- }, [editId, getById])
43
-
44
- async function save(values: Record<string, any>) {
45
- // Client-side plan guard before the provider create — posts only, on create.
46
- if (!isEdit && isPost && (await guardBlogPosts()) === 'blocked') return
47
- try {
48
- if (isEdit) await update(editId!, values)
49
- else {
50
- await create(values)
51
- if (isPost) invalidateLimit('blog_posts')
52
- }
53
- toast.success(isEdit ? `${entity.name} updated` : `${entity.name} created`)
54
- onDone()
55
- } catch (err) {
56
- toast.error(`Failed to save ${entity.name.toLowerCase()}`)
57
- throw err
58
- }
59
- }
60
-
61
- async function del() {
62
- if (!editId) return
63
- try {
64
- await remove(editId)
65
- toast.success(`${entity.name} deleted`)
66
- onDone()
67
- } catch {
68
- toast.error(`Failed to delete ${entity.name.toLowerCase()}`)
69
- }
70
- }
71
-
72
- const title = isEdit
73
- ? (initialData?.[entity.displayField ?? 'name'] ?? entity.name)
74
- : `New ${entity.name.toLowerCase()}`
75
-
76
- return (
77
- <div className="space-y-4">
78
- <SubpageHeader
79
- title={String(title)}
80
- onBack={onDone}
81
- parentLabel={parentLabel}
82
- actions={isEdit ? (
83
- <Button variant="ghost" className="text-destructive hover:text-destructive" onClick={del}>
84
- Delete
85
- </Button>
86
- ) : undefined}
87
- />
88
- {initialData !== null && (
89
- <CrudFormPage
90
- entityDef={entity}
91
- mode={isEdit ? 'edit' : 'create'}
92
- initialData={initialData}
93
- namePlural={namePlural}
94
- onCancel={onDone}
95
- onSubmit={save}
96
- hideBreadcrumb
97
- />
98
- )}
99
- </div>
100
- )
101
- }
@@ -1,50 +0,0 @@
1
- import { useMemo, useState, useCallback } from 'react'
2
- import { resolveDataProvider } from '@fayz-ai/core'
3
- import type { EntityDef } from '@fayz-ai/core'
4
-
5
- interface ListQuery {
6
- search?: string
7
- filters?: Record<string, unknown>
8
- page?: number
9
- pageSize?: number
10
- sortBy?: string
11
- sortDir?: 'asc' | 'desc'
12
- }
13
-
14
- /**
15
- * Minimal store-less CRUD wiring for a plugin that owns its data + routing.
16
- * Resolves the generic Supabase provider from the EntityDef's `data.table`
17
- * (mock fallback when Supabase is not configured) and exposes list/get/save/
18
- * remove. Used by the blog backoffice list + form views — the heavy list/form
19
- * rendering stays in the shared CrudListView / CrudFormPage.
20
- */
21
- export function useEntityCrud<T extends { id: string }>(entity: EntityDef<T>) {
22
- const provider = useMemo(() => resolveDataProvider<T>(entity), [entity])
23
- const [items, setItems] = useState<T[] | null>(null)
24
- const [total, setTotal] = useState(0)
25
-
26
- const fetch = useCallback(async (query: ListQuery = {}) => {
27
- try {
28
- const res = await provider.list(query as any)
29
- setItems(res.data)
30
- setTotal(res.total)
31
- } catch (err) {
32
- // Table not provisioned yet (migration not applied to this pool) or a
33
- // transient error — surface an empty list instead of a perpetual skeleton.
34
- console.warn('[blog] list failed; showing empty. Apply the plugin-blog migration to this pool.', err)
35
- setItems([])
36
- setTotal(0)
37
- }
38
- }, [provider])
39
-
40
- const getById = useCallback(async (id: string): Promise<T | null> => {
41
- const res = await provider.list({ filters: { id }, page: 1, pageSize: 1 } as any)
42
- return res.data[0] ?? null
43
- }, [provider])
44
-
45
- const create = useCallback((data: Record<string, unknown>) => provider.create(data as any), [provider])
46
- const update = useCallback((id: string, data: Record<string, unknown>) => provider.update(id, data as any), [provider])
47
- const remove = useCallback((id: string) => provider.remove(id), [provider])
48
-
49
- return { items, total, fetch, getById, create, update, remove }
50
- }
@@ -1,13 +0,0 @@
1
- import { createPluginContext } from '@fayz-ai/saas'
2
- import type { ResolvedMarketingConfig } from '../../MarketingContext'
3
- import type { ContentPlannerProvider } from '../../data/contentTypes'
4
- import type { ContentPlannerUIState } from './contentStore'
5
-
6
- // Separate context trio for the content planner — MarketingContext's generics
7
- // are fixed to the analytics provider/store, so the planner mounts its own.
8
- const ctx = createPluginContext<ResolvedMarketingConfig, ContentPlannerProvider, ContentPlannerUIState>('ContentPlanner')
9
-
10
- export const ContentPlannerContextProvider = ctx.ContextProvider
11
- export const useContentPlannerConfig = ctx.useConfig
12
- export const useContentPlannerProvider = ctx.useProvider
13
- export const useContentPlannerStore = ctx.useStore