@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.
- package/README.md +2 -0
- package/dist/MarketingContext.d.ts +1 -0
- package/dist/MarketingContext.d.ts.map +1 -1
- package/dist/MarketingPage.d.ts +5 -1
- package/dist/MarketingPage.d.ts.map +1 -1
- package/dist/components/icons.d.ts +4 -0
- package/dist/components/icons.d.ts.map +1 -1
- package/dist/data/contentMedia.d.ts +3 -0
- package/dist/data/contentMedia.d.ts.map +1 -0
- package/dist/data/contentMock.d.ts +3 -0
- package/dist/data/contentMock.d.ts.map +1 -0
- package/dist/data/contentSupabase.d.ts +3 -0
- package/dist/data/contentSupabase.d.ts.map +1 -0
- package/dist/data/contentTypes.d.ts +111 -0
- package/dist/data/contentTypes.d.ts.map +1 -0
- package/dist/data/tables.d.ts +6 -0
- package/dist/data/tables.d.ts.map +1 -0
- package/dist/index.cjs +2813 -129
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2815 -133
- package/dist/index.js.map +1 -1
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/pt-BR.d.ts.map +1 -1
- package/dist/migrations/index.d.ts +9 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/presets.d.ts +2 -0
- package/dist/presets.d.ts.map +1 -1
- package/dist/schema/index.d.ts +717 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema.cjs +65 -0
- package/dist/schema.cjs.map +1 -0
- package/dist/schema.js +61 -0
- package/dist/schema.js.map +1 -0
- package/dist/views/content/ContentPlannerContext.d.ts +13 -0
- package/dist/views/content/ContentPlannerContext.d.ts.map +1 -0
- package/dist/views/content/ContentView.d.ts +5 -0
- package/dist/views/content/ContentView.d.ts.map +1 -0
- package/dist/views/content/PlanBriefView.d.ts +12 -0
- package/dist/views/content/PlanBriefView.d.ts.map +1 -0
- package/dist/views/content/PostPage.d.ts +6 -0
- package/dist/views/content/PostPage.d.ts.map +1 -0
- package/dist/views/content/PostSocialPreview.d.ts +11 -0
- package/dist/views/content/PostSocialPreview.d.ts.map +1 -0
- package/dist/views/content/RecordingChecklist.d.ts +6 -0
- package/dist/views/content/RecordingChecklist.d.ts.map +1 -0
- package/dist/views/content/contentBits.d.ts +28 -0
- package/dist/views/content/contentBits.d.ts.map +1 -0
- package/dist/views/content/contentStore.d.ts +28 -0
- package/dist/views/content/contentStore.d.ts.map +1 -0
- package/dist/views/content/templates.d.ts +3 -0
- package/dist/views/content/templates.d.ts.map +1 -0
- package/package.json +17 -4
- package/src/MarketingContext.tsx +1 -0
- package/src/MarketingPage.tsx +23 -3
- package/src/components/icons.tsx +19 -0
- package/src/data/contentMedia.ts +25 -0
- package/src/data/contentMock.ts +253 -0
- package/src/data/contentSupabase.ts +263 -0
- package/src/data/contentTypes.ts +138 -0
- package/src/data/tables.ts +7 -0
- package/src/index.ts +64 -2
- package/src/locales/en.ts +95 -0
- package/src/locales/pt-BR.ts +95 -0
- package/src/migrations/000_plg_rename.sql +15 -0
- package/src/migrations/001_content_planner.sql +94 -0
- package/src/migrations/002_multi_platform.sql +25 -0
- package/src/migrations/003_recording_ops.sql +25 -0
- package/src/migrations/index.ts +177 -0
- package/src/presets.ts +5 -3
- package/src/schema/index.ts +69 -0
- package/src/views/content/ContentPlannerContext.tsx +13 -0
- package/src/views/content/ContentView.tsx +901 -0
- package/src/views/content/PlanBriefView.tsx +268 -0
- package/src/views/content/PostPage.tsx +370 -0
- package/src/views/content/PostSocialPreview.tsx +212 -0
- package/src/views/content/RecordingChecklist.tsx +145 -0
- package/src/views/content/contentBits.tsx +114 -0
- package/src/views/content/contentStore.ts +160 -0
- package/src/views/content/templates.ts +172 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// AUTO-GENERATED from 000_plg_rename.sql, 001_content_planner.sql, 002_multi_platform.sql, 003_recording_ops.sql — regenerate with scripts/embed-migrations.mjs
|
|
2
|
+
// SQL files are the source of truth; this inline copy lets the manifest declare
|
|
3
|
+
// migrations as data. Do not edit by hand — run the embed script instead.
|
|
4
|
+
|
|
5
|
+
export const MIGRATION_000_PLG_RENAME = `-- 000_plg_rename.sql — rename legacy marketing tables to plg_marketing_* for pools
|
|
6
|
+
-- provisioned before the industry-pool rename. Guarded: fires only when legacy
|
|
7
|
+
-- name exists and target does not, so fresh pools skip every branch.
|
|
8
|
+
DO $$
|
|
9
|
+
BEGIN
|
|
10
|
+
IF to_regclass('public.mkt_social_accounts') IS NOT NULL AND to_regclass('public.plg_marketing_social_accounts') IS NULL THEN
|
|
11
|
+
ALTER TABLE public.mkt_social_accounts RENAME TO plg_marketing_social_accounts;
|
|
12
|
+
END IF;
|
|
13
|
+
IF to_regclass('public.mkt_content_plans') IS NOT NULL AND to_regclass('public.plg_marketing_content_plans') IS NULL THEN
|
|
14
|
+
ALTER TABLE public.mkt_content_plans RENAME TO plg_marketing_content_plans;
|
|
15
|
+
END IF;
|
|
16
|
+
IF to_regclass('public.mkt_content_posts') IS NOT NULL AND to_regclass('public.plg_marketing_content_posts') IS NULL THEN
|
|
17
|
+
ALTER TABLE public.mkt_content_posts RENAME TO plg_marketing_content_posts;
|
|
18
|
+
END IF;
|
|
19
|
+
END $$;
|
|
20
|
+
`
|
|
21
|
+
|
|
22
|
+
export const MIGRATION_001_CONTENT_PLANNER = `-- Marketing Plugin: Content Planner
|
|
23
|
+
-- Prefix: plg_marketing_
|
|
24
|
+
-- Social accounts + content plans + posts (Notion-style markdown pages).
|
|
25
|
+
|
|
26
|
+
CREATE TABLE IF NOT EXISTS public.plg_marketing_social_accounts (
|
|
27
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
28
|
+
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
29
|
+
name text NOT NULL,
|
|
30
|
+
handle text,
|
|
31
|
+
platform text NOT NULL DEFAULT 'instagram'
|
|
32
|
+
CHECK (platform IN ('instagram', 'tiktok', 'youtube', 'facebook', 'linkedin')),
|
|
33
|
+
is_active boolean NOT NULL DEFAULT true,
|
|
34
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
35
|
+
);
|
|
36
|
+
ALTER TABLE public.plg_marketing_social_accounts ENABLE ROW LEVEL SECURITY;
|
|
37
|
+
CREATE INDEX IF NOT EXISTS idx_plg_marketing_social_accounts_tenant ON public.plg_marketing_social_accounts(tenant_id);
|
|
38
|
+
|
|
39
|
+
CREATE TABLE IF NOT EXISTS public.plg_marketing_content_plans (
|
|
40
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
41
|
+
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
42
|
+
account_id uuid NOT NULL REFERENCES public.plg_marketing_social_accounts(id) ON DELETE CASCADE,
|
|
43
|
+
name text NOT NULL,
|
|
44
|
+
status text NOT NULL DEFAULT 'active'
|
|
45
|
+
CHECK (status IN ('draft', 'active', 'archived')),
|
|
46
|
+
weeks_count integer NOT NULL DEFAULT 8,
|
|
47
|
+
start_date date,
|
|
48
|
+
objective text,
|
|
49
|
+
tone text,
|
|
50
|
+
pillars jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
51
|
+
formats jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
52
|
+
weekly_frequency integer NOT NULL DEFAULT 3,
|
|
53
|
+
brief_md text NOT NULL DEFAULT '',
|
|
54
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
55
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
56
|
+
);
|
|
57
|
+
ALTER TABLE public.plg_marketing_content_plans ENABLE ROW LEVEL SECURITY;
|
|
58
|
+
CREATE INDEX IF NOT EXISTS idx_plg_marketing_content_plans_tenant ON public.plg_marketing_content_plans(tenant_id);
|
|
59
|
+
CREATE INDEX IF NOT EXISTS idx_plg_marketing_content_plans_account ON public.plg_marketing_content_plans(account_id);
|
|
60
|
+
|
|
61
|
+
CREATE TABLE IF NOT EXISTS public.plg_marketing_content_posts (
|
|
62
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
63
|
+
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
64
|
+
plan_id uuid NOT NULL REFERENCES public.plg_marketing_content_plans(id) ON DELETE CASCADE,
|
|
65
|
+
week_number integer NOT NULL DEFAULT 1,
|
|
66
|
+
position integer NOT NULL DEFAULT 0,
|
|
67
|
+
title text NOT NULL,
|
|
68
|
+
format text NOT NULL DEFAULT 'reel'
|
|
69
|
+
CHECK (format IN ('reel', 'static', 'carousel', 'story')),
|
|
70
|
+
status text NOT NULL DEFAULT 'idea'
|
|
71
|
+
CHECK (status IN ('idea', 'script', 'recording', 'editing', 'scheduled', 'published')),
|
|
72
|
+
scheduled_date date,
|
|
73
|
+
hook text,
|
|
74
|
+
cta text,
|
|
75
|
+
content_md text NOT NULL DEFAULT '',
|
|
76
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
77
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
78
|
+
);
|
|
79
|
+
ALTER TABLE public.plg_marketing_content_posts ENABLE ROW LEVEL SECURITY;
|
|
80
|
+
CREATE INDEX IF NOT EXISTS idx_plg_marketing_content_posts_tenant ON public.plg_marketing_content_posts(tenant_id);
|
|
81
|
+
CREATE INDEX IF NOT EXISTS idx_plg_marketing_content_posts_plan ON public.plg_marketing_content_posts(plan_id, week_number, position);
|
|
82
|
+
CREATE INDEX IF NOT EXISTS idx_plg_marketing_content_posts_status ON public.plg_marketing_content_posts(tenant_id, status);
|
|
83
|
+
|
|
84
|
+
-- RLS policies for plg_marketing_social_accounts
|
|
85
|
+
DROP POLICY IF EXISTS plg_marketing_social_accounts_select ON public.plg_marketing_social_accounts;
|
|
86
|
+
DROP POLICY IF EXISTS plg_marketing_social_accounts_insert ON public.plg_marketing_social_accounts;
|
|
87
|
+
DROP POLICY IF EXISTS plg_marketing_social_accounts_update ON public.plg_marketing_social_accounts;
|
|
88
|
+
DROP POLICY IF EXISTS plg_marketing_social_accounts_delete ON public.plg_marketing_social_accounts;
|
|
89
|
+
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()));
|
|
90
|
+
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()));
|
|
91
|
+
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()));
|
|
92
|
+
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()));
|
|
93
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_marketing_social_accounts TO authenticated;
|
|
94
|
+
|
|
95
|
+
-- RLS policies for plg_marketing_content_plans
|
|
96
|
+
DROP POLICY IF EXISTS plg_marketing_content_plans_select ON public.plg_marketing_content_plans;
|
|
97
|
+
DROP POLICY IF EXISTS plg_marketing_content_plans_insert ON public.plg_marketing_content_plans;
|
|
98
|
+
DROP POLICY IF EXISTS plg_marketing_content_plans_update ON public.plg_marketing_content_plans;
|
|
99
|
+
DROP POLICY IF EXISTS plg_marketing_content_plans_delete ON public.plg_marketing_content_plans;
|
|
100
|
+
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()));
|
|
101
|
+
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()));
|
|
102
|
+
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()));
|
|
103
|
+
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()));
|
|
104
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_marketing_content_plans TO authenticated;
|
|
105
|
+
|
|
106
|
+
-- RLS policies for plg_marketing_content_posts
|
|
107
|
+
DROP POLICY IF EXISTS plg_marketing_content_posts_select ON public.plg_marketing_content_posts;
|
|
108
|
+
DROP POLICY IF EXISTS plg_marketing_content_posts_insert ON public.plg_marketing_content_posts;
|
|
109
|
+
DROP POLICY IF EXISTS plg_marketing_content_posts_update ON public.plg_marketing_content_posts;
|
|
110
|
+
DROP POLICY IF EXISTS plg_marketing_content_posts_delete ON public.plg_marketing_content_posts;
|
|
111
|
+
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()));
|
|
112
|
+
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()));
|
|
113
|
+
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()));
|
|
114
|
+
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()));
|
|
115
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_marketing_content_posts TO authenticated;
|
|
116
|
+
`
|
|
117
|
+
|
|
118
|
+
export const MIGRATION_002_MULTI_PLATFORM = `-- Marketing Plugin: multi-platform accounts + per-post targets.
|
|
119
|
+
-- An account is a brand/profile that publishes to MANY platforms (Instagram,
|
|
120
|
+
-- YouTube, TikTok, ...). Posts optionally narrow the target platforms; an
|
|
121
|
+
-- empty array means "inherit the account's platforms".
|
|
122
|
+
|
|
123
|
+
ALTER TABLE public.plg_marketing_social_accounts
|
|
124
|
+
ADD COLUMN IF NOT EXISTS platforms text[] NOT NULL DEFAULT '{instagram}';
|
|
125
|
+
|
|
126
|
+
-- Backfill from the legacy single-platform column, then drop it (also drops
|
|
127
|
+
-- its CHECK constraint — the platform list is UI-driven from here on).
|
|
128
|
+
DO $$
|
|
129
|
+
BEGIN
|
|
130
|
+
IF EXISTS (
|
|
131
|
+
SELECT 1 FROM information_schema.columns
|
|
132
|
+
WHERE table_schema = 'public' AND table_name = 'plg_marketing_social_accounts' AND column_name = 'platform'
|
|
133
|
+
) THEN
|
|
134
|
+
UPDATE public.plg_marketing_social_accounts
|
|
135
|
+
SET platforms = ARRAY[platform]
|
|
136
|
+
WHERE platform IS NOT NULL AND platforms = '{instagram}';
|
|
137
|
+
ALTER TABLE public.plg_marketing_social_accounts DROP COLUMN platform;
|
|
138
|
+
END IF;
|
|
139
|
+
END $$;
|
|
140
|
+
|
|
141
|
+
ALTER TABLE public.plg_marketing_content_posts
|
|
142
|
+
ADD COLUMN IF NOT EXISTS platforms text[] NOT NULL DEFAULT '{}';
|
|
143
|
+
`
|
|
144
|
+
|
|
145
|
+
export const MIGRATION_003_RECORDING_OPS = `-- Marketing Plugin: recording-day ops on posts.
|
|
146
|
+
-- checklist: shooting checklist items [{ id, text, done }] — the on-set
|
|
147
|
+
-- companion for recording day. media_url: uploaded asset (static posts get a
|
|
148
|
+
-- caption + final art instead of a script).
|
|
149
|
+
|
|
150
|
+
ALTER TABLE public.plg_marketing_content_posts
|
|
151
|
+
ADD COLUMN IF NOT EXISTS checklist jsonb NOT NULL DEFAULT '[]';
|
|
152
|
+
|
|
153
|
+
ALTER TABLE public.plg_marketing_content_posts
|
|
154
|
+
ADD COLUMN IF NOT EXISTS media_url text;
|
|
155
|
+
|
|
156
|
+
-- Public media bucket for content assets (mirrors the avatars-bucket pattern:
|
|
157
|
+
-- public read, authenticated write). Path convention: content/{tenantId}/{postId}-{ts}.{ext}
|
|
158
|
+
INSERT INTO storage.buckets (id, name, public)
|
|
159
|
+
VALUES ('mkt-media', 'mkt-media', true)
|
|
160
|
+
ON CONFLICT (id) DO NOTHING;
|
|
161
|
+
|
|
162
|
+
DROP POLICY IF EXISTS plg_marketing_media_read ON storage.objects;
|
|
163
|
+
DROP POLICY IF EXISTS plg_marketing_media_insert ON storage.objects;
|
|
164
|
+
DROP POLICY IF EXISTS plg_marketing_media_update ON storage.objects;
|
|
165
|
+
DROP POLICY IF EXISTS plg_marketing_media_delete ON storage.objects;
|
|
166
|
+
CREATE POLICY plg_marketing_media_read ON storage.objects FOR SELECT TO public USING (bucket_id = 'mkt-media');
|
|
167
|
+
CREATE POLICY plg_marketing_media_insert ON storage.objects FOR INSERT TO authenticated WITH CHECK (bucket_id = 'mkt-media');
|
|
168
|
+
CREATE POLICY plg_marketing_media_update ON storage.objects FOR UPDATE TO authenticated USING (bucket_id = 'mkt-media');
|
|
169
|
+
CREATE POLICY plg_marketing_media_delete ON storage.objects FOR DELETE TO authenticated USING (bucket_id = 'mkt-media');
|
|
170
|
+
`
|
|
171
|
+
|
|
172
|
+
export const MIGRATIONS: Array<{ id: string; sql: string }> = [
|
|
173
|
+
{ id: "000_plg_rename", sql: MIGRATION_000_PLG_RENAME },
|
|
174
|
+
{ id: "001_content_planner", sql: MIGRATION_001_CONTENT_PLANNER },
|
|
175
|
+
{ id: "002_multi_platform", sql: MIGRATION_002_MULTI_PLATFORM },
|
|
176
|
+
{ id: "003_recording_ops", sql: MIGRATION_003_RECORDING_OPS },
|
|
177
|
+
]
|
package/src/presets.ts
CHANGED
|
@@ -14,6 +14,8 @@ export interface MarketingDomainModules {
|
|
|
14
14
|
campaigns: boolean
|
|
15
15
|
funnel: boolean
|
|
16
16
|
landingPages: boolean
|
|
17
|
+
/** Social content planner — accounts, week board, markdown post pages. */
|
|
18
|
+
contentPlanner: boolean
|
|
17
19
|
/** Outbound — reserved, off by default, implemented in a later milestone. */
|
|
18
20
|
broadcasts: boolean
|
|
19
21
|
journeys: boolean
|
|
@@ -42,7 +44,7 @@ const AGENCY: MarketingDomainPreset = {
|
|
|
42
44
|
{ id: 'referral', label: 'Referral', icon: 'Users', kind: 'referral' },
|
|
43
45
|
{ id: 'direct', label: 'Direct', icon: 'MousePointerClick', kind: 'direct' },
|
|
44
46
|
],
|
|
45
|
-
modules: { channels: true, campaigns: true, funnel: true, landingPages: true, broadcasts: false, journeys: false },
|
|
47
|
+
modules: { channels: true, campaigns: true, funnel: true, landingPages: true, contentPlanner: false, broadcasts: false, journeys: false },
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
const BEAUTY: MarketingDomainPreset = {
|
|
@@ -61,7 +63,7 @@ const BEAUTY: MarketingDomainPreset = {
|
|
|
61
63
|
{ id: 'whatsapp', label: 'WhatsApp', icon: 'MessageCircle', kind: 'social' },
|
|
62
64
|
{ id: 'walkin', label: 'Walk-in', icon: 'DoorOpen', kind: 'direct' },
|
|
63
65
|
],
|
|
64
|
-
modules: { channels: true, campaigns: true, funnel: true, landingPages: false, broadcasts: false, journeys: false },
|
|
66
|
+
modules: { channels: true, campaigns: true, funnel: true, landingPages: false, contentPlanner: true, broadcasts: false, journeys: false },
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
const RESTO: MarketingDomainPreset = {
|
|
@@ -80,7 +82,7 @@ const RESTO: MarketingDomainPreset = {
|
|
|
80
82
|
{ id: 'instagram', label: 'Instagram', icon: 'Instagram', kind: 'social' },
|
|
81
83
|
{ id: 'walkin', label: 'Walk-in', icon: 'DoorOpen', kind: 'direct' },
|
|
82
84
|
],
|
|
83
|
-
modules: { channels: true, campaigns: true, funnel: true, landingPages: false, broadcasts: false, journeys: false },
|
|
85
|
+
modules: { channels: true, campaigns: true, funnel: true, landingPages: false, contentPlanner: false, broadcasts: false, journeys: false },
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
export const MARKETING_PRESETS: Record<MarketingDomain, MarketingDomainPreset> = {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Builders come from @fayz-ai/db (single drizzle-orm instance) — never import
|
|
2
|
+
// pgTable/uuid directly from drizzle-orm/pg-core. See MIGRATION-ARCHITECTURE §6.
|
|
3
|
+
import {
|
|
4
|
+
pgTable, uuid, text, boolean, integer, date, jsonb, index,
|
|
5
|
+
tenantId, timestamps, createdAt,
|
|
6
|
+
} from '@fayz-ai/db'
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Marketing plugin — Ring-1 schema (schema-as-code), content planner tables.
|
|
10
|
+
// RLS policies + grants are companion SQL (Drizzle doesn't diff those) —
|
|
11
|
+
// see src/migrations/001_content_planner.sql.
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
export const mktSocialAccounts = pgTable('plg_marketing_social_accounts', {
|
|
15
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
16
|
+
tenantId: tenantId(),
|
|
17
|
+
name: text('name').notNull(),
|
|
18
|
+
handle: text('handle'),
|
|
19
|
+
// One account, many platform connections (Instagram, YouTube, TikTok, ...)
|
|
20
|
+
platforms: text('platforms').array().notNull().default(['instagram']),
|
|
21
|
+
isActive: boolean('is_active').notNull().default(true),
|
|
22
|
+
...createdAt,
|
|
23
|
+
}, (t) => [index('idx_plg_marketing_social_accounts_tenant').on(t.tenantId)])
|
|
24
|
+
|
|
25
|
+
export const mktContentPlans = pgTable('plg_marketing_content_plans', {
|
|
26
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
27
|
+
tenantId: tenantId(),
|
|
28
|
+
accountId: uuid('account_id').notNull().references(() => mktSocialAccounts.id, { onDelete: 'cascade' }),
|
|
29
|
+
name: text('name').notNull(),
|
|
30
|
+
status: text('status').notNull().default('active'),
|
|
31
|
+
weeksCount: integer('weeks_count').notNull().default(8),
|
|
32
|
+
startDate: date('start_date'),
|
|
33
|
+
objective: text('objective'),
|
|
34
|
+
tone: text('tone'),
|
|
35
|
+
pillars: jsonb('pillars').notNull().default([]),
|
|
36
|
+
formats: jsonb('formats').notNull().default([]),
|
|
37
|
+
weeklyFrequency: integer('weekly_frequency').notNull().default(3),
|
|
38
|
+
briefMd: text('brief_md').notNull().default(''),
|
|
39
|
+
...timestamps,
|
|
40
|
+
}, (t) => [
|
|
41
|
+
index('idx_plg_marketing_content_plans_tenant').on(t.tenantId),
|
|
42
|
+
index('idx_plg_marketing_content_plans_account').on(t.accountId),
|
|
43
|
+
])
|
|
44
|
+
|
|
45
|
+
export const mktContentPosts = pgTable('plg_marketing_content_posts', {
|
|
46
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
47
|
+
tenantId: tenantId(),
|
|
48
|
+
planId: uuid('plan_id').notNull().references(() => mktContentPlans.id, { onDelete: 'cascade' }),
|
|
49
|
+
weekNumber: integer('week_number').notNull().default(1),
|
|
50
|
+
position: integer('position').notNull().default(0),
|
|
51
|
+
title: text('title').notNull(),
|
|
52
|
+
format: text('format').notNull().default('reel'),
|
|
53
|
+
status: text('status').notNull().default('idea'),
|
|
54
|
+
scheduledDate: date('scheduled_date'),
|
|
55
|
+
// Target platforms for this post; empty = inherit the account's platforms
|
|
56
|
+
platforms: text('platforms').array().notNull().default([]),
|
|
57
|
+
// Recording-day checklist [{ id, text, done }]
|
|
58
|
+
checklist: jsonb('checklist').notNull().default([]),
|
|
59
|
+
// Uploaded asset (static posts: caption + final art instead of a script)
|
|
60
|
+
mediaUrl: text('media_url'),
|
|
61
|
+
hook: text('hook'),
|
|
62
|
+
cta: text('cta'),
|
|
63
|
+
contentMd: text('content_md').notNull().default(''),
|
|
64
|
+
...timestamps,
|
|
65
|
+
}, (t) => [
|
|
66
|
+
index('idx_plg_marketing_content_posts_tenant').on(t.tenantId),
|
|
67
|
+
index('idx_plg_marketing_content_posts_plan').on(t.planId, t.weekNumber, t.position),
|
|
68
|
+
index('idx_plg_marketing_content_posts_status').on(t.tenantId, t.status),
|
|
69
|
+
])
|
|
@@ -0,0 +1,13 @@
|
|
|
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
|