@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 @@
|
|
|
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"}
|
package/dist/schema.cjs
ADDED
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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
|
-
"
|
|
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/
|
|
30
|
-
"@fayz-ai/
|
|
31
|
-
"@fayz-ai/
|
|
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",
|
package/src/MarketingContext.tsx
CHANGED
package/src/MarketingPage.tsx
CHANGED
|
@@ -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
|
}
|
package/src/components/icons.tsx
CHANGED
|
@@ -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
|
+
}
|