@fayz-ai/plugin-marketing 0.9.0-next.1 → 0.9.1
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 +3 -3
- package/dist/{MarketingContext.d.ts → context.d.ts} +5 -3
- package/dist/context.d.ts.map +1 -0
- package/dist/data/mock.d.ts +1 -1
- package/dist/data/mock.d.ts.map +1 -1
- package/dist/{registries.d.ts → data/registries.d.ts} +1 -1
- package/dist/data/registries.d.ts.map +1 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -22
- package/dist/index.js.map +1 -1
- package/dist/lib/format.d.ts.map +1 -0
- package/dist/{presets.d.ts → lib/presets.d.ts} +1 -1
- package/dist/lib/presets.d.ts.map +1 -0
- package/dist/{MarketingPage.d.ts → views/MarketingPage.d.ts} +5 -5
- package/dist/views/MarketingPage.d.ts.map +1 -0
- package/dist/views/blog/BlogAdminView.d.ts.map +1 -1
- package/dist/views/content/ContentPlannerContext.d.ts +1 -1
- package/dist/views/content/ContentPlannerContext.d.ts.map +1 -1
- package/dist/views/dashboardWidgets.d.ts +1 -1
- package/dist/views/dashboardWidgets.d.ts.map +1 -1
- package/package.json +6 -9
- package/dist/MarketingContext.d.ts.map +0 -1
- package/dist/MarketingPage.d.ts.map +0 -1
- package/dist/format.d.ts.map +0 -1
- package/dist/presets.d.ts.map +0 -1
- package/dist/registries.d.ts.map +0 -1
- package/src/MarketingContext.tsx +0 -42
- package/src/MarketingPage.tsx +0 -139
- package/src/components/MarketingBits.tsx +0 -131
- package/src/components/icons.tsx +0 -36
- package/src/data/blogEntities.tsx +0 -91
- package/src/data/capability.test.ts +0 -92
- package/src/data/contentMedia.ts +0 -25
- package/src/data/contentMock.ts +0 -253
- package/src/data/contentSupabase.ts +0 -263
- package/src/data/contentTypes.ts +0 -138
- package/src/data/mock.ts +0 -308
- package/src/data/supabase.ts +0 -613
- package/src/data/tables.ts +0 -33
- package/src/data/types.ts +0 -97
- package/src/format.ts +0 -40
- package/src/index.ts +0 -457
- package/src/integrations/ranklayer/RANKLAYER.md +0 -40
- package/src/integrations/ranklayer/connectorDef.tsx +0 -102
- package/src/integrations/ranklayer/data/supabase.ts +0 -106
- package/src/integrations/ranklayer/types.ts +0 -16
- package/src/locales/en.ts +0 -192
- package/src/locales/index.ts +0 -7
- package/src/locales/pt-BR.ts +0 -192
- package/src/migrations/000_plg_rename.sql +0 -15
- package/src/migrations/001_content_planner.sql +0 -94
- package/src/migrations/002_multi_platform.sql +0 -25
- package/src/migrations/003_recording_ops.sql +0 -25
- package/src/migrations/004_ranklayer.sql +0 -50
- package/src/migrations/005_analytics_base.sql +0 -60
- package/src/migrations/006_analytics_views.sql +0 -68
- package/src/migrations/007_agent_rpcs.sql +0 -119
- package/src/migrations/008_landing_pages.sql +0 -112
- package/src/migrations/index.ts +0 -601
- package/src/presets.ts +0 -119
- package/src/registries.ts +0 -62
- package/src/schema/index.ts +0 -106
- package/src/store.ts +0 -102
- package/src/types.ts +0 -153
- package/src/views/CampaignComposer.tsx +0 -89
- package/src/views/CampaignsView.tsx +0 -59
- package/src/views/ChannelDetailView.tsx +0 -59
- package/src/views/ChannelsView.tsx +0 -46
- package/src/views/FunnelView.tsx +0 -56
- package/src/views/LandingPageComposer.tsx +0 -201
- package/src/views/LandingPagesView.tsx +0 -172
- package/src/views/OverviewView.tsx +0 -24
- package/src/views/SettingsView.tsx +0 -72
- package/src/views/blog/BlogAdminView.tsx +0 -99
- package/src/views/blog/BlogEntityForm.tsx +0 -101
- package/src/views/blog/useEntityCrud.ts +0 -50
- package/src/views/content/ContentPlannerContext.tsx +0 -13
- package/src/views/content/ContentView.tsx +0 -908
- package/src/views/content/PlanBriefView.tsx +0 -268
- package/src/views/content/PostPage.tsx +0 -370
- package/src/views/content/PostSocialPreview.tsx +0 -212
- package/src/views/content/RecordingChecklist.tsx +0 -145
- package/src/views/content/contentBits.tsx +0 -114
- package/src/views/content/contentStore.ts +0 -160
- package/src/views/content/templates.ts +0 -172
- package/src/views/dashboardWidgets.tsx +0 -173
- /package/dist/{format.d.ts → lib/format.d.ts} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/lib/format.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACf;AAED,eAAO,MAAM,gBAAgB,EAAE,iBAAiE,CAAA;AAEhG,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,GAAG,MAAM,CAUjF;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,SAAI,GAAG,MAAM,CAE/D;AAED,kDAAkD;AAClD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGnD;AAED,0DAA0D;AAC1D,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,CAAA;CAAE,CAI7G"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../../src/lib/presets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AASnE,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAA;AAE3D,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,OAAO,CAAA;IACjB,SAAS,EAAE,OAAO,CAAA;IAClB,MAAM,EAAE,OAAO,CAAA;IACf,YAAY,EAAE,OAAO,CAAA;IACrB,0EAA0E;IAC1E,cAAc,EAAE,OAAO,CAAA;IACvB,6FAA6F;IAC7F,IAAI,EAAE,OAAO,CAAA;IACb,6EAA6E;IAC7E,UAAU,EAAE,OAAO,CAAA;IACnB,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,eAAe,CAAA;IAC3B,QAAQ,EAAE,kBAAkB,EAAE,CAAA;IAC9B,OAAO,EAAE,sBAAsB,CAAA;CAChC;AA4DD,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,eAAe,EAAE,qBAAqB,CAI5E,CAAA;AAED,mEAAmE;AACnE,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,EAAE,CAsBjE,CAAA"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { StoreApi } from 'zustand/vanilla';
|
|
3
|
-
import { type ResolvedMarketingConfig } from '
|
|
4
|
-
import type { MarketingDataProvider } from '
|
|
5
|
-
import type { ContentPlannerProvider } from '
|
|
6
|
-
import type { MarketingUIState } from '
|
|
7
|
-
import type { ContentPlannerUIState } from './
|
|
3
|
+
import { type ResolvedMarketingConfig } from '../context';
|
|
4
|
+
import type { MarketingDataProvider } from '../data/types';
|
|
5
|
+
import type { ContentPlannerProvider } from '../data/contentTypes';
|
|
6
|
+
import type { MarketingUIState } from '../store';
|
|
7
|
+
import type { ContentPlannerUIState } from './content/contentStore';
|
|
8
8
|
export declare function MarketingPage({ config, provider, store, contentProvider, contentStore }: {
|
|
9
9
|
config: ResolvedMarketingConfig;
|
|
10
10
|
provider: MarketingDataProvider;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MarketingPage.d.ts","sourceRoot":"","sources":["../../src/views/MarketingPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAK/C,OAAO,EAA4B,KAAK,uBAAuB,EAAE,MAAM,YAAY,CAAA;AACnF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAC1D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAClE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAQhD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAkDnE,wBAAgB,aAAa,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,EAAE;IACxF,MAAM,EAAE,uBAAuB,CAAA;IAC/B,QAAQ,EAAE,qBAAqB,CAAA;IAC/B,KAAK,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAA;IACjC,eAAe,EAAE,sBAAsB,CAAA;IACvC,YAAY,EAAE,QAAQ,CAAC,qBAAqB,CAAC,CAAA;CAC9C,qBAkEA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BlogAdminView.d.ts","sourceRoot":"","sources":["../../../src/views/blog/BlogAdminView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"BlogAdminView.d.ts","sourceRoot":"","sources":["../../../src/views/blog/BlogAdminView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAA;AAmF3D,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,qBAgB/E"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ResolvedMarketingConfig } from '../../
|
|
1
|
+
import type { ResolvedMarketingConfig } from '../../context';
|
|
2
2
|
import type { ContentPlannerProvider } from '../../data/contentTypes';
|
|
3
3
|
import type { ContentPlannerUIState } from './contentStore';
|
|
4
4
|
export declare const ContentPlannerContextProvider: import("react").FC<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContentPlannerContext.d.ts","sourceRoot":"","sources":["../../../src/views/content/ContentPlannerContext.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"ContentPlannerContext.d.ts","sourceRoot":"","sources":["../../../src/views/content/ContentPlannerContext.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAA;AAC5D,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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { StoreApi } from 'zustand';
|
|
2
2
|
import type { DashboardWidgetDef } from '@fayz-ai/core';
|
|
3
|
-
import { type ResolvedMarketingConfig } from '../
|
|
3
|
+
import { type ResolvedMarketingConfig } from '../context';
|
|
4
4
|
import type { MarketingDataProvider } from '../data/types';
|
|
5
5
|
import type { MarketingUIState } from '../store';
|
|
6
6
|
export declare function createMarketingDashboardWidgets(ctx: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboardWidgets.d.ts","sourceRoot":"","sources":["../../src/views/dashboardWidgets.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AACvC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAMvD,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,
|
|
1
|
+
{"version":3,"file":"dashboardWidgets.d.ts","sourceRoot":"","sources":["../../src/views/dashboardWidgets.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AACvC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAMvD,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,YAAY,CAAA;AACnB,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAC1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAkIhD,wBAAgB,+BAA+B,CAAC,GAAG,EAAE;IACnD,MAAM,EAAE,uBAAuB,CAAA;IAC/B,QAAQ,EAAE,qBAAqB,CAAA;IAC/B,KAAK,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAA;CAClC,GAAG,kBAAkB,EAAE,CAuBvB"}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"fayz": {
|
|
4
4
|
"status": "beta"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.9.
|
|
6
|
+
"version": "0.9.1",
|
|
7
7
|
"description": "Fayz SDK — marketing campaigns & broadcasts plugin",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"sideEffects": false,
|
|
@@ -12,20 +12,17 @@
|
|
|
12
12
|
"types": "./dist/index.d.ts",
|
|
13
13
|
"exports": {
|
|
14
14
|
".": {
|
|
15
|
-
"source": "./src/index.ts",
|
|
16
15
|
"types": "./dist/index.d.ts",
|
|
17
16
|
"import": "./dist/index.js"
|
|
18
17
|
},
|
|
19
18
|
"./schema": {
|
|
20
|
-
"source": "./src/schema/index.ts",
|
|
21
19
|
"types": "./dist/schema/index.d.ts",
|
|
22
20
|
"import": "./dist/schema.js",
|
|
23
21
|
"require": "./dist/schema.cjs"
|
|
24
22
|
}
|
|
25
23
|
},
|
|
26
24
|
"files": [
|
|
27
|
-
"dist"
|
|
28
|
-
"src"
|
|
25
|
+
"dist"
|
|
29
26
|
],
|
|
30
27
|
"peerDependencies": {
|
|
31
28
|
"react": "^18.0.0 || ^19.0.0",
|
|
@@ -35,10 +32,10 @@
|
|
|
35
32
|
},
|
|
36
33
|
"dependencies": {
|
|
37
34
|
"@tanstack/react-table": "^8.20.0",
|
|
38
|
-
"@fayz-ai/core": "^0.
|
|
39
|
-
"@fayz-ai/
|
|
40
|
-
"@fayz-ai/
|
|
41
|
-
"@fayz-ai/db": "^0.
|
|
35
|
+
"@fayz-ai/core": "^0.10.0",
|
|
36
|
+
"@fayz-ai/admin": "^0.10.0",
|
|
37
|
+
"@fayz-ai/ui": "^0.10.0",
|
|
38
|
+
"@fayz-ai/db": "^0.10.0"
|
|
42
39
|
},
|
|
43
40
|
"devDependencies": {
|
|
44
41
|
"@types/react": "^18.3.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"MarketingContext.d.ts","sourceRoot":"","sources":["../src/MarketingContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAClE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AACjD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAA;AACvD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE/C,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,cAAc,EAAE,MAAM,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,eAAe,CAAA;IAC3B,QAAQ,EAAE,kBAAkB,EAAE,CAAA;IAC9B,OAAO,EAAE,sBAAsB,CAAA;IAC/B,QAAQ,EAAE,iBAAiB,CAAA;IAC3B,MAAM,EAAE,eAAe,CAAA;CACxB;AAID,eAAO,MAAM,wBAAwB;;;;;EAAsB,CAAA;AAC3D,eAAO,MAAM,kBAAkB,+BAAgB,CAAA;AAC/C,eAAO,MAAM,oBAAoB,6BAAkB,CAAA;AACnD,eAAO,MAAM,iBAAiB,oDAAe,CAAA;AAE7C,uDAAuD;AACvD,wBAAgB,gBAAgB,IAAI,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAGlE"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"MarketingPage.d.ts","sourceRoot":"","sources":["../src/MarketingPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAK/C,OAAO,EAA4B,KAAK,uBAAuB,EAAE,MAAM,oBAAoB,CAAA;AAC3F,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACzD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AACjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAQ/C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAA;AAkDzE,wBAAgB,aAAa,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,EAAE;IACxF,MAAM,EAAE,uBAAuB,CAAA;IAC/B,QAAQ,EAAE,qBAAqB,CAAA;IAC/B,KAAK,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAA;IACjC,eAAe,EAAE,sBAAsB,CAAA;IACvC,YAAY,EAAE,QAAQ,CAAC,qBAAqB,CAAC,CAAA;CAC9C,qBAiEA"}
|
package/dist/format.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACf;AAED,eAAO,MAAM,gBAAgB,EAAE,iBAAiE,CAAA;AAEhG,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,GAAG,MAAM,CAUjF;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,SAAI,GAAG,MAAM,CAE/D;AAED,kDAAkD;AAClD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGnD;AAED,0DAA0D;AAC1D,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,CAAA;CAAE,CAI7G"}
|
package/dist/presets.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../src/presets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AASlE,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAA;AAE3D,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,OAAO,CAAA;IACjB,SAAS,EAAE,OAAO,CAAA;IAClB,MAAM,EAAE,OAAO,CAAA;IACf,YAAY,EAAE,OAAO,CAAA;IACrB,0EAA0E;IAC1E,cAAc,EAAE,OAAO,CAAA;IACvB,6FAA6F;IAC7F,IAAI,EAAE,OAAO,CAAA;IACb,6EAA6E;IAC7E,UAAU,EAAE,OAAO,CAAA;IACnB,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,eAAe,CAAA;IAC3B,QAAQ,EAAE,kBAAkB,EAAE,CAAA;IAC9B,OAAO,EAAE,sBAAsB,CAAA;CAChC;AA4DD,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,eAAe,EAAE,qBAAqB,CAI5E,CAAA;AAED,mEAAmE;AACnE,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,EAAE,CAsBjE,CAAA"}
|
package/dist/registries.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"registries.d.ts","sourceRoot":"","sources":["../src/registries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAa,MAAM,eAAe,CAAA;AACjE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA;AAwCjD;;;;0EAI0E;AAC1E,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,kBAAkB,EAAE,GAAG,iBAAiB,EAAE,CAe5F"}
|
package/src/MarketingContext.tsx
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { createPluginContext } from '@fayz-ai/saas'
|
|
3
|
-
import type { AcquisitionChannel, ConversionModel } from './types'
|
|
4
|
-
import type { MarketingCurrency } from './format'
|
|
5
|
-
import type { MarketingDomainModules } from './presets'
|
|
6
|
-
import type { MarketingDataProvider } from './data/types'
|
|
7
|
-
import type { MarketingUIState } from './store'
|
|
8
|
-
|
|
9
|
-
export interface MarketingLabels {
|
|
10
|
-
pageTitle: string
|
|
11
|
-
pageSubtitle: string
|
|
12
|
-
overview: string
|
|
13
|
-
channels: string
|
|
14
|
-
campaigns: string
|
|
15
|
-
funnel: string
|
|
16
|
-
landingPages: string
|
|
17
|
-
content: string
|
|
18
|
-
blog: string
|
|
19
|
-
blogCategories: string
|
|
20
|
-
settings: string
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface ResolvedMarketingConfig {
|
|
24
|
-
conversion: ConversionModel
|
|
25
|
-
channels: AcquisitionChannel[]
|
|
26
|
-
modules: MarketingDomainModules
|
|
27
|
-
currency: MarketingCurrency
|
|
28
|
-
labels: MarketingLabels
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const ctx = createPluginContext<ResolvedMarketingConfig, MarketingDataProvider, MarketingUIState>('MarketingPage')
|
|
32
|
-
|
|
33
|
-
export const MarketingContextProvider = ctx.ContextProvider
|
|
34
|
-
export const useMarketingConfig = ctx.useConfig
|
|
35
|
-
export const useMarketingProvider = ctx.useProvider
|
|
36
|
-
export const useMarketingStore = ctx.useStore
|
|
37
|
-
|
|
38
|
-
/** Convenience: look up a channel definition by id. */
|
|
39
|
-
export function useChannelLookup(): Map<string, AcquisitionChannel> {
|
|
40
|
-
const { channels } = useMarketingConfig()
|
|
41
|
-
return React.useMemo(() => new Map(channels.map((c) => [c.id, c])), [channels])
|
|
42
|
-
}
|
package/src/MarketingPage.tsx
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import type { StoreApi } from 'zustand/vanilla'
|
|
3
|
-
import { ModulePage, type ModuleNavItem } from '@fayz-ai/ui'
|
|
4
|
-
import { useTranslation, useDataChanged } from '@fayz-ai/core'
|
|
5
|
-
import type { PluginQuickAction } from '@fayz-ai/core'
|
|
6
|
-
import { useModuleNavigation, ModuleActionBar, createViewRouter } from '@fayz-ai/saas'
|
|
7
|
-
import { MarketingContextProvider, type ResolvedMarketingConfig } from './MarketingContext'
|
|
8
|
-
import type { MarketingDataProvider } from './data/types'
|
|
9
|
-
import type { ContentPlannerProvider } from './data/contentTypes'
|
|
10
|
-
import type { MarketingUIState } from './store'
|
|
11
|
-
import { OverviewView } from './views/OverviewView'
|
|
12
|
-
import { ChannelsView } from './views/ChannelsView'
|
|
13
|
-
import { ChannelDetailView } from './views/ChannelDetailView'
|
|
14
|
-
import { CampaignsView } from './views/CampaignsView'
|
|
15
|
-
import { FunnelView } from './views/FunnelView'
|
|
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'
|
|
22
|
-
import { BlogAdminView } from './views/blog/BlogAdminView'
|
|
23
|
-
import { BlogEntityForm } from './views/blog/BlogEntityForm'
|
|
24
|
-
import { buildBlogPostEntity, buildBlogCategoryEntity } from './data/blogEntities'
|
|
25
|
-
|
|
26
|
-
function buildNav(
|
|
27
|
-
config: ResolvedMarketingConfig,
|
|
28
|
-
view: string,
|
|
29
|
-
navigate: (v: string) => void,
|
|
30
|
-
): ModuleNavItem[] {
|
|
31
|
-
const items: ModuleNavItem[] = [
|
|
32
|
-
{ id: 'overview', label: config.labels.overview, icon: 'BarChart3', active: view === 'overview', onClick: () => navigate('overview') },
|
|
33
|
-
]
|
|
34
|
-
if (config.modules.channels) {
|
|
35
|
-
items.push({
|
|
36
|
-
id: 'channels', label: config.labels.channels, icon: 'Radio',
|
|
37
|
-
active: view === 'channels' || view.startsWith('channel-detail:'),
|
|
38
|
-
onClick: () => navigate('channels'),
|
|
39
|
-
})
|
|
40
|
-
}
|
|
41
|
-
if (config.modules.campaigns) {
|
|
42
|
-
items.push({ id: 'campaigns', label: config.labels.campaigns, icon: 'Megaphone', active: view === 'campaigns', onClick: () => navigate('campaigns') })
|
|
43
|
-
}
|
|
44
|
-
if (config.modules.funnel) {
|
|
45
|
-
items.push({ id: 'funnel', label: config.labels.funnel, icon: 'Filter', active: view === 'funnel', onClick: () => navigate('funnel') })
|
|
46
|
-
}
|
|
47
|
-
if (config.modules.landingPages) {
|
|
48
|
-
items.push({ id: 'landing-pages', label: config.labels.landingPages, icon: 'LayoutTemplate', active: view === 'landing-pages', onClick: () => navigate('landing-pages') })
|
|
49
|
-
}
|
|
50
|
-
if (config.modules.contentPlanner) {
|
|
51
|
-
items.push({
|
|
52
|
-
id: 'content', label: config.labels.content, icon: 'Clapperboard',
|
|
53
|
-
active: view === 'content' || view === 'content-brief' || view.startsWith('content-post:'),
|
|
54
|
-
onClick: () => navigate('content'),
|
|
55
|
-
})
|
|
56
|
-
}
|
|
57
|
-
if (config.modules.blog) {
|
|
58
|
-
items.push({
|
|
59
|
-
id: 'blog', label: config.labels.blog, icon: 'Newspaper',
|
|
60
|
-
active: view === 'blog' || view === 'blog-new' || view.startsWith('blog-post:')
|
|
61
|
-
|| view === 'blog-cat-new' || view.startsWith('blog-cat:'),
|
|
62
|
-
onClick: () => navigate('blog'),
|
|
63
|
-
})
|
|
64
|
-
}
|
|
65
|
-
return items
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function MarketingPage({ config, provider, store, contentProvider, contentStore }: {
|
|
69
|
-
config: ResolvedMarketingConfig
|
|
70
|
-
provider: MarketingDataProvider
|
|
71
|
-
store: StoreApi<MarketingUIState>
|
|
72
|
-
contentProvider: ContentPlannerProvider
|
|
73
|
-
contentStore: StoreApi<ContentPlannerUIState>
|
|
74
|
-
}) {
|
|
75
|
-
const t = useTranslation()
|
|
76
|
-
const { view, direction, navigate } = useModuleNavigation('/marketing', {
|
|
77
|
-
overview: 0, channels: 0, campaigns: 0, funnel: 0, 'landing-pages': 0, content: 0, blog: 0,
|
|
78
|
-
'channel-detail': 1, 'content-post': 1, 'content-brief': 1,
|
|
79
|
-
'blog-new': 1, 'blog-post': 1, 'blog-cat-new': 1, 'blog-cat': 1,
|
|
80
|
-
}, 'overview')
|
|
81
|
-
|
|
82
|
-
React.useEffect(() => { void store.getState().load() }, [store])
|
|
83
|
-
|
|
84
|
-
// Agent RPC writes (createCampaign) land outside the provider — refresh past
|
|
85
|
-
// its read cache when any agent write is announced.
|
|
86
|
-
useDataChanged({ table: 'plg_marketing_campaigns' }, () => { void store.getState().refresh() }, [store])
|
|
87
|
-
|
|
88
|
-
const isOverview = view === 'overview'
|
|
89
|
-
const nav = buildNav(config, view, navigate)
|
|
90
|
-
|
|
91
|
-
// Blog backoffice entities — one shared EntityDef each drives the list + form.
|
|
92
|
-
const blogPostEntity = React.useMemo(() => buildBlogPostEntity(), [])
|
|
93
|
-
const blogCategoryEntity = React.useMemo(() => buildBlogCategoryEntity(), [])
|
|
94
|
-
|
|
95
|
-
const quickActions = React.useMemo<PluginQuickAction[]>(() => [
|
|
96
|
-
{ id: 'new-campaign', label: t('marketing.campaigns.new'), icon: 'Megaphone', description: config.labels.campaigns, action: () => navigate('campaigns') },
|
|
97
|
-
], [t, config.labels.campaigns])
|
|
98
|
-
|
|
99
|
-
const renderView = createViewRouter([
|
|
100
|
-
{ id: 'channels', render: () => <ChannelsView onOpen={(id) => navigate(`channel-detail:${id}`)} /> },
|
|
101
|
-
{ id: 'channel-detail', render: ({ id }) => <ChannelDetailView channelId={id!} onBack={() => navigate('channels')} /> },
|
|
102
|
-
{ id: 'campaigns', render: () => <CampaignsView /> },
|
|
103
|
-
{ id: 'funnel', render: () => <FunnelView /> },
|
|
104
|
-
{ id: 'landing-pages', render: () => <LandingPagesView /> },
|
|
105
|
-
{ id: 'content', render: () => <ContentView onOpenPost={(id) => navigate(`content-post:${id}`)} /> },
|
|
106
|
-
{ id: 'content-post', render: ({ id }) => <PostPage postId={id!} onBack={() => navigate('content')} /> },
|
|
107
|
-
{ id: 'content-brief', render: () => <PlanBriefView onBack={() => navigate('content')} /> },
|
|
108
|
-
{ id: 'blog', render: () => <BlogAdminView navigate={navigate} /> },
|
|
109
|
-
{ id: 'blog-new', render: () => <BlogEntityForm entity={blogPostEntity} parentLabel={config.labels.blog} onDone={() => navigate('blog')} /> },
|
|
110
|
-
{ id: 'blog-post', render: ({ id }) => <BlogEntityForm entity={blogPostEntity} editId={id!} parentLabel={config.labels.blog} onDone={() => navigate('blog')} /> },
|
|
111
|
-
{ id: 'blog-cat-new', render: () => <BlogEntityForm entity={blogCategoryEntity} parentLabel={config.labels.blogCategories} onDone={() => navigate('blog')} /> },
|
|
112
|
-
{ id: 'blog-cat', render: ({ id }) => <BlogEntityForm entity={blogCategoryEntity} editId={id!} parentLabel={config.labels.blogCategories} onDone={() => navigate('blog')} /> },
|
|
113
|
-
{ id: 'overview', render: () => <OverviewView /> },
|
|
114
|
-
], 'overview')
|
|
115
|
-
|
|
116
|
-
return (
|
|
117
|
-
<MarketingContextProvider config={config} provider={provider} store={store}>
|
|
118
|
-
<ContentPlannerContextProvider config={config} provider={contentProvider} store={contentStore}>
|
|
119
|
-
<ModulePage
|
|
120
|
-
title={config.labels.pageTitle}
|
|
121
|
-
subtitle={config.labels.pageSubtitle}
|
|
122
|
-
nav={nav}
|
|
123
|
-
showHeader={isOverview}
|
|
124
|
-
viewKey={view}
|
|
125
|
-
direction={direction}
|
|
126
|
-
headerAction={
|
|
127
|
-
<ModuleActionBar
|
|
128
|
-
quickActions={quickActions}
|
|
129
|
-
settingsPath="/settings/marketing"
|
|
130
|
-
settingsLabel={config.labels.pageTitle}
|
|
131
|
-
/>
|
|
132
|
-
}
|
|
133
|
-
>
|
|
134
|
-
{renderView(view)}
|
|
135
|
-
</ModulePage>
|
|
136
|
-
</ContentPlannerContextProvider>
|
|
137
|
-
</MarketingContextProvider>
|
|
138
|
-
)
|
|
139
|
-
}
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { ArrowDownRight, ArrowUpRight } from 'lucide-react'
|
|
3
|
-
import { cn, SegmentedControl } from '@fayz-ai/ui'
|
|
4
|
-
import { useTranslation } from '@fayz-ai/core'
|
|
5
|
-
import type { CampaignStatus, DateRangeKey } from '../types'
|
|
6
|
-
import { trendDelta } from '../format'
|
|
7
|
-
import { useMarketingStore } from '../MarketingContext'
|
|
8
|
-
import { ChannelIcon } from './icons'
|
|
9
|
-
import { useChannelLookup } from '../MarketingContext'
|
|
10
|
-
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
// KPI card with optional trend chip.
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
|
|
15
|
-
export function KpiCard({
|
|
16
|
-
icon: Icon, label, value, sub, current, previous, invertTrend,
|
|
17
|
-
}: {
|
|
18
|
-
icon: React.ComponentType<{ className?: string }>
|
|
19
|
-
label: string
|
|
20
|
-
value: string
|
|
21
|
-
sub?: string
|
|
22
|
-
current?: number
|
|
23
|
-
previous?: number
|
|
24
|
-
/** for cost-style metrics where down is good (e.g. CPA) */
|
|
25
|
-
invertTrend?: boolean
|
|
26
|
-
}) {
|
|
27
|
-
const trend = current != null && previous != null ? trendDelta(current, previous) : null
|
|
28
|
-
const good = trend ? (invertTrend ? trend.dir === 'down' : trend.dir === 'up') : false
|
|
29
|
-
const bad = trend ? (invertTrend ? trend.dir === 'up' : trend.dir === 'down') : false
|
|
30
|
-
return (
|
|
31
|
-
<div className="rounded-card border border-border bg-card p-4">
|
|
32
|
-
<div className="flex items-center gap-2 text-muted-foreground">
|
|
33
|
-
<Icon className="h-4 w-4" />
|
|
34
|
-
<span className="text-xs font-medium">{label}</span>
|
|
35
|
-
</div>
|
|
36
|
-
<div className="mt-2 flex items-end justify-between gap-2">
|
|
37
|
-
<p className="text-2xl font-semibold text-foreground">{value}</p>
|
|
38
|
-
{trend && trend.dir !== 'neutral' && (
|
|
39
|
-
<span className={cn(
|
|
40
|
-
'mb-0.5 inline-flex items-center gap-0.5 text-xs font-medium',
|
|
41
|
-
good && 'text-emerald-600', bad && 'text-rose-600',
|
|
42
|
-
!good && !bad && 'text-muted-foreground',
|
|
43
|
-
)}>
|
|
44
|
-
{trend.dir === 'up' ? <ArrowUpRight className="h-3 w-3" /> : <ArrowDownRight className="h-3 w-3" />}
|
|
45
|
-
{trend.pct.toFixed(0)}%
|
|
46
|
-
</span>
|
|
47
|
-
)}
|
|
48
|
-
</div>
|
|
49
|
-
{sub && <p className="mt-0.5 text-xs text-muted-foreground">{sub}</p>}
|
|
50
|
-
</div>
|
|
51
|
-
)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// ---------------------------------------------------------------------------
|
|
55
|
-
// Date-range selector (segmented). Reads/writes the shared store range.
|
|
56
|
-
// ---------------------------------------------------------------------------
|
|
57
|
-
|
|
58
|
-
const RANGES: DateRangeKey[] = ['7d', '30d', '90d']
|
|
59
|
-
|
|
60
|
-
export function RangeTabs() {
|
|
61
|
-
const range = useMarketingStore((s) => s.range)
|
|
62
|
-
const setRange = useMarketingStore((s) => s.setRange)
|
|
63
|
-
return (
|
|
64
|
-
<SegmentedControl
|
|
65
|
-
options={RANGES}
|
|
66
|
-
value={range}
|
|
67
|
-
onChange={(r) => void setRange(r)}
|
|
68
|
-
aria-label="Time range"
|
|
69
|
-
/>
|
|
70
|
-
)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// ---------------------------------------------------------------------------
|
|
74
|
-
// Status badge for campaigns.
|
|
75
|
-
// ---------------------------------------------------------------------------
|
|
76
|
-
|
|
77
|
-
const STATUS_STYLE: Record<CampaignStatus, string> = {
|
|
78
|
-
active: 'bg-emerald-100 text-emerald-700',
|
|
79
|
-
paused: 'bg-amber-100 text-amber-700',
|
|
80
|
-
ended: 'bg-muted text-muted-foreground',
|
|
81
|
-
draft: 'bg-slate-100 text-slate-600',
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export function StatusBadge({ status }: { status: CampaignStatus }) {
|
|
85
|
-
const t = useTranslation()
|
|
86
|
-
return (
|
|
87
|
-
<span className={cn('rounded-full px-2 py-0.5 text-xs font-medium capitalize', STATUS_STYLE[status])}>
|
|
88
|
-
{t(`marketing.status.${status}`)}
|
|
89
|
-
</span>
|
|
90
|
-
)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// ---------------------------------------------------------------------------
|
|
94
|
-
// Channel cell (icon + label) used in tables.
|
|
95
|
-
// ---------------------------------------------------------------------------
|
|
96
|
-
|
|
97
|
-
export function ChannelCell({ channelId }: { channelId: string }) {
|
|
98
|
-
const lookup = useChannelLookup()
|
|
99
|
-
const ch = lookup.get(channelId)
|
|
100
|
-
return (
|
|
101
|
-
<span className="inline-flex items-center gap-1.5 text-foreground">
|
|
102
|
-
<ChannelIcon name={ch?.icon ?? 'Link2'} className="h-3.5 w-3.5 text-muted-foreground" />
|
|
103
|
-
{ch?.label ?? channelId}
|
|
104
|
-
</span>
|
|
105
|
-
)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// ---------------------------------------------------------------------------
|
|
109
|
-
// Horizontal proportion bar (funnel / channel-mix), no charting dep.
|
|
110
|
-
// ---------------------------------------------------------------------------
|
|
111
|
-
|
|
112
|
-
export function ProportionBar({ value, max, color, label, right }: {
|
|
113
|
-
value: number
|
|
114
|
-
max: number
|
|
115
|
-
color: string
|
|
116
|
-
label: React.ReactNode
|
|
117
|
-
right?: React.ReactNode
|
|
118
|
-
}) {
|
|
119
|
-
const pct = max > 0 ? Math.max((value / max) * 100, 2) : 0
|
|
120
|
-
return (
|
|
121
|
-
<div className="flex items-center gap-3">
|
|
122
|
-
<div className="w-32 shrink-0 text-sm text-muted-foreground">{label}</div>
|
|
123
|
-
<div className="h-7 flex-1 overflow-hidden rounded-md bg-muted/40">
|
|
124
|
-
<div className="flex h-full items-center rounded-md px-2 text-xs font-semibold"
|
|
125
|
-
style={{ width: `${pct}%`, backgroundColor: color + '26', color }}>
|
|
126
|
-
{right}
|
|
127
|
-
</div>
|
|
128
|
-
</div>
|
|
129
|
-
</div>
|
|
130
|
-
)
|
|
131
|
-
}
|
package/src/components/icons.tsx
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import {
|
|
3
|
-
Search, Megaphone, Globe, Mail, Users, MousePointerClick,
|
|
4
|
-
Instagram, MessageCircle, DoorOpen, Utensils, Bike, Link2,
|
|
5
|
-
Youtube, Facebook, Linkedin, Twitter, Music2, Pin, AtSign,
|
|
6
|
-
} from 'lucide-react'
|
|
7
|
-
|
|
8
|
-
// Channel icon names referenced by presets → lucide components. Falls back to
|
|
9
|
-
// a generic link icon for anything unmapped.
|
|
10
|
-
const ICONS: Record<string, React.ComponentType<{ className?: string }>> = {
|
|
11
|
-
Search, Megaphone, Globe, Mail, Users, MousePointerClick,
|
|
12
|
-
Instagram, MessageCircle, DoorOpen, Utensils, Bike,
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function ChannelIcon({ name, className }: { name: string; className?: string }) {
|
|
16
|
-
const Icon = ICONS[name] ?? Link2
|
|
17
|
-
return <Icon className={className} />
|
|
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
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import type { EntityDef, FieldDef } from '@fayz-ai/core'
|
|
2
|
-
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
// Blog backoffice entities — the single source of truth that drives BOTH the
|
|
5
|
-
// generic list (CrudListView columns + facets) and the create/edit form
|
|
6
|
-
// (CrudFormPage fields, incl. the markdown body + category relation).
|
|
7
|
-
//
|
|
8
|
-
// Persistence is the generic Supabase provider resolved from `data.table`
|
|
9
|
-
// (plg_blog_posts / plg_blog_categories, owned by @fayz-ai/plugin-blog's
|
|
10
|
-
// migrations). camelCase field keys map to snake_case columns automatically.
|
|
11
|
-
// The website reads the same rows through the anon v_public_blog_posts view.
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
|
|
14
|
-
export const BLOG_POSTS_TABLE = 'plg_blog_posts'
|
|
15
|
-
export const BLOG_CATEGORIES_TABLE = 'plg_blog_categories'
|
|
16
|
-
|
|
17
|
-
export interface BlogPostRow {
|
|
18
|
-
id: string
|
|
19
|
-
title: string
|
|
20
|
-
slug: string
|
|
21
|
-
excerpt: string
|
|
22
|
-
body: string
|
|
23
|
-
categoryId?: string
|
|
24
|
-
coverImage?: string
|
|
25
|
-
authorName?: string
|
|
26
|
-
authorRole?: string
|
|
27
|
-
authorAvatarUrl?: string
|
|
28
|
-
authorBio?: string
|
|
29
|
-
status: 'draft' | 'published'
|
|
30
|
-
readTime?: string
|
|
31
|
-
publishedAt?: string
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface BlogCategoryRow {
|
|
35
|
-
id: string
|
|
36
|
-
name: string
|
|
37
|
-
slug: string
|
|
38
|
-
description?: string
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function buildBlogPostEntity(): EntityDef<BlogPostRow> {
|
|
42
|
-
const fields: FieldDef[] = [
|
|
43
|
-
{ key: 'title', label: 'Title', type: 'text', required: true, searchable: true, showInTable: true, sortable: true },
|
|
44
|
-
{ key: 'slug', label: 'Slug', type: 'text', required: true, searchable: true, showInTable: false, hint: 'URL identifier, e.g. "cannabis-and-oral-health"' },
|
|
45
|
-
{ key: 'status', label: 'Status', type: 'select', options: ['draft', 'published'], showInTable: true, defaultValue: 'draft' },
|
|
46
|
-
{ key: 'categoryId', label: 'Category', type: 'relation', relation: { table: BLOG_CATEGORIES_TABLE, valueField: 'id', labelField: 'name', tenantScoped: true }, showInTable: false },
|
|
47
|
-
{ key: 'excerpt', label: 'Excerpt', type: 'textarea', span: 2, showInTable: false, placeholder: 'Short summary shown on cards' },
|
|
48
|
-
{ key: 'body', label: 'Body', type: 'markdown', span: 2, showInTable: false },
|
|
49
|
-
{ key: 'coverImage', label: 'Cover image', type: 'image', showInTable: false, placeholder: 'https://…' },
|
|
50
|
-
{ key: 'readTime', label: 'Read time', type: 'text', showInTable: false, placeholder: '5 min' },
|
|
51
|
-
{ key: 'publishedAt', label: 'Published at', type: 'date', showInTable: true, sortable: true },
|
|
52
|
-
{ key: 'authorName', label: 'Author name', type: 'text', showInTable: false },
|
|
53
|
-
{ key: 'authorRole', label: 'Author role', type: 'text', showInTable: false, placeholder: 'e.g. Cirurgião-Dentista' },
|
|
54
|
-
{ key: 'authorAvatarUrl', label: 'Author avatar', type: 'image', showInTable: false, placeholder: 'https://…' },
|
|
55
|
-
{ key: 'authorBio', label: 'Author bio', type: 'textarea', span: 2, showInTable: false },
|
|
56
|
-
]
|
|
57
|
-
return {
|
|
58
|
-
name: 'Post',
|
|
59
|
-
namePlural: 'Posts',
|
|
60
|
-
icon: 'Newspaper',
|
|
61
|
-
fields,
|
|
62
|
-
displayField: 'title',
|
|
63
|
-
subtitleField: 'slug',
|
|
64
|
-
facets: [{ field: 'status', allLabel: 'All' }],
|
|
65
|
-
data: {
|
|
66
|
-
table: BLOG_POSTS_TABLE,
|
|
67
|
-
tenantScoped: true,
|
|
68
|
-
searchColumns: ['title', 'slug', 'excerpt'],
|
|
69
|
-
},
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export function buildBlogCategoryEntity(): EntityDef<BlogCategoryRow> {
|
|
74
|
-
const fields: FieldDef[] = [
|
|
75
|
-
{ key: 'name', label: 'Name', type: 'text', required: true, searchable: true, showInTable: true, sortable: true },
|
|
76
|
-
{ key: 'slug', label: 'Slug', type: 'text', required: true, searchable: true, showInTable: true, hint: 'URL identifier' },
|
|
77
|
-
{ key: 'description', label: 'Description', type: 'textarea', span: 2, showInTable: false },
|
|
78
|
-
]
|
|
79
|
-
return {
|
|
80
|
-
name: 'Category',
|
|
81
|
-
namePlural: 'Categories',
|
|
82
|
-
icon: 'Tags',
|
|
83
|
-
fields,
|
|
84
|
-
displayField: 'name',
|
|
85
|
-
data: {
|
|
86
|
-
table: BLOG_CATEGORIES_TABLE,
|
|
87
|
-
tenantScoped: true,
|
|
88
|
-
searchColumns: ['name', 'slug'],
|
|
89
|
-
},
|
|
90
|
-
}
|
|
91
|
-
}
|