@bernierllc/email-campaign-management 1.0.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/.eslintrc.cjs +45 -0
- package/README.md +316 -0
- package/__tests__/api/campaigns.test.ts +217 -0
- package/__tests__/api/client.test.ts +330 -0
- package/__tests__/components/CampaignBuilder.test.tsx +103 -0
- package/__tests__/components/CampaignDashboard.test.tsx +89 -0
- package/__tests__/components/CampaignList.test.tsx +144 -0
- package/__tests__/components/MetricsOverview.test.tsx +200 -0
- package/__tests__/components/PerformanceChart.test.tsx +206 -0
- package/__tests__/hooks/useCampaignStore.test.ts +450 -0
- package/__tests__/hooks/useWorkflowValidation.test.ts +176 -0
- package/__tests__/utils/formatting.test.ts +48 -0
- package/__tests__/utils/validation.test.ts +199 -0
- package/__tests__/utils/workflow-helpers.test.ts +134 -0
- package/coverage/clover.xml +314 -0
- package/coverage/coverage-final.json +16 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +221 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov-report/src/api/campaigns.ts.html +199 -0
- package/coverage/lcov-report/src/api/client.ts.html +478 -0
- package/coverage/lcov-report/src/api/index.html +131 -0
- package/coverage/lcov-report/src/components/CampaignBuilder/index.html +116 -0
- package/coverage/lcov-report/src/components/CampaignBuilder/index.tsx.html +454 -0
- package/coverage/lcov-report/src/components/CampaignDashboard/MetricsOverview.tsx.html +208 -0
- package/coverage/lcov-report/src/components/CampaignDashboard/PerformanceChart.tsx.html +232 -0
- package/coverage/lcov-report/src/components/CampaignDashboard/index.html +146 -0
- package/coverage/lcov-report/src/components/CampaignDashboard/index.tsx.html +241 -0
- package/coverage/lcov-report/src/components/CampaignList/index.html +116 -0
- package/coverage/lcov-report/src/components/CampaignList/index.tsx.html +244 -0
- package/coverage/lcov-report/src/config.ts.html +202 -0
- package/coverage/lcov-report/src/hooks/index.html +146 -0
- package/coverage/lcov-report/src/hooks/useCampaignMetrics.ts.html +208 -0
- package/coverage/lcov-report/src/hooks/useCampaignStore.ts.html +343 -0
- package/coverage/lcov-report/src/hooks/useWorkflowValidation.ts.html +136 -0
- package/coverage/lcov-report/src/index.html +116 -0
- package/coverage/lcov-report/src/types/index.html +116 -0
- package/coverage/lcov-report/src/types/index.ts.html +127 -0
- package/coverage/lcov-report/src/utils/formatting.ts.html +163 -0
- package/coverage/lcov-report/src/utils/index.html +146 -0
- package/coverage/lcov-report/src/utils/validation.ts.html +394 -0
- package/coverage/lcov-report/src/utils/workflow-helpers.ts.html +277 -0
- package/coverage/lcov.info +657 -0
- package/dist/api/campaigns.d.ts +9 -0
- package/dist/api/campaigns.js +38 -0
- package/dist/api/client.d.ts +14 -0
- package/dist/api/client.js +116 -0
- package/dist/components/CampaignBuilder/index.d.ts +8 -0
- package/dist/components/CampaignBuilder/index.js +88 -0
- package/dist/components/CampaignDashboard/MetricsOverview.d.ts +6 -0
- package/dist/components/CampaignDashboard/MetricsOverview.js +34 -0
- package/dist/components/CampaignDashboard/PerformanceChart.d.ts +7 -0
- package/dist/components/CampaignDashboard/PerformanceChart.js +45 -0
- package/dist/components/CampaignDashboard/index.d.ts +5 -0
- package/dist/components/CampaignDashboard/index.js +44 -0
- package/dist/components/CampaignList/index.d.ts +6 -0
- package/dist/components/CampaignList/index.js +68 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.js +31 -0
- package/dist/hooks/useCampaignMetrics.d.ts +2 -0
- package/dist/hooks/useCampaignMetrics.js +42 -0
- package/dist/hooks/useCampaignStore.d.ts +14 -0
- package/dist/hooks/useCampaignStore.js +105 -0
- package/dist/hooks/useWorkflowValidation.d.ts +3 -0
- package/dist/hooks/useWorkflowValidation.js +17 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +52 -0
- package/dist/types/abtest.d.ts +15 -0
- package/dist/types/abtest.js +9 -0
- package/dist/types/audience.d.ts +18 -0
- package/dist/types/audience.js +9 -0
- package/dist/types/campaign.d.ts +31 -0
- package/dist/types/campaign.js +9 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.js +29 -0
- package/dist/types/metrics.d.ts +27 -0
- package/dist/types/metrics.js +9 -0
- package/dist/types/schedule.d.ts +15 -0
- package/dist/types/schedule.js +9 -0
- package/dist/types/workflow.d.ts +37 -0
- package/dist/types/workflow.js +9 -0
- package/dist/utils/formatting.d.ts +4 -0
- package/dist/utils/formatting.js +28 -0
- package/dist/utils/validation.d.ts +8 -0
- package/dist/utils/validation.js +81 -0
- package/dist/utils/workflow-helpers.d.ts +12 -0
- package/dist/utils/workflow-helpers.js +62 -0
- package/jest.config.cjs +33 -0
- package/jest.setup.cjs +9 -0
- package/package.json +72 -0
- package/src/api/campaigns.ts +38 -0
- package/src/api/client.ts +131 -0
- package/src/components/CampaignBuilder/index.tsx +123 -0
- package/src/components/CampaignDashboard/MetricsOverview.tsx +41 -0
- package/src/components/CampaignDashboard/PerformanceChart.tsx +49 -0
- package/src/components/CampaignDashboard/index.tsx +52 -0
- package/src/components/CampaignList/index.tsx +53 -0
- package/src/config.ts +39 -0
- package/src/hooks/useCampaignMetrics.ts +41 -0
- package/src/hooks/useCampaignStore.ts +86 -0
- package/src/hooks/useWorkflowValidation.ts +17 -0
- package/src/index.ts +32 -0
- package/src/types/abtest.ts +25 -0
- package/src/types/audience.ts +30 -0
- package/src/types/campaign.ts +44 -0
- package/src/types/index.ts +14 -0
- package/src/types/metrics.ts +36 -0
- package/src/types/schedule.ts +26 -0
- package/src/types/workflow.ts +53 -0
- package/src/utils/formatting.ts +26 -0
- package/src/utils/validation.ts +103 -0
- package/src/utils/workflow-helpers.ts +64 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (c) 2025 Bernier LLC
|
|
4
|
+
|
|
5
|
+
This file is licensed to the client under a limited-use license.
|
|
6
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
7
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.apiClient = exports.APIClient = void 0;
|
|
11
|
+
const config_1 = require("../config");
|
|
12
|
+
class APIClient {
|
|
13
|
+
constructor(baseUrl) {
|
|
14
|
+
this.baseUrl = baseUrl || (0, config_1.getConfig)().apiUrl;
|
|
15
|
+
}
|
|
16
|
+
async get(endpoint) {
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch(`${this.baseUrl}${endpoint}`, {
|
|
19
|
+
method: 'GET',
|
|
20
|
+
headers: {
|
|
21
|
+
'Content-Type': 'application/json'
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
return {
|
|
26
|
+
success: false,
|
|
27
|
+
error: `HTTP ${response.status}: ${response.statusText}`
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const data = await response.json();
|
|
31
|
+
return { success: true, data };
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
return {
|
|
35
|
+
success: false,
|
|
36
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async post(endpoint, body) {
|
|
41
|
+
try {
|
|
42
|
+
const response = await fetch(`${this.baseUrl}${endpoint}`, {
|
|
43
|
+
method: 'POST',
|
|
44
|
+
headers: {
|
|
45
|
+
'Content-Type': 'application/json'
|
|
46
|
+
},
|
|
47
|
+
body: JSON.stringify(body)
|
|
48
|
+
});
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
return {
|
|
51
|
+
success: false,
|
|
52
|
+
error: `HTTP ${response.status}: ${response.statusText}`
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const data = await response.json();
|
|
56
|
+
return { success: true, data };
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
return {
|
|
60
|
+
success: false,
|
|
61
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async put(endpoint, body) {
|
|
66
|
+
try {
|
|
67
|
+
const response = await fetch(`${this.baseUrl}${endpoint}`, {
|
|
68
|
+
method: 'PUT',
|
|
69
|
+
headers: {
|
|
70
|
+
'Content-Type': 'application/json'
|
|
71
|
+
},
|
|
72
|
+
body: JSON.stringify(body)
|
|
73
|
+
});
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
return {
|
|
76
|
+
success: false,
|
|
77
|
+
error: `HTTP ${response.status}: ${response.statusText}`
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const data = await response.json();
|
|
81
|
+
return { success: true, data };
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
return {
|
|
85
|
+
success: false,
|
|
86
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async delete(endpoint) {
|
|
91
|
+
try {
|
|
92
|
+
const response = await fetch(`${this.baseUrl}${endpoint}`, {
|
|
93
|
+
method: 'DELETE',
|
|
94
|
+
headers: {
|
|
95
|
+
'Content-Type': 'application/json'
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
if (!response.ok) {
|
|
99
|
+
return {
|
|
100
|
+
success: false,
|
|
101
|
+
error: `HTTP ${response.status}: ${response.statusText}`
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const data = await response.json();
|
|
105
|
+
return { success: true, data };
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
return {
|
|
109
|
+
success: false,
|
|
110
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
exports.APIClient = APIClient;
|
|
116
|
+
exports.apiClient = new APIClient();
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Campaign } from '../../types';
|
|
3
|
+
export interface CampaignBuilderProps {
|
|
4
|
+
campaign?: Campaign;
|
|
5
|
+
onSave: (campaign: Partial<Campaign>) => void;
|
|
6
|
+
onPublish?: (campaign: Campaign) => void;
|
|
7
|
+
}
|
|
8
|
+
export declare const CampaignBuilder: React.FC<CampaignBuilderProps>;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (c) 2025 Bernier LLC
|
|
4
|
+
|
|
5
|
+
This file is licensed to the client under a limited-use license.
|
|
6
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
7
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.CampaignBuilder = void 0;
|
|
44
|
+
const react_1 = __importStar(require("react"));
|
|
45
|
+
const workflow_helpers_1 = require("../../utils/workflow-helpers");
|
|
46
|
+
const CampaignBuilder = ({ campaign, onSave, onPublish }) => {
|
|
47
|
+
const [name, setName] = (0, react_1.useState)(campaign?.name || '');
|
|
48
|
+
const [description, setDescription] = (0, react_1.useState)(campaign?.description || '');
|
|
49
|
+
const [workflow] = (0, react_1.useState)(campaign?.workflow || (0, workflow_helpers_1.getDefaultWorkflow)());
|
|
50
|
+
const [activeTab, setActiveTab] = (0, react_1.useState)('workflow');
|
|
51
|
+
const handleSave = () => {
|
|
52
|
+
onSave({
|
|
53
|
+
name,
|
|
54
|
+
description,
|
|
55
|
+
workflow,
|
|
56
|
+
type: 'one-time',
|
|
57
|
+
status: 'draft'
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
return (react_1.default.createElement("div", { className: "campaign-builder" },
|
|
61
|
+
react_1.default.createElement("div", { className: "campaign-header" },
|
|
62
|
+
react_1.default.createElement("input", { type: "text", value: name, onChange: (e) => setName(e.target.value), placeholder: "Campaign Name", className: "campaign-name-input" }),
|
|
63
|
+
react_1.default.createElement("textarea", { value: description, onChange: (e) => setDescription(e.target.value), placeholder: "Campaign Description", className: "campaign-description-input" })),
|
|
64
|
+
react_1.default.createElement("div", { className: "campaign-tabs" },
|
|
65
|
+
react_1.default.createElement("button", { className: activeTab === 'workflow' ? 'active' : '', onClick: () => setActiveTab('workflow') }, "Workflow"),
|
|
66
|
+
react_1.default.createElement("button", { className: activeTab === 'audience' ? 'active' : '', onClick: () => setActiveTab('audience') }, "Audience"),
|
|
67
|
+
react_1.default.createElement("button", { className: activeTab === 'schedule' ? 'active' : '', onClick: () => setActiveTab('schedule') }, "Schedule"),
|
|
68
|
+
react_1.default.createElement("button", { className: activeTab === 'abtest' ? 'active' : '', onClick: () => setActiveTab('abtest') }, "A/B Test")),
|
|
69
|
+
react_1.default.createElement("div", { className: "campaign-content" },
|
|
70
|
+
activeTab === 'workflow' && (react_1.default.createElement("div", { className: "workflow-tab" },
|
|
71
|
+
react_1.default.createElement("p", null, "Workflow editor placeholder"),
|
|
72
|
+
react_1.default.createElement("p", null,
|
|
73
|
+
"Nodes: ",
|
|
74
|
+
workflow.nodes.length),
|
|
75
|
+
react_1.default.createElement("p", null,
|
|
76
|
+
"Edges: ",
|
|
77
|
+
workflow.edges.length))),
|
|
78
|
+
activeTab === 'audience' && (react_1.default.createElement("div", { className: "audience-tab" },
|
|
79
|
+
react_1.default.createElement("p", null, "Audience selector placeholder"))),
|
|
80
|
+
activeTab === 'schedule' && (react_1.default.createElement("div", { className: "schedule-tab" },
|
|
81
|
+
react_1.default.createElement("p", null, "Schedule settings placeholder"))),
|
|
82
|
+
activeTab === 'abtest' && (react_1.default.createElement("div", { className: "abtest-tab" },
|
|
83
|
+
react_1.default.createElement("p", null, "A/B test configuration placeholder")))),
|
|
84
|
+
react_1.default.createElement("div", { className: "campaign-actions" },
|
|
85
|
+
react_1.default.createElement("button", { onClick: handleSave, className: "btn-save" }, "Save Campaign"),
|
|
86
|
+
onPublish && campaign && (react_1.default.createElement("button", { onClick: () => onPublish(campaign), className: "btn-publish" }, "Publish Campaign")))));
|
|
87
|
+
};
|
|
88
|
+
exports.CampaignBuilder = CampaignBuilder;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (c) 2025 Bernier LLC
|
|
4
|
+
|
|
5
|
+
This file is licensed to the client under a limited-use license.
|
|
6
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
7
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
8
|
+
*/
|
|
9
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
10
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.MetricsOverview = void 0;
|
|
14
|
+
const react_1 = __importDefault(require("react"));
|
|
15
|
+
const formatting_1 = require("../../utils/formatting");
|
|
16
|
+
const MetricsOverview = ({ metrics }) => {
|
|
17
|
+
return (react_1.default.createElement("div", { className: "metrics-overview" },
|
|
18
|
+
react_1.default.createElement("div", { className: "metric-card" },
|
|
19
|
+
react_1.default.createElement("div", { className: "metric-value" }, (0, formatting_1.formatNumber)(metrics.sent)),
|
|
20
|
+
react_1.default.createElement("div", { className: "metric-label" }, "Sent")),
|
|
21
|
+
react_1.default.createElement("div", { className: "metric-card" },
|
|
22
|
+
react_1.default.createElement("div", { className: "metric-value" }, (0, formatting_1.formatNumber)(metrics.opened)),
|
|
23
|
+
react_1.default.createElement("div", { className: "metric-label" }, "Opened"),
|
|
24
|
+
react_1.default.createElement("div", { className: "metric-rate" }, (0, formatting_1.formatPercentage)(metrics.openRate))),
|
|
25
|
+
react_1.default.createElement("div", { className: "metric-card" },
|
|
26
|
+
react_1.default.createElement("div", { className: "metric-value" }, (0, formatting_1.formatNumber)(metrics.clicked)),
|
|
27
|
+
react_1.default.createElement("div", { className: "metric-label" }, "Clicked"),
|
|
28
|
+
react_1.default.createElement("div", { className: "metric-rate" }, (0, formatting_1.formatPercentage)(metrics.clickRate))),
|
|
29
|
+
react_1.default.createElement("div", { className: "metric-card" },
|
|
30
|
+
react_1.default.createElement("div", { className: "metric-value" }, (0, formatting_1.formatNumber)(metrics.converted)),
|
|
31
|
+
react_1.default.createElement("div", { className: "metric-label" }, "Converted"),
|
|
32
|
+
react_1.default.createElement("div", { className: "metric-rate" }, (0, formatting_1.formatPercentage)(metrics.conversionRate)))));
|
|
33
|
+
};
|
|
34
|
+
exports.MetricsOverview = MetricsOverview;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { CampaignMetrics } from '../../types';
|
|
3
|
+
export interface PerformanceChartProps {
|
|
4
|
+
metrics: CampaignMetrics;
|
|
5
|
+
type: 'open_rate' | 'click_rate' | 'conversion_rate';
|
|
6
|
+
}
|
|
7
|
+
export declare const PerformanceChart: React.FC<PerformanceChartProps>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (c) 2025 Bernier LLC
|
|
4
|
+
|
|
5
|
+
This file is licensed to the client under a limited-use license.
|
|
6
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
7
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
8
|
+
*/
|
|
9
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
10
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.PerformanceChart = void 0;
|
|
14
|
+
const react_1 = __importDefault(require("react"));
|
|
15
|
+
const PerformanceChart = ({ metrics, type }) => {
|
|
16
|
+
const getTitle = () => {
|
|
17
|
+
switch (type) {
|
|
18
|
+
case 'open_rate':
|
|
19
|
+
return 'Open Rate';
|
|
20
|
+
case 'click_rate':
|
|
21
|
+
return 'Click Rate';
|
|
22
|
+
case 'conversion_rate':
|
|
23
|
+
return 'Conversion Rate';
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const getValue = () => {
|
|
27
|
+
switch (type) {
|
|
28
|
+
case 'open_rate':
|
|
29
|
+
return metrics.openRate;
|
|
30
|
+
case 'click_rate':
|
|
31
|
+
return metrics.clickRate;
|
|
32
|
+
case 'conversion_rate':
|
|
33
|
+
return metrics.conversionRate;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
return (react_1.default.createElement("div", { className: "performance-chart" },
|
|
37
|
+
react_1.default.createElement("h3", null, getTitle()),
|
|
38
|
+
react_1.default.createElement("div", { className: "chart-placeholder" },
|
|
39
|
+
react_1.default.createElement("p", null,
|
|
40
|
+
"Chart: ",
|
|
41
|
+
getValue().toFixed(2),
|
|
42
|
+
"%"),
|
|
43
|
+
react_1.default.createElement("p", null, "(Integration with recharts or similar library needed)"))));
|
|
44
|
+
};
|
|
45
|
+
exports.PerformanceChart = PerformanceChart;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (c) 2025 Bernier LLC
|
|
4
|
+
|
|
5
|
+
This file is licensed to the client under a limited-use license.
|
|
6
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
7
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
8
|
+
*/
|
|
9
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
10
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.CampaignDashboard = void 0;
|
|
14
|
+
const react_1 = __importDefault(require("react"));
|
|
15
|
+
const useCampaignMetrics_1 = require("../../hooks/useCampaignMetrics");
|
|
16
|
+
const MetricsOverview_1 = require("./MetricsOverview");
|
|
17
|
+
const PerformanceChart_1 = require("./PerformanceChart");
|
|
18
|
+
const CampaignDashboard = ({ campaignId }) => {
|
|
19
|
+
const { data: metrics, isLoading: metricsLoading, isError: metricsError } = (0, useCampaignMetrics_1.useCampaignMetrics)(campaignId);
|
|
20
|
+
const { data: emailMetrics, isLoading: emailLoading } = (0, useCampaignMetrics_1.useEmailMetrics)(campaignId);
|
|
21
|
+
if (metricsLoading || emailLoading) {
|
|
22
|
+
return react_1.default.createElement("div", { className: "loading" }, "Loading campaign analytics...");
|
|
23
|
+
}
|
|
24
|
+
if (metricsError || !metrics) {
|
|
25
|
+
return react_1.default.createElement("div", { className: "error" }, "Failed to load campaign metrics");
|
|
26
|
+
}
|
|
27
|
+
return (react_1.default.createElement("div", { className: "campaign-dashboard" },
|
|
28
|
+
react_1.default.createElement("h2", null, "Campaign Analytics"),
|
|
29
|
+
react_1.default.createElement(MetricsOverview_1.MetricsOverview, { metrics: metrics }),
|
|
30
|
+
react_1.default.createElement("div", { className: "charts-grid" },
|
|
31
|
+
react_1.default.createElement(PerformanceChart_1.PerformanceChart, { metrics: metrics, type: "open_rate" }),
|
|
32
|
+
react_1.default.createElement(PerformanceChart_1.PerformanceChart, { metrics: metrics, type: "click_rate" })),
|
|
33
|
+
emailMetrics && (react_1.default.createElement("div", { className: "email-metrics" },
|
|
34
|
+
react_1.default.createElement("h3", null, "Email Performance"),
|
|
35
|
+
react_1.default.createElement("ul", null, emailMetrics.map(email => (react_1.default.createElement("li", { key: email.emailId },
|
|
36
|
+
"Node ",
|
|
37
|
+
email.nodeId,
|
|
38
|
+
": ",
|
|
39
|
+
email.sent,
|
|
40
|
+
" sent, ",
|
|
41
|
+
email.openRate.toFixed(2),
|
|
42
|
+
"% open rate"))))))));
|
|
43
|
+
};
|
|
44
|
+
exports.CampaignDashboard = CampaignDashboard;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (c) 2025 Bernier LLC
|
|
4
|
+
|
|
5
|
+
This file is licensed to the client under a limited-use license.
|
|
6
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
7
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.CampaignList = void 0;
|
|
44
|
+
const react_1 = __importStar(require("react"));
|
|
45
|
+
const useCampaignStore_1 = require("../../hooks/useCampaignStore");
|
|
46
|
+
const CampaignList = ({ onSelectCampaign }) => {
|
|
47
|
+
const { campaigns, loading, error, fetchCampaigns } = (0, useCampaignStore_1.useCampaignStore)();
|
|
48
|
+
(0, react_1.useEffect)(() => {
|
|
49
|
+
fetchCampaigns();
|
|
50
|
+
}, [fetchCampaigns]);
|
|
51
|
+
if (loading) {
|
|
52
|
+
return react_1.default.createElement("div", { className: "loading" }, "Loading campaigns...");
|
|
53
|
+
}
|
|
54
|
+
if (error) {
|
|
55
|
+
return react_1.default.createElement("div", { className: "error" },
|
|
56
|
+
"Error: ",
|
|
57
|
+
error);
|
|
58
|
+
}
|
|
59
|
+
return (react_1.default.createElement("div", { className: "campaign-list" },
|
|
60
|
+
react_1.default.createElement("h2", null, "Campaigns"),
|
|
61
|
+
campaigns.length === 0 ? (react_1.default.createElement("p", null, "No campaigns found. Create your first campaign!")) : (react_1.default.createElement("ul", null, campaigns.map(campaign => (react_1.default.createElement("li", { key: campaign.id, onClick: () => onSelectCampaign?.(campaign) },
|
|
62
|
+
react_1.default.createElement("div", { className: "campaign-item" },
|
|
63
|
+
react_1.default.createElement("h3", null, campaign.name),
|
|
64
|
+
react_1.default.createElement("p", null, campaign.description),
|
|
65
|
+
react_1.default.createElement("span", { className: "campaign-status" }, campaign.status),
|
|
66
|
+
react_1.default.createElement("span", { className: "campaign-type" }, campaign.type)))))))));
|
|
67
|
+
};
|
|
68
|
+
exports.CampaignList = CampaignList;
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface CampaignUIConfig {
|
|
2
|
+
apiUrl: string;
|
|
3
|
+
enableABTesting: boolean;
|
|
4
|
+
enableAnalytics: boolean;
|
|
5
|
+
metricsRefreshInterval: number;
|
|
6
|
+
maxWorkflowNodes: number;
|
|
7
|
+
defaultTimezone: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const defaultConfig: CampaignUIConfig;
|
|
10
|
+
export declare function getConfig(): CampaignUIConfig;
|
|
11
|
+
export declare function setConfig(config: Partial<CampaignUIConfig>): void;
|
|
12
|
+
export declare function resetConfig(): void;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (c) 2025 Bernier LLC
|
|
4
|
+
|
|
5
|
+
This file is licensed to the client under a limited-use license.
|
|
6
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
7
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.defaultConfig = void 0;
|
|
11
|
+
exports.getConfig = getConfig;
|
|
12
|
+
exports.setConfig = setConfig;
|
|
13
|
+
exports.resetConfig = resetConfig;
|
|
14
|
+
exports.defaultConfig = {
|
|
15
|
+
apiUrl: process.env.REACT_APP_API_URL || 'http://localhost:3000/api',
|
|
16
|
+
enableABTesting: process.env.REACT_APP_ENABLE_AB_TESTING !== 'false',
|
|
17
|
+
enableAnalytics: process.env.REACT_APP_ENABLE_ANALYTICS !== 'false',
|
|
18
|
+
metricsRefreshInterval: parseInt(process.env.REACT_APP_METRICS_REFRESH_INTERVAL || '30000', 10),
|
|
19
|
+
maxWorkflowNodes: parseInt(process.env.REACT_APP_MAX_WORKFLOW_NODES || '50', 10),
|
|
20
|
+
defaultTimezone: process.env.REACT_APP_DEFAULT_TIMEZONE || 'UTC'
|
|
21
|
+
};
|
|
22
|
+
let currentConfig = { ...exports.defaultConfig };
|
|
23
|
+
function getConfig() {
|
|
24
|
+
return { ...currentConfig };
|
|
25
|
+
}
|
|
26
|
+
function setConfig(config) {
|
|
27
|
+
currentConfig = { ...currentConfig, ...config };
|
|
28
|
+
}
|
|
29
|
+
function resetConfig() {
|
|
30
|
+
currentConfig = { ...exports.defaultConfig };
|
|
31
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare function useCampaignMetrics(campaignId: string): import("@tanstack/react-query").UseQueryResult<import("..").CampaignMetrics | undefined, Error>;
|
|
2
|
+
export declare function useEmailMetrics(campaignId: string): import("@tanstack/react-query").UseQueryResult<import("..").EmailMetrics[] | undefined, Error>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (c) 2025 Bernier LLC
|
|
4
|
+
|
|
5
|
+
This file is licensed to the client under a limited-use license.
|
|
6
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
7
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.useCampaignMetrics = useCampaignMetrics;
|
|
11
|
+
exports.useEmailMetrics = useEmailMetrics;
|
|
12
|
+
const react_query_1 = require("@tanstack/react-query");
|
|
13
|
+
const campaigns_1 = require("../api/campaigns");
|
|
14
|
+
const config_1 = require("../config");
|
|
15
|
+
function useCampaignMetrics(campaignId) {
|
|
16
|
+
return (0, react_query_1.useQuery)({
|
|
17
|
+
queryKey: ['campaign-metrics', campaignId],
|
|
18
|
+
queryFn: async () => {
|
|
19
|
+
const response = await (0, campaigns_1.getCampaignMetrics)(campaignId);
|
|
20
|
+
if (!response.success) {
|
|
21
|
+
throw new Error(response.error || 'Failed to fetch campaign metrics');
|
|
22
|
+
}
|
|
23
|
+
return response.data;
|
|
24
|
+
},
|
|
25
|
+
refetchInterval: (0, config_1.getConfig)().metricsRefreshInterval,
|
|
26
|
+
enabled: !!campaignId
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function useEmailMetrics(campaignId) {
|
|
30
|
+
return (0, react_query_1.useQuery)({
|
|
31
|
+
queryKey: ['email-metrics', campaignId],
|
|
32
|
+
queryFn: async () => {
|
|
33
|
+
const response = await (0, campaigns_1.getEmailMetrics)(campaignId);
|
|
34
|
+
if (!response.success) {
|
|
35
|
+
throw new Error(response.error || 'Failed to fetch email metrics');
|
|
36
|
+
}
|
|
37
|
+
return response.data;
|
|
38
|
+
},
|
|
39
|
+
refetchInterval: (0, config_1.getConfig)().metricsRefreshInterval,
|
|
40
|
+
enabled: !!campaignId
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Campaign } from '../types';
|
|
2
|
+
interface CampaignStore {
|
|
3
|
+
campaigns: Campaign[];
|
|
4
|
+
activeCampaign: Campaign | null;
|
|
5
|
+
loading: boolean;
|
|
6
|
+
error: string | null;
|
|
7
|
+
fetchCampaigns: () => Promise<void>;
|
|
8
|
+
createCampaign: (campaign: Partial<Campaign>) => Promise<Campaign | null>;
|
|
9
|
+
updateCampaign: (id: string, updates: Partial<Campaign>) => Promise<void>;
|
|
10
|
+
deleteCampaign: (id: string) => Promise<void>;
|
|
11
|
+
setActiveCampaign: (campaign: Campaign | null) => void;
|
|
12
|
+
}
|
|
13
|
+
export declare const useCampaignStore: import("zustand").UseBoundStore<import("zustand").StoreApi<CampaignStore>>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (c) 2025 Bernier LLC
|
|
4
|
+
|
|
5
|
+
This file is licensed to the client under a limited-use license.
|
|
6
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
7
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.useCampaignStore = void 0;
|
|
44
|
+
const zustand_1 = require("zustand");
|
|
45
|
+
const campaignAPI = __importStar(require("../api/campaigns"));
|
|
46
|
+
exports.useCampaignStore = (0, zustand_1.create)((set) => ({
|
|
47
|
+
campaigns: [],
|
|
48
|
+
activeCampaign: null,
|
|
49
|
+
loading: false,
|
|
50
|
+
error: null,
|
|
51
|
+
fetchCampaigns: async () => {
|
|
52
|
+
set({ loading: true, error: null });
|
|
53
|
+
const response = await campaignAPI.getCampaigns();
|
|
54
|
+
if (response.success && response.data) {
|
|
55
|
+
set({ campaigns: response.data, loading: false });
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
set({ error: response.error || 'Failed to fetch campaigns', loading: false });
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
createCampaign: async (campaign) => {
|
|
62
|
+
set({ loading: true, error: null });
|
|
63
|
+
const response = await campaignAPI.createCampaign(campaign);
|
|
64
|
+
if (response.success && response.data) {
|
|
65
|
+
set(state => ({
|
|
66
|
+
campaigns: [...state.campaigns, response.data],
|
|
67
|
+
loading: false
|
|
68
|
+
}));
|
|
69
|
+
return response.data;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
set({ error: response.error || 'Failed to create campaign', loading: false });
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
updateCampaign: async (id, updates) => {
|
|
77
|
+
set({ loading: true, error: null });
|
|
78
|
+
const response = await campaignAPI.updateCampaign(id, updates);
|
|
79
|
+
if (response.success && response.data) {
|
|
80
|
+
set(state => ({
|
|
81
|
+
campaigns: state.campaigns.map(c => c.id === id ? response.data : c),
|
|
82
|
+
activeCampaign: state.activeCampaign?.id === id ? response.data : state.activeCampaign,
|
|
83
|
+
loading: false
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
set({ error: response.error || 'Failed to update campaign', loading: false });
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
deleteCampaign: async (id) => {
|
|
91
|
+
set({ loading: true, error: null });
|
|
92
|
+
const response = await campaignAPI.deleteCampaign(id);
|
|
93
|
+
if (response.success) {
|
|
94
|
+
set(state => ({
|
|
95
|
+
campaigns: state.campaigns.filter(c => c.id !== id),
|
|
96
|
+
activeCampaign: state.activeCampaign?.id === id ? null : state.activeCampaign,
|
|
97
|
+
loading: false
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
set({ error: response.error || 'Failed to delete campaign', loading: false });
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
setActiveCampaign: (campaign) => set({ activeCampaign: campaign })
|
|
105
|
+
}));
|