@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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schema/index.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAS+C,CAAA;AAE7E,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkB1B,CAAA;AAEF,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAwB1B,CAAA"}
@@ -0,0 +1,65 @@
1
+ 'use strict';
2
+
3
+ var db = require('@fayz-ai/db');
4
+
5
+ // src/schema/index.ts
6
+ var mktSocialAccounts = db.pgTable("plg_marketing_social_accounts", {
7
+ id: db.uuid("id").primaryKey().defaultRandom(),
8
+ tenantId: db.tenantId(),
9
+ name: db.text("name").notNull(),
10
+ handle: db.text("handle"),
11
+ // One account, many platform connections (Instagram, YouTube, TikTok, ...)
12
+ platforms: db.text("platforms").array().notNull().default(["instagram"]),
13
+ isActive: db.boolean("is_active").notNull().default(true),
14
+ ...db.createdAt
15
+ }, (t) => [db.index("idx_plg_marketing_social_accounts_tenant").on(t.tenantId)]);
16
+ var mktContentPlans = db.pgTable("plg_marketing_content_plans", {
17
+ id: db.uuid("id").primaryKey().defaultRandom(),
18
+ tenantId: db.tenantId(),
19
+ accountId: db.uuid("account_id").notNull().references(() => mktSocialAccounts.id, { onDelete: "cascade" }),
20
+ name: db.text("name").notNull(),
21
+ status: db.text("status").notNull().default("active"),
22
+ weeksCount: db.integer("weeks_count").notNull().default(8),
23
+ startDate: db.date("start_date"),
24
+ objective: db.text("objective"),
25
+ tone: db.text("tone"),
26
+ pillars: db.jsonb("pillars").notNull().default([]),
27
+ formats: db.jsonb("formats").notNull().default([]),
28
+ weeklyFrequency: db.integer("weekly_frequency").notNull().default(3),
29
+ briefMd: db.text("brief_md").notNull().default(""),
30
+ ...db.timestamps
31
+ }, (t) => [
32
+ db.index("idx_plg_marketing_content_plans_tenant").on(t.tenantId),
33
+ db.index("idx_plg_marketing_content_plans_account").on(t.accountId)
34
+ ]);
35
+ var mktContentPosts = db.pgTable("plg_marketing_content_posts", {
36
+ id: db.uuid("id").primaryKey().defaultRandom(),
37
+ tenantId: db.tenantId(),
38
+ planId: db.uuid("plan_id").notNull().references(() => mktContentPlans.id, { onDelete: "cascade" }),
39
+ weekNumber: db.integer("week_number").notNull().default(1),
40
+ position: db.integer("position").notNull().default(0),
41
+ title: db.text("title").notNull(),
42
+ format: db.text("format").notNull().default("reel"),
43
+ status: db.text("status").notNull().default("idea"),
44
+ scheduledDate: db.date("scheduled_date"),
45
+ // Target platforms for this post; empty = inherit the account's platforms
46
+ platforms: db.text("platforms").array().notNull().default([]),
47
+ // Recording-day checklist [{ id, text, done }]
48
+ checklist: db.jsonb("checklist").notNull().default([]),
49
+ // Uploaded asset (static posts: caption + final art instead of a script)
50
+ mediaUrl: db.text("media_url"),
51
+ hook: db.text("hook"),
52
+ cta: db.text("cta"),
53
+ contentMd: db.text("content_md").notNull().default(""),
54
+ ...db.timestamps
55
+ }, (t) => [
56
+ db.index("idx_plg_marketing_content_posts_tenant").on(t.tenantId),
57
+ db.index("idx_plg_marketing_content_posts_plan").on(t.planId, t.weekNumber, t.position),
58
+ db.index("idx_plg_marketing_content_posts_status").on(t.tenantId, t.status)
59
+ ]);
60
+
61
+ exports.mktContentPlans = mktContentPlans;
62
+ exports.mktContentPosts = mktContentPosts;
63
+ exports.mktSocialAccounts = mktSocialAccounts;
64
+ //# sourceMappingURL=schema.cjs.map
65
+ //# sourceMappingURL=schema.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/schema/index.ts"],"names":["pgTable","uuid","tenantId","text","boolean","createdAt","index","integer","date","jsonb","timestamps"],"mappings":"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","file":"schema.cjs","sourcesContent":["// Builders come from @fayz-ai/db (single drizzle-orm instance) — never import\n// pgTable/uuid directly from drizzle-orm/pg-core. See MIGRATION-ARCHITECTURE §6.\nimport {\n pgTable, uuid, text, boolean, integer, date, jsonb, index,\n tenantId, timestamps, createdAt,\n} from '@fayz-ai/db'\n\n// ---------------------------------------------------------------------------\n// Marketing plugin — Ring-1 schema (schema-as-code), content planner tables.\n// RLS policies + grants are companion SQL (Drizzle doesn't diff those) —\n// see src/migrations/001_content_planner.sql.\n// ---------------------------------------------------------------------------\n\nexport const mktSocialAccounts = pgTable('plg_marketing_social_accounts', {\n id: uuid('id').primaryKey().defaultRandom(),\n tenantId: tenantId(),\n name: text('name').notNull(),\n handle: text('handle'),\n // One account, many platform connections (Instagram, YouTube, TikTok, ...)\n platforms: text('platforms').array().notNull().default(['instagram']),\n isActive: boolean('is_active').notNull().default(true),\n ...createdAt,\n}, (t) => [index('idx_plg_marketing_social_accounts_tenant').on(t.tenantId)])\n\nexport const mktContentPlans = pgTable('plg_marketing_content_plans', {\n id: uuid('id').primaryKey().defaultRandom(),\n tenantId: tenantId(),\n accountId: uuid('account_id').notNull().references(() => mktSocialAccounts.id, { onDelete: 'cascade' }),\n name: text('name').notNull(),\n status: text('status').notNull().default('active'),\n weeksCount: integer('weeks_count').notNull().default(8),\n startDate: date('start_date'),\n objective: text('objective'),\n tone: text('tone'),\n pillars: jsonb('pillars').notNull().default([]),\n formats: jsonb('formats').notNull().default([]),\n weeklyFrequency: integer('weekly_frequency').notNull().default(3),\n briefMd: text('brief_md').notNull().default(''),\n ...timestamps,\n}, (t) => [\n index('idx_plg_marketing_content_plans_tenant').on(t.tenantId),\n index('idx_plg_marketing_content_plans_account').on(t.accountId),\n])\n\nexport const mktContentPosts = pgTable('plg_marketing_content_posts', {\n id: uuid('id').primaryKey().defaultRandom(),\n tenantId: tenantId(),\n planId: uuid('plan_id').notNull().references(() => mktContentPlans.id, { onDelete: 'cascade' }),\n weekNumber: integer('week_number').notNull().default(1),\n position: integer('position').notNull().default(0),\n title: text('title').notNull(),\n format: text('format').notNull().default('reel'),\n status: text('status').notNull().default('idea'),\n scheduledDate: date('scheduled_date'),\n // Target platforms for this post; empty = inherit the account's platforms\n platforms: text('platforms').array().notNull().default([]),\n // Recording-day checklist [{ id, text, done }]\n checklist: jsonb('checklist').notNull().default([]),\n // Uploaded asset (static posts: caption + final art instead of a script)\n mediaUrl: text('media_url'),\n hook: text('hook'),\n cta: text('cta'),\n contentMd: text('content_md').notNull().default(''),\n ...timestamps,\n}, (t) => [\n index('idx_plg_marketing_content_posts_tenant').on(t.tenantId),\n index('idx_plg_marketing_content_posts_plan').on(t.planId, t.weekNumber, t.position),\n index('idx_plg_marketing_content_posts_status').on(t.tenantId, t.status),\n])\n"]}
package/dist/schema.js ADDED
@@ -0,0 +1,61 @@
1
+ import { pgTable, createdAt, boolean, text, tenantId, uuid, index, timestamps, integer, jsonb, date } from '@fayz-ai/db';
2
+
3
+ // src/schema/index.ts
4
+ var mktSocialAccounts = pgTable("plg_marketing_social_accounts", {
5
+ id: uuid("id").primaryKey().defaultRandom(),
6
+ tenantId: tenantId(),
7
+ name: text("name").notNull(),
8
+ handle: text("handle"),
9
+ // One account, many platform connections (Instagram, YouTube, TikTok, ...)
10
+ platforms: text("platforms").array().notNull().default(["instagram"]),
11
+ isActive: boolean("is_active").notNull().default(true),
12
+ ...createdAt
13
+ }, (t) => [index("idx_plg_marketing_social_accounts_tenant").on(t.tenantId)]);
14
+ var mktContentPlans = pgTable("plg_marketing_content_plans", {
15
+ id: uuid("id").primaryKey().defaultRandom(),
16
+ tenantId: tenantId(),
17
+ accountId: uuid("account_id").notNull().references(() => mktSocialAccounts.id, { onDelete: "cascade" }),
18
+ name: text("name").notNull(),
19
+ status: text("status").notNull().default("active"),
20
+ weeksCount: integer("weeks_count").notNull().default(8),
21
+ startDate: date("start_date"),
22
+ objective: text("objective"),
23
+ tone: text("tone"),
24
+ pillars: jsonb("pillars").notNull().default([]),
25
+ formats: jsonb("formats").notNull().default([]),
26
+ weeklyFrequency: integer("weekly_frequency").notNull().default(3),
27
+ briefMd: text("brief_md").notNull().default(""),
28
+ ...timestamps
29
+ }, (t) => [
30
+ index("idx_plg_marketing_content_plans_tenant").on(t.tenantId),
31
+ index("idx_plg_marketing_content_plans_account").on(t.accountId)
32
+ ]);
33
+ var mktContentPosts = pgTable("plg_marketing_content_posts", {
34
+ id: uuid("id").primaryKey().defaultRandom(),
35
+ tenantId: tenantId(),
36
+ planId: uuid("plan_id").notNull().references(() => mktContentPlans.id, { onDelete: "cascade" }),
37
+ weekNumber: integer("week_number").notNull().default(1),
38
+ position: integer("position").notNull().default(0),
39
+ title: text("title").notNull(),
40
+ format: text("format").notNull().default("reel"),
41
+ status: text("status").notNull().default("idea"),
42
+ scheduledDate: date("scheduled_date"),
43
+ // Target platforms for this post; empty = inherit the account's platforms
44
+ platforms: text("platforms").array().notNull().default([]),
45
+ // Recording-day checklist [{ id, text, done }]
46
+ checklist: jsonb("checklist").notNull().default([]),
47
+ // Uploaded asset (static posts: caption + final art instead of a script)
48
+ mediaUrl: text("media_url"),
49
+ hook: text("hook"),
50
+ cta: text("cta"),
51
+ contentMd: text("content_md").notNull().default(""),
52
+ ...timestamps
53
+ }, (t) => [
54
+ index("idx_plg_marketing_content_posts_tenant").on(t.tenantId),
55
+ index("idx_plg_marketing_content_posts_plan").on(t.planId, t.weekNumber, t.position),
56
+ index("idx_plg_marketing_content_posts_status").on(t.tenantId, t.status)
57
+ ]);
58
+
59
+ export { mktContentPlans, mktContentPosts, mktSocialAccounts };
60
+ //# sourceMappingURL=schema.js.map
61
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/schema/index.ts"],"names":[],"mappings":"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","file":"schema.js","sourcesContent":["// Builders come from @fayz-ai/db (single drizzle-orm instance) — never import\n// pgTable/uuid directly from drizzle-orm/pg-core. See MIGRATION-ARCHITECTURE §6.\nimport {\n pgTable, uuid, text, boolean, integer, date, jsonb, index,\n tenantId, timestamps, createdAt,\n} from '@fayz-ai/db'\n\n// ---------------------------------------------------------------------------\n// Marketing plugin — Ring-1 schema (schema-as-code), content planner tables.\n// RLS policies + grants are companion SQL (Drizzle doesn't diff those) —\n// see src/migrations/001_content_planner.sql.\n// ---------------------------------------------------------------------------\n\nexport const mktSocialAccounts = pgTable('plg_marketing_social_accounts', {\n id: uuid('id').primaryKey().defaultRandom(),\n tenantId: tenantId(),\n name: text('name').notNull(),\n handle: text('handle'),\n // One account, many platform connections (Instagram, YouTube, TikTok, ...)\n platforms: text('platforms').array().notNull().default(['instagram']),\n isActive: boolean('is_active').notNull().default(true),\n ...createdAt,\n}, (t) => [index('idx_plg_marketing_social_accounts_tenant').on(t.tenantId)])\n\nexport const mktContentPlans = pgTable('plg_marketing_content_plans', {\n id: uuid('id').primaryKey().defaultRandom(),\n tenantId: tenantId(),\n accountId: uuid('account_id').notNull().references(() => mktSocialAccounts.id, { onDelete: 'cascade' }),\n name: text('name').notNull(),\n status: text('status').notNull().default('active'),\n weeksCount: integer('weeks_count').notNull().default(8),\n startDate: date('start_date'),\n objective: text('objective'),\n tone: text('tone'),\n pillars: jsonb('pillars').notNull().default([]),\n formats: jsonb('formats').notNull().default([]),\n weeklyFrequency: integer('weekly_frequency').notNull().default(3),\n briefMd: text('brief_md').notNull().default(''),\n ...timestamps,\n}, (t) => [\n index('idx_plg_marketing_content_plans_tenant').on(t.tenantId),\n index('idx_plg_marketing_content_plans_account').on(t.accountId),\n])\n\nexport const mktContentPosts = pgTable('plg_marketing_content_posts', {\n id: uuid('id').primaryKey().defaultRandom(),\n tenantId: tenantId(),\n planId: uuid('plan_id').notNull().references(() => mktContentPlans.id, { onDelete: 'cascade' }),\n weekNumber: integer('week_number').notNull().default(1),\n position: integer('position').notNull().default(0),\n title: text('title').notNull(),\n format: text('format').notNull().default('reel'),\n status: text('status').notNull().default('idea'),\n scheduledDate: date('scheduled_date'),\n // Target platforms for this post; empty = inherit the account's platforms\n platforms: text('platforms').array().notNull().default([]),\n // Recording-day checklist [{ id, text, done }]\n checklist: jsonb('checklist').notNull().default([]),\n // Uploaded asset (static posts: caption + final art instead of a script)\n mediaUrl: text('media_url'),\n hook: text('hook'),\n cta: text('cta'),\n contentMd: text('content_md').notNull().default(''),\n ...timestamps,\n}, (t) => [\n index('idx_plg_marketing_content_posts_tenant').on(t.tenantId),\n index('idx_plg_marketing_content_posts_plan').on(t.planId, t.weekNumber, t.position),\n index('idx_plg_marketing_content_posts_status').on(t.tenantId, t.status),\n])\n"]}
@@ -0,0 +1,13 @@
1
+ import type { ResolvedMarketingConfig } from '../../MarketingContext';
2
+ import type { ContentPlannerProvider } from '../../data/contentTypes';
3
+ import type { ContentPlannerUIState } from './contentStore';
4
+ export declare const ContentPlannerContextProvider: import("react").FC<{
5
+ config: ResolvedMarketingConfig;
6
+ provider: ContentPlannerProvider;
7
+ store: import("zustand").StoreApi<ContentPlannerUIState>;
8
+ children?: React.ReactNode;
9
+ }>;
10
+ export declare const useContentPlannerConfig: () => ResolvedMarketingConfig;
11
+ export declare const useContentPlannerProvider: () => ContentPlannerProvider;
12
+ export declare const useContentPlannerStore: <T>(selector: (state: ContentPlannerUIState) => T) => T;
13
+ //# sourceMappingURL=ContentPlannerContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ContentPlannerContext.d.ts","sourceRoot":"","sources":["../../../src/views/content/ContentPlannerContext.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAA;AACrE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA;AACrE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AAM3D,eAAO,MAAM,6BAA6B;;;;;EAAsB,CAAA;AAChE,eAAO,MAAM,uBAAuB,+BAAgB,CAAA;AACpD,eAAO,MAAM,yBAAyB,8BAAkB,CAAA;AACxD,eAAO,MAAM,sBAAsB,yDAAe,CAAA"}
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+ export declare function ContentView({ onOpenPost }: {
3
+ onOpenPost: (id: string) => void;
4
+ }): React.JSX.Element;
5
+ //# sourceMappingURL=ContentView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ContentView.d.ts","sourceRoot":"","sources":["../../../src/views/content/ContentView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAwsBzB,wBAAgB,WAAW,CAAC,EAAE,UAAU,EAAE,EAAE;IAC1C,UAAU,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CACjC,qBA0LA"}
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ /** Full-page variant — kept for the /marketing/content/brief deep link. */
3
+ export declare function PlanBriefView({ onBack }: {
4
+ onBack: () => void;
5
+ }): React.JSX.Element;
6
+ /** Right slide-over variant — the board's Briefing button opens this, keeping
7
+ * the calendar in sight while the strategy is edited. */
8
+ export declare function PlanBriefSheet({ open, onClose }: {
9
+ open: boolean;
10
+ onClose: () => void;
11
+ }): React.JSX.Element;
12
+ //# sourceMappingURL=PlanBriefView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PlanBriefView.d.ts","sourceRoot":"","sources":["../../../src/views/content/PlanBriefView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAiNzB,2EAA2E;AAC3E,wBAAgB,aAAa,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,IAAI,CAAA;CAAE,qBAwB/D;AAED;0DAC0D;AAC1D,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,qBA6BvF"}
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ export declare function PostPage({ postId, onBack }: {
3
+ postId: string;
4
+ onBack: () => void;
5
+ }): React.JSX.Element;
6
+ //# sourceMappingURL=PostPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PostPage.d.ts","sourceRoot":"","sources":["../../../src/views/content/PostPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAqHzB,wBAAgB,QAAQ,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,IAAI,CAAA;CAAE,qBA4PlF"}
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ interface PreviewProps {
3
+ caption: string;
4
+ mediaUrl?: string;
5
+ accountName: string;
6
+ accountHandle?: string;
7
+ platforms: string[];
8
+ }
9
+ export declare function PostSocialPreview(props: PreviewProps): React.JSX.Element;
10
+ export {};
11
+ //# sourceMappingURL=PostSocialPreview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PostSocialPreview.d.ts","sourceRoot":"","sources":["../../../src/views/content/PostSocialPreview.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAgBzB,UAAU,YAAY;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,SAAS,EAAE,MAAM,EAAE,CAAA;CACpB;AA6JD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,YAAY,qBAgCpD"}
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ import type { ContentPost } from '../../data/contentTypes';
3
+ export declare function RecordingChecklist({ post }: {
4
+ post: ContentPost;
5
+ }): React.JSX.Element;
6
+ //# sourceMappingURL=RecordingChecklist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RecordingChecklist.d.ts","sourceRoot":"","sources":["../../../src/views/content/RecordingChecklist.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAIzB,OAAO,KAAK,EAAiB,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAgBzE,wBAAgB,kBAAkB,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,qBA4HjE"}
@@ -0,0 +1,28 @@
1
+ import React from 'react';
2
+ import type { PostFormat, PostStatus } from '../../data/contentTypes';
3
+ import { POST_STATUS_ORDER } from '../../data/contentTypes';
4
+ export declare const POST_FORMATS: PostFormat[];
5
+ export { POST_STATUS_ORDER };
6
+ export declare const POST_STATUS_COLOR: Record<PostStatus, string>;
7
+ /** Small status dot (agenda/CRM convention) for selects and lists. */
8
+ export declare function StatusDot({ status, className }: {
9
+ status: PostStatus;
10
+ className?: string;
11
+ }): React.JSX.Element;
12
+ export declare function FormatBadge({ format }: {
13
+ format: PostFormat;
14
+ }): React.JSX.Element;
15
+ export declare function PostStatusBadge({ status }: {
16
+ status: PostStatus;
17
+ }): React.JSX.Element;
18
+ /** Compact row of platform glyphs (post targets or account connections). */
19
+ export declare function PlatformBadges({ platforms, className }: {
20
+ platforms: string[];
21
+ className?: string;
22
+ }): React.JSX.Element | null;
23
+ /** Toggleable platform chips — used by the add-account form and the post page. */
24
+ export declare function PlatformPicker({ value, onChange }: {
25
+ value: string[];
26
+ onChange: (platforms: string[]) => void;
27
+ }): React.JSX.Element;
28
+ //# sourceMappingURL=contentBits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contentBits.d.ts","sourceRoot":"","sources":["../../../src/views/content/contentBits.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AACrE,OAAO,EAAa,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAGtE,eAAO,MAAM,YAAY,EAAE,UAAU,EAAwE,CAAA;AAC7G,OAAO,EAAE,iBAAiB,EAAE,CAAA;AAe5B,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAOxD,CAAA;AAED,sEAAsE;AACtE,wBAAgB,SAAS,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,qBAO1F;AAWD,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,UAAU,CAAA;CAAE,qBAO7D;AAED,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,UAAU,CAAA;CAAE,qBAOjE;AAED,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,4BASnG;AAED,kFAAkF;AAClF,wBAAgB,cAAc,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE;IAClD,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;CACxC,qBA2BA"}
@@ -0,0 +1,28 @@
1
+ import { type StoreApi } from 'zustand/vanilla';
2
+ import type { ContentPlan, ContentPlannerProvider, ContentPost, SaveContentPlanInput, SaveContentPostInput, SaveSocialAccountInput, SocialAccount } from '../../data/contentTypes';
3
+ export type BoardViewMode = 'board' | 'calendar' | 'list';
4
+ export interface ContentPlannerUIState {
5
+ loaded: boolean;
6
+ loading: boolean;
7
+ /** Tenant the current data was loaded for. The tenant hydrates async after
8
+ * login/refresh, so views re-load when this diverges from the active one. */
9
+ loadedTenantId: string | null;
10
+ /** Board / calendar / list — survives opening a post and coming back. */
11
+ viewMode: BoardViewMode;
12
+ setViewMode(mode: BoardViewMode): void;
13
+ accounts: SocialAccount[];
14
+ activeAccountId: string | null;
15
+ plans: ContentPlan[];
16
+ activePlan: ContentPlan | null;
17
+ posts: ContentPost[];
18
+ load(): Promise<void>;
19
+ setAccount(accountId: string): Promise<void>;
20
+ selectPlan(planId: string): Promise<void>;
21
+ saveAccount(input: SaveSocialAccountInput): Promise<SocialAccount>;
22
+ deleteAccount(id: string): Promise<void>;
23
+ savePlan(input: SaveContentPlanInput): Promise<ContentPlan>;
24
+ savePost(input: SaveContentPostInput): Promise<ContentPost>;
25
+ deletePost(id: string): Promise<void>;
26
+ }
27
+ export declare function createContentPlannerStore(provider: ContentPlannerProvider): StoreApi<ContentPlannerUIState>;
28
+ //# sourceMappingURL=contentStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contentStore.d.ts","sourceRoot":"","sources":["../../../src/views/content/contentStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAE5D,OAAO,KAAK,EACV,WAAW,EACX,sBAAsB,EACtB,WAAW,EACX,oBAAoB,EACpB,oBAAoB,EACpB,sBAAsB,EACtB,aAAa,EACd,MAAM,yBAAyB,CAAA;AAQhC,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,CAAA;AAEzD,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,OAAO,CAAA;IACf,OAAO,EAAE,OAAO,CAAA;IAChB;kFAC8E;IAC9E,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,yEAAyE;IACzE,QAAQ,EAAE,aAAa,CAAA;IACvB,WAAW,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CAAA;IACtC,QAAQ,EAAE,aAAa,EAAE,CAAA;IACzB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,KAAK,EAAE,WAAW,EAAE,CAAA;IACpB,UAAU,EAAE,WAAW,GAAG,IAAI,CAAA;IAC9B,KAAK,EAAE,WAAW,EAAE,CAAA;IAEpB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACrB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5C,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACzC,WAAW,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,aAAa,CAAC,CAAA;IAClE,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACxC,QAAQ,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAC3D,QAAQ,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAC3D,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACtC;AAED,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,sBAAsB,GAC/B,QAAQ,CAAC,qBAAqB,CAAC,CAgHjC"}
@@ -0,0 +1,3 @@
1
+ import type { PostFormat } from '../../data/contentTypes';
2
+ export declare const POST_TEMPLATES: Record<PostFormat, (title: string) => string>;
3
+ //# sourceMappingURL=templates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../../src/views/content/templates.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AAmKzD,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAQxE,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "name": "@fayz-ai/plugin-marketing",
3
- "version": "0.2.0",
3
+ "fayz": {
4
+ "status": "beta"
5
+ },
6
+ "version": "0.8.0",
4
7
  "description": "Fayz SDK — marketing campaigns & broadcasts plugin",
5
8
  "type": "module",
6
9
  "main": "./dist/index.cjs",
@@ -12,6 +15,12 @@
12
15
  "types": "./dist/index.d.ts",
13
16
  "import": "./dist/index.js",
14
17
  "require": "./dist/index.cjs"
18
+ },
19
+ "./schema": {
20
+ "source": "./src/schema/index.ts",
21
+ "types": "./dist/schema/index.d.ts",
22
+ "import": "./dist/schema.js",
23
+ "require": "./dist/schema.cjs"
15
24
  }
16
25
  },
17
26
  "files": [
@@ -26,9 +35,10 @@
26
35
  "@tanstack/react-table": "^8.20.0",
27
36
  "lucide-react": "^0.400.0",
28
37
  "zustand": "^4.5.0",
29
- "@fayz-ai/core": "^0.6.6",
30
- "@fayz-ai/saas": "^0.6.6",
31
- "@fayz-ai/ui": "^0.6.6"
38
+ "@fayz-ai/ui": "^0.8.0",
39
+ "@fayz-ai/core": "^0.8.0",
40
+ "@fayz-ai/saas": "^0.8.0",
41
+ "@fayz-ai/db": "^0.8.0"
32
42
  },
33
43
  "devDependencies": {
34
44
  "@types/react": "^18.3.0",
@@ -43,6 +53,9 @@
43
53
  "fayz-plugin",
44
54
  "fayz-sdk"
45
55
  ],
56
+ "publishConfig": {
57
+ "access": "public"
58
+ },
46
59
  "scripts": {
47
60
  "build": "tsup && tsc --emitDeclarationOnly --declaration --declarationMap --noEmit false",
48
61
  "dev": "tsup --watch",
@@ -14,6 +14,7 @@ export interface MarketingLabels {
14
14
  campaigns: string
15
15
  funnel: string
16
16
  landingPages: string
17
+ content: string
17
18
  settings: string
18
19
  }
19
20
 
@@ -6,6 +6,7 @@ import type { PluginQuickAction } from '@fayz-ai/core'
6
6
  import { useModuleNavigation, ModuleActionBar, createViewRouter } from '@fayz-ai/saas'
7
7
  import { MarketingContextProvider, type ResolvedMarketingConfig } from './MarketingContext'
8
8
  import type { MarketingDataProvider } from './data/types'
9
+ import type { ContentPlannerProvider } from './data/contentTypes'
9
10
  import type { MarketingUIState } from './store'
10
11
  import { OverviewView } from './views/OverviewView'
11
12
  import { ChannelsView } from './views/ChannelsView'
@@ -13,6 +14,11 @@ import { ChannelDetailView } from './views/ChannelDetailView'
13
14
  import { CampaignsView } from './views/CampaignsView'
14
15
  import { FunnelView } from './views/FunnelView'
15
16
  import { LandingPagesView } from './views/LandingPagesView'
17
+ import { ContentPlannerContextProvider } from './views/content/ContentPlannerContext'
18
+ import type { ContentPlannerUIState } from './views/content/contentStore'
19
+ import { ContentView } from './views/content/ContentView'
20
+ import { PostPage } from './views/content/PostPage'
21
+ import { PlanBriefView } from './views/content/PlanBriefView'
16
22
 
17
23
  function buildNav(
18
24
  config: ResolvedMarketingConfig,
@@ -38,18 +44,27 @@ function buildNav(
38
44
  if (config.modules.landingPages) {
39
45
  items.push({ id: 'landing-pages', label: config.labels.landingPages, icon: 'LayoutTemplate', active: view === 'landing-pages', onClick: () => navigate('landing-pages') })
40
46
  }
47
+ if (config.modules.contentPlanner) {
48
+ items.push({
49
+ id: 'content', label: config.labels.content, icon: 'Clapperboard',
50
+ active: view === 'content' || view === 'content-brief' || view.startsWith('content-post:'),
51
+ onClick: () => navigate('content'),
52
+ })
53
+ }
41
54
  return items
42
55
  }
43
56
 
44
- export function MarketingPage({ config, provider, store }: {
57
+ export function MarketingPage({ config, provider, store, contentProvider, contentStore }: {
45
58
  config: ResolvedMarketingConfig
46
59
  provider: MarketingDataProvider
47
60
  store: StoreApi<MarketingUIState>
61
+ contentProvider: ContentPlannerProvider
62
+ contentStore: StoreApi<ContentPlannerUIState>
48
63
  }) {
49
64
  const t = useTranslation()
50
65
  const { view, direction, navigate } = useModuleNavigation('/marketing', {
51
- overview: 0, channels: 0, campaigns: 0, funnel: 0, 'landing-pages': 0,
52
- 'channel-detail': 1,
66
+ overview: 0, channels: 0, campaigns: 0, funnel: 0, 'landing-pages': 0, content: 0,
67
+ 'channel-detail': 1, 'content-post': 1, 'content-brief': 1,
53
68
  }, 'overview')
54
69
 
55
70
  React.useEffect(() => { void store.getState().load() }, [store])
@@ -67,11 +82,15 @@ export function MarketingPage({ config, provider, store }: {
67
82
  { id: 'campaigns', render: () => <CampaignsView /> },
68
83
  { id: 'funnel', render: () => <FunnelView /> },
69
84
  { id: 'landing-pages', render: () => <LandingPagesView /> },
85
+ { id: 'content', render: () => <ContentView onOpenPost={(id) => navigate(`content-post:${id}`)} /> },
86
+ { id: 'content-post', render: ({ id }) => <PostPage postId={id!} onBack={() => navigate('content')} /> },
87
+ { id: 'content-brief', render: () => <PlanBriefView onBack={() => navigate('content')} /> },
70
88
  { id: 'overview', render: () => <OverviewView /> },
71
89
  ], 'overview')
72
90
 
73
91
  return (
74
92
  <MarketingContextProvider config={config} provider={provider} store={store}>
93
+ <ContentPlannerContextProvider config={config} provider={contentProvider} store={contentStore}>
75
94
  <ModulePage
76
95
  title={config.labels.pageTitle}
77
96
  subtitle={config.labels.pageSubtitle}
@@ -89,6 +108,7 @@ export function MarketingPage({ config, provider, store }: {
89
108
  >
90
109
  {renderView(view)}
91
110
  </ModulePage>
111
+ </ContentPlannerContextProvider>
92
112
  </MarketingContextProvider>
93
113
  )
94
114
  }
@@ -2,6 +2,7 @@ import React from 'react'
2
2
  import {
3
3
  Search, Megaphone, Globe, Mail, Users, MousePointerClick,
4
4
  Instagram, MessageCircle, DoorOpen, Utensils, Bike, Link2,
5
+ Youtube, Facebook, Linkedin, Twitter, Music2, Pin, AtSign,
5
6
  } from 'lucide-react'
6
7
 
7
8
  // Channel icon names referenced by presets → lucide components. Falls back to
@@ -15,3 +16,21 @@ export function ChannelIcon({ name, className }: { name: string; className?: str
15
16
  const Icon = ICONS[name] ?? Link2
16
17
  return <Icon className={className} />
17
18
  }
19
+
20
+ // Platform icons for the content planner. Lucide has no TikTok / Pinterest /
21
+ // Threads glyphs — Music2 / Pin / AtSign are the conventional stand-ins.
22
+ const PLATFORM_ICONS: Record<string, React.ComponentType<{ className?: string }>> = {
23
+ instagram: Instagram,
24
+ tiktok: Music2,
25
+ youtube: Youtube,
26
+ facebook: Facebook,
27
+ linkedin: Linkedin,
28
+ x: Twitter,
29
+ pinterest: Pin,
30
+ threads: AtSign,
31
+ }
32
+
33
+ export function PlatformIcon({ platform, className }: { platform: string; className?: string }) {
34
+ const Icon = PLATFORM_ICONS[platform] ?? Link2
35
+ return <Icon className={className} />
36
+ }
@@ -0,0 +1,25 @@
1
+ import { getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core'
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Media asset upload for content posts (static art, covers). Public bucket
5
+ // `mkt-media` (created by migration 003); path content/{tenantId}/{postId}-{ts}.{ext}
6
+ // so re-uploads never collide and old URLs keep resolving until replaced.
7
+ // ---------------------------------------------------------------------------
8
+
9
+ export function canUploadMedia(): boolean {
10
+ return !!getSupabaseClientOptional()
11
+ }
12
+
13
+ export async function uploadPostMedia(postId: string, file: File): Promise<string> {
14
+ const supabase = getSupabaseClientOptional() as any
15
+ if (!supabase) throw new Error('Supabase not initialized')
16
+ const tenantId = getActiveTenantId()
17
+ if (!tenantId) throw new Error('No active tenant')
18
+
19
+ const ext = file.name.split('.').pop()?.toLowerCase() ?? 'bin'
20
+ const path = `content/${tenantId}/${postId}-${Date.now()}.${ext}`
21
+ const { error } = await supabase.storage.from('mkt-media').upload(path, file, { upsert: true })
22
+ if (error) throw error
23
+ const { data } = supabase.storage.from('mkt-media').getPublicUrl(path)
24
+ return data.publicUrl as string
25
+ }