@bernierllc/email-admin-suite 0.1.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.
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { EmailManager } from '@bernierllc/email-manager';
3
+ interface EmailAdminSuiteProps {
4
+ emailManager?: EmailManager;
5
+ basePath?: string;
6
+ className?: string;
7
+ style?: React.CSSProperties;
8
+ }
9
+ export declare const EmailAdminSuite: React.FC<EmailAdminSuiteProps>;
10
+ export {};
11
+ //# sourceMappingURL=EmailAdminSuite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EmailAdminSuite.d.ts","sourceRoot":"","sources":["../src/EmailAdminSuite.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAQxC,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAMzD,UAAU,oBAAoB;IAC5B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B;AAWD,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAkF1D,CAAC"}
@@ -0,0 +1,49 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
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
+ import { useState } from 'react';
10
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
11
+ import { BrowserRouter as Router, Routes, Route, NavLink } from 'react-router-dom';
12
+ import { EmailManager } from '@bernierllc/email-manager';
13
+ import { TemplatesPage } from './pages/TemplatesPage';
14
+ import { SchedulePage } from './pages/SchedulePage';
15
+ import { AnalyticsPage } from './pages/AnalyticsPage';
16
+ import { ComposePage } from './pages/ComposePage';
17
+ const queryClient = new QueryClient({
18
+ defaultOptions: {
19
+ queries: {
20
+ retry: 2,
21
+ refetchOnWindowFocus: false,
22
+ },
23
+ },
24
+ });
25
+ export const EmailAdminSuite = ({ emailManager, basePath = '/admin/email', className = '', style, }) => {
26
+ const [manager] = useState(() => emailManager || new EmailManager({
27
+ providers: [],
28
+ scheduling: {
29
+ enabled: true,
30
+ checkIntervalMs: 60000,
31
+ maxRetries: 3,
32
+ retryDelayMs: 5000
33
+ },
34
+ analytics: {
35
+ enabled: true,
36
+ trackOpens: true,
37
+ trackClicks: true,
38
+ trackBounces: true,
39
+ maxEvents: 10000
40
+ }
41
+ }));
42
+ const navigation = [
43
+ { name: 'Compose', path: `${basePath}/compose`, icon: '✉️' },
44
+ { name: 'Templates', path: `${basePath}/templates`, icon: '📄' },
45
+ { name: 'Schedule', path: `${basePath}/schedule`, icon: '📅' },
46
+ { name: 'Analytics', path: `${basePath}/analytics`, icon: '📊' },
47
+ ];
48
+ return (_jsx(QueryClientProvider, { client: queryClient, children: _jsx(Router, { children: _jsxs("div", { className: `email-admin-suite ${className}`, style: style, children: [_jsxs("nav", { className: "email-admin-suite__nav", children: [_jsx("div", { className: "nav__brand", children: _jsx("h1", { children: "Email Admin" }) }), _jsx("div", { className: "nav__links", children: navigation.map((item) => (_jsxs(NavLink, { to: item.path, className: ({ isActive }) => `nav__link ${isActive ? 'nav__link--active' : ''}`, children: [_jsx("span", { className: "nav__icon", children: item.icon }), item.name] }, item.path))) })] }), _jsx("main", { className: "email-admin-suite__content", children: _jsxs(Routes, { children: [_jsx(Route, { path: `${basePath}/compose`, element: _jsx(ComposePage, { emailManager: manager }) }), _jsx(Route, { path: `${basePath}/templates`, element: _jsx(TemplatesPage, { emailManager: manager }) }), _jsx(Route, { path: `${basePath}/schedule`, element: _jsx(SchedulePage, { emailManager: manager }) }), _jsx(Route, { path: `${basePath}/analytics`, element: _jsx(AnalyticsPage, { emailManager: manager }) }), _jsx(Route, { path: basePath, element: _jsx(ComposePage, { emailManager: manager }) })] }) })] }) }) }));
49
+ };
@@ -0,0 +1,3 @@
1
+ export { EmailAdminSuite } from './EmailAdminSuite';
2
+ export * from './pages';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,cAAc,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ /*
2
+ Copyright (c) 2025 Bernier LLC
3
+
4
+ This file is licensed to the client under a limited-use license.
5
+ The client may use and modify this code *only within the scope of the project it was delivered for*.
6
+ Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
7
+ */
8
+ export { EmailAdminSuite } from './EmailAdminSuite';
9
+ export * from './pages';
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import { EmailManager } from '@bernierllc/email-manager';
3
+ interface AnalyticsPageProps {
4
+ emailManager: EmailManager;
5
+ }
6
+ export declare const AnalyticsPage: React.FC<AnalyticsPageProps>;
7
+ export {};
8
+ //# sourceMappingURL=AnalyticsPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnalyticsPage.d.ts","sourceRoot":"","sources":["../../src/pages/AnalyticsPage.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAGxC,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,UAAU,kBAAkB;IAC1B,YAAY,EAAE,YAAY,CAAC;CAC5B;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CA0EtD,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
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
+ import { useState } from 'react';
10
+ import { useQuery } from '@tanstack/react-query';
11
+ import { AnalyticsDashboard } from '@bernierllc/email-ui';
12
+ export const AnalyticsPage = ({ emailManager }) => {
13
+ const [dateRange, setDateRange] = useState({
14
+ start: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000), // 30 days ago
15
+ end: new Date(),
16
+ });
17
+ const { data: analytics, isLoading, refetch } = useQuery({
18
+ queryKey: ['analytics', dateRange],
19
+ queryFn: async () => {
20
+ const days = Math.ceil((dateRange.end.getTime() - dateRange.start.getTime()) / (1000 * 60 * 60 * 24));
21
+ return await emailManager.getAnalyticsSummary(days);
22
+ },
23
+ staleTime: 1000 * 60 * 5, // 5 minutes
24
+ });
25
+ const handleRefresh = async () => {
26
+ await refetch();
27
+ };
28
+ const handleExport = async (format) => {
29
+ try {
30
+ // Export functionality would be implemented here
31
+ console.log(`Exporting analytics as ${format}`);
32
+ }
33
+ catch (error) {
34
+ console.error(`Failed to export ${format}:`, error);
35
+ }
36
+ };
37
+ const handleDateRangeChange = (start, end) => {
38
+ setDateRange({ start, end });
39
+ };
40
+ return (_jsxs("div", { className: "analytics-page", children: [_jsxs("div", { className: "page-header", children: [_jsx("h2", { children: "Email Analytics" }), _jsx("div", { className: "page-header__controls", children: _jsxs("div", { className: "date-range-picker", children: [_jsx("label", { children: "Date Range:" }), _jsx("input", { type: "date", value: dateRange.start.toISOString().split('T')[0], onChange: (e) => handleDateRangeChange(new Date(e.target.value), dateRange.end), className: "form-input" }), _jsx("span", { children: "to" }), _jsx("input", { type: "date", value: dateRange.end.toISOString().split('T')[0], onChange: (e) => handleDateRangeChange(dateRange.start, new Date(e.target.value)), className: "form-input" })] }) })] }), isLoading ? (_jsx("div", { className: "loading-state", children: "Loading analytics..." })) : analytics ? (_jsx(AnalyticsDashboard, { analytics: analytics, isLoading: isLoading, onRefresh: handleRefresh, onExport: handleExport, className: "analytics-page__dashboard" })) : (_jsxs("div", { className: "empty-state", children: [_jsx("h3", { children: "No analytics data available" }), _jsx("p", { children: "Analytics will appear here once you start sending emails." })] }))] }));
41
+ };
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import { EmailManager } from '@bernierllc/email-manager';
3
+ interface ComposePageProps {
4
+ emailManager: EmailManager;
5
+ }
6
+ export declare const ComposePage: React.FC<ComposePageProps>;
7
+ export {};
8
+ //# sourceMappingURL=ComposePage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ComposePage.d.ts","sourceRoot":"","sources":["../../src/pages/ComposePage.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAIzD,UAAU,gBAAgB;IACxB,YAAY,EAAE,YAAY,CAAC;CAC5B;AAeD,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAoFlD,CAAC"}
@@ -0,0 +1,79 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useQuery } from '@tanstack/react-query';
3
+ import { EmailComposer } from '@bernierllc/email-ui';
4
+ const convertTemplateToUI = (template) => ({
5
+ id: template.id,
6
+ name: template.name,
7
+ subject: template.subject,
8
+ content: template.htmlTemplate,
9
+ variables: template.variables.map(v => v.name),
10
+ category: template.category,
11
+ tags: [], // email-manager doesn't have tags field
12
+ createdAt: template.createdAt,
13
+ updatedAt: template.updatedAt,
14
+ isActive: template.isActive,
15
+ });
16
+ export const ComposePage = ({ emailManager }) => {
17
+ const { data: templates, isLoading: templatesLoading } = useQuery({
18
+ queryKey: ['templates'],
19
+ queryFn: async () => {
20
+ const result = await emailManager.listTemplates();
21
+ return result.templates.map(convertTemplateToUI);
22
+ },
23
+ staleTime: 1000 * 60 * 5, // 5 minutes
24
+ });
25
+ const convertToEmailData = async (email) => {
26
+ // Convert File[] attachments to Attachment[] format
27
+ const attachments = email.attachments ? await Promise.all(email.attachments.map(async (file) => {
28
+ const buffer = Buffer.from(await file.arrayBuffer());
29
+ return {
30
+ filename: file.name,
31
+ content: buffer,
32
+ contentType: file.type,
33
+ };
34
+ })) : undefined;
35
+ return {
36
+ to: email.to,
37
+ cc: email.cc,
38
+ bcc: email.bcc,
39
+ subject: email.subject,
40
+ html: email.content,
41
+ templateId: email.templateId,
42
+ attachments,
43
+ };
44
+ };
45
+ const handleSend = async (email) => {
46
+ try {
47
+ const emailData = await convertToEmailData(email);
48
+ await emailManager.sendEmail(emailData);
49
+ // Show success notification
50
+ }
51
+ catch (error) {
52
+ console.error('Failed to send email:', error);
53
+ // Show error notification
54
+ }
55
+ };
56
+ const handleSchedule = async (email) => {
57
+ try {
58
+ if (email.scheduleTime) {
59
+ const emailData = await convertToEmailData(email);
60
+ await emailManager.scheduleEmail(emailData, email.scheduleTime);
61
+ // Show success notification
62
+ }
63
+ }
64
+ catch (error) {
65
+ console.error('Failed to schedule email:', error);
66
+ // Show error notification
67
+ }
68
+ };
69
+ const handleSaveDraft = async (email) => {
70
+ try {
71
+ // Save draft functionality would be implemented here
72
+ console.log('Saving draft:', email);
73
+ }
74
+ catch (error) {
75
+ console.error('Failed to save draft:', error);
76
+ }
77
+ };
78
+ return (_jsxs("div", { className: "compose-page", children: [_jsxs("div", { className: "page-header", children: [_jsx("h2", { children: "Compose Email" }), _jsx("p", { children: "Create and send emails using templates or compose from scratch" })] }), _jsx(EmailComposer, { templates: templates || [], onSend: handleSend, onSchedule: handleSchedule, onSaveDraft: handleSaveDraft, isLoading: templatesLoading, className: "compose-page__composer" })] }));
79
+ };
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import { EmailManager } from '@bernierllc/email-manager';
3
+ interface SchedulePageProps {
4
+ emailManager: EmailManager;
5
+ }
6
+ export declare const SchedulePage: React.FC<SchedulePageProps>;
7
+ export {};
8
+ //# sourceMappingURL=SchedulePage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SchedulePage.d.ts","sourceRoot":"","sources":["../../src/pages/SchedulePage.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAGxC,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAGzD,UAAU,iBAAiB;IACzB,YAAY,EAAE,YAAY,CAAC;CAC5B;AAED,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CA6MpD,CAAC"}
@@ -0,0 +1,84 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
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
+ import { useState } from 'react';
10
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
11
+ import { format } from 'date-fns';
12
+ export const SchedulePage = ({ emailManager }) => {
13
+ const [statusFilter, setStatusFilter] = useState('all');
14
+ const queryClient = useQueryClient();
15
+ // Note: EmailManager doesn't currently expose getScheduledEmails() method
16
+ // This would need to be added to the EmailScheduler manager and exposed via EmailManager
17
+ const { data: scheduledEmails, isLoading } = useQuery({
18
+ queryKey: ['scheduled-emails', statusFilter],
19
+ queryFn: async () => {
20
+ // Stubbed for now - would need EmailManager.getScheduledEmails() implementation
21
+ return [];
22
+ },
23
+ });
24
+ const cancelEmailMutation = useMutation({
25
+ mutationFn: (emailId) => emailManager.cancelScheduledEmail(emailId),
26
+ onSuccess: () => {
27
+ queryClient.invalidateQueries({ queryKey: ['scheduled-emails'] });
28
+ },
29
+ });
30
+ const rescheduleEmailMutation = useMutation({
31
+ mutationFn: async ({ emailId, newTime }) => {
32
+ // Note: EmailManager doesn't have rescheduleEmail method
33
+ // Workaround: cancel and reschedule
34
+ await emailManager.cancelScheduledEmail(emailId);
35
+ // Would need to store original email data to reschedule
36
+ // This is a limitation of the current API
37
+ return { success: true };
38
+ },
39
+ onSuccess: () => {
40
+ queryClient.invalidateQueries({ queryKey: ['scheduled-emails'] });
41
+ },
42
+ });
43
+ const handleCancelEmail = (emailId) => {
44
+ if (window.confirm('Are you sure you want to cancel this scheduled email?')) {
45
+ cancelEmailMutation.mutate(emailId);
46
+ }
47
+ };
48
+ const handleReschedule = (emailId) => {
49
+ const newTimeStr = prompt('Enter new date and time (YYYY-MM-DD HH:MM):');
50
+ if (newTimeStr) {
51
+ const newTime = new Date(newTimeStr);
52
+ if (!isNaN(newTime.getTime()) && newTime > new Date()) {
53
+ rescheduleEmailMutation.mutate({ emailId, newTime });
54
+ }
55
+ else {
56
+ alert('Please enter a valid future date and time.');
57
+ }
58
+ }
59
+ };
60
+ const getStatusColor = (status) => {
61
+ switch (status) {
62
+ case 'pending': return 'text-yellow-600';
63
+ case 'sent': return 'text-green-600';
64
+ case 'failed': return 'text-red-600';
65
+ case 'cancelled': return 'text-gray-600';
66
+ default: return 'text-gray-600';
67
+ }
68
+ };
69
+ const getStatusIcon = (status) => {
70
+ switch (status) {
71
+ case 'pending': return '⏳';
72
+ case 'sent': return '✅';
73
+ case 'failed': return '❌';
74
+ case 'cancelled': return '🚫';
75
+ default: return '❓';
76
+ }
77
+ };
78
+ return (_jsxs("div", { className: "schedule-page", children: [_jsxs("div", { className: "page-header", children: [_jsx("h2", { children: "Scheduled Emails" }), _jsx("div", { className: "page-header__controls", children: _jsxs("div", { className: "filter-controls", children: [_jsx("label", { htmlFor: "status-filter", children: "Filter by status:" }), _jsxs("select", { id: "status-filter", value: statusFilter, onChange: (e) => setStatusFilter(e.target.value), className: "form-select", children: [_jsx("option", { value: "all", children: "All" }), _jsx("option", { value: "pending", children: "Pending" }), _jsx("option", { value: "sent", children: "Sent" }), _jsx("option", { value: "failed", children: "Failed" }), _jsx("option", { value: "cancelled", children: "Cancelled" })] })] }) })] }), isLoading ? (_jsx("div", { className: "loading-state", children: "Loading scheduled emails..." })) : scheduledEmails && scheduledEmails.length > 0 ? (_jsx("div", { className: "scheduled-emails-table", children: _jsxs("table", { className: "data-table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Status" }), _jsx("th", { children: "Recipient" }), _jsx("th", { children: "Template" }), _jsx("th", { children: "Scheduled Time" }), _jsx("th", { children: "Created" }), _jsx("th", { children: "Actions" })] }) }), _jsx("tbody", { children: scheduledEmails.map((email) => (_jsxs("tr", { children: [_jsx("td", { children: _jsxs("span", { className: `status-badge ${getStatusColor(email.status)}`, children: [getStatusIcon(email.status), " ", email.status] }) }), _jsx("td", { children: email.recipientEmail }), _jsx("td", { children: email.templateId }), _jsx("td", { children: format(new Date(email.scheduledTime), 'MMM dd, yyyy HH:mm') }), _jsx("td", { children: format(new Date(email.scheduledTime), 'MMM dd, yyyy') }), _jsx("td", { children: _jsxs("div", { className: "action-buttons", children: [email.status === 'pending' && (_jsxs(_Fragment, { children: [_jsx("button", { onClick: () => handleReschedule(email.id), className: "btn btn--small btn--secondary", disabled: rescheduleEmailMutation.isPending, children: "Reschedule" }), _jsx("button", { onClick: () => handleCancelEmail(email.id), className: "btn btn--small btn--danger", disabled: cancelEmailMutation.isPending, children: "Cancel" })] })), email.status === 'failed' && (_jsx("button", { onClick: () => {
79
+ // Retry logic would be implemented here
80
+ console.log('Retrying email:', email.id);
81
+ }, className: "btn btn--small btn--primary", children: "Retry" }))] }) })] }, email.id))) })] }) })) : (_jsxs("div", { className: "empty-state", children: [_jsx("h3", { children: "No scheduled emails" }), _jsx("p", { children: statusFilter !== 'all'
82
+ ? `No ${statusFilter} scheduled emails found.`
83
+ : 'You haven\'t scheduled any emails yet.' })] })), _jsxs("div", { className: "schedule-summary", children: [_jsx("h3", { children: "Quick Statistics" }), _jsxs("div", { className: "stats-grid", children: [_jsxs("div", { className: "stat-card", children: [_jsx("div", { className: "stat-card__value", children: scheduledEmails?.filter((e) => e.status === 'pending').length || 0 }), _jsx("div", { className: "stat-card__label", children: "Pending" })] }), _jsxs("div", { className: "stat-card", children: [_jsx("div", { className: "stat-card__value", children: scheduledEmails?.filter((e) => e.status === 'sent').length || 0 }), _jsx("div", { className: "stat-card__label", children: "Sent" })] }), _jsxs("div", { className: "stat-card", children: [_jsx("div", { className: "stat-card__value", children: scheduledEmails?.filter((e) => e.status === 'failed').length || 0 }), _jsx("div", { className: "stat-card__label", children: "Failed" })] }), _jsxs("div", { className: "stat-card", children: [_jsx("div", { className: "stat-card__value", children: scheduledEmails?.filter((e) => e.status === 'cancelled').length || 0 }), _jsx("div", { className: "stat-card__label", children: "Cancelled" })] })] })] })] }));
84
+ };
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import { EmailManager } from '@bernierllc/email-manager';
3
+ interface TemplatesPageProps {
4
+ emailManager: EmailManager;
5
+ }
6
+ export declare const TemplatesPage: React.FC<TemplatesPageProps>;
7
+ export {};
8
+ //# sourceMappingURL=TemplatesPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TemplatesPage.d.ts","sourceRoot":"","sources":["../../src/pages/TemplatesPage.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAGxC,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAIzD,UAAU,kBAAkB;IAC1B,YAAY,EAAE,YAAY,CAAC;CAC5B;AAeD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAkMtD,CAAC"}
@@ -0,0 +1,95 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
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
+ import { useState } from 'react';
10
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
11
+ import { TemplateEditor } from '@bernierllc/email-ui';
12
+ const convertTemplateToUI = (template) => ({
13
+ id: template.id,
14
+ name: template.name,
15
+ subject: template.subject,
16
+ content: template.htmlTemplate,
17
+ variables: template.variables.map(v => v.name),
18
+ category: template.category,
19
+ tags: [], // email-manager doesn't have tags field
20
+ createdAt: template.createdAt,
21
+ updatedAt: template.updatedAt,
22
+ isActive: template.isActive,
23
+ });
24
+ export const TemplatesPage = ({ emailManager }) => {
25
+ const [selectedTemplate, setSelectedTemplate] = useState(null);
26
+ const [isEditing, setIsEditing] = useState(false);
27
+ const [searchTerm, setSearchTerm] = useState('');
28
+ const queryClient = useQueryClient();
29
+ const { data: templates, isLoading } = useQuery({
30
+ queryKey: ['templates'],
31
+ queryFn: async () => {
32
+ const result = await emailManager.listTemplates();
33
+ return result.templates.map(convertTemplateToUI);
34
+ },
35
+ });
36
+ const createTemplateMutation = useMutation({
37
+ mutationFn: (templateData) => emailManager.createTemplate(templateData),
38
+ onSuccess: () => {
39
+ queryClient.invalidateQueries({ queryKey: ['templates'] });
40
+ setIsEditing(false);
41
+ setSelectedTemplate(null);
42
+ },
43
+ });
44
+ const updateTemplateMutation = useMutation({
45
+ mutationFn: ({ id, data }) => emailManager.updateTemplate(id, data),
46
+ onSuccess: () => {
47
+ queryClient.invalidateQueries({ queryKey: ['templates'] });
48
+ setIsEditing(false);
49
+ setSelectedTemplate(null);
50
+ },
51
+ });
52
+ const deleteTemplateMutation = useMutation({
53
+ mutationFn: (id) => emailManager.deleteTemplate(id),
54
+ onSuccess: () => {
55
+ queryClient.invalidateQueries({ queryKey: ['templates'] });
56
+ setSelectedTemplate(null);
57
+ },
58
+ });
59
+ const filteredTemplates = templates?.filter((template) => template.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
60
+ template.subject.toLowerCase().includes(searchTerm.toLowerCase()) ||
61
+ template.category?.toLowerCase().includes(searchTerm.toLowerCase())) || [];
62
+ const handleSaveTemplate = async (templateData) => {
63
+ if (selectedTemplate?.id) {
64
+ await updateTemplateMutation.mutateAsync({
65
+ id: selectedTemplate.id,
66
+ data: templateData,
67
+ });
68
+ }
69
+ else {
70
+ await createTemplateMutation.mutateAsync(templateData);
71
+ }
72
+ };
73
+ const handleDeleteTemplate = (id) => {
74
+ if (window.confirm('Are you sure you want to delete this template?')) {
75
+ deleteTemplateMutation.mutate(id);
76
+ }
77
+ };
78
+ const handleNewTemplate = () => {
79
+ setSelectedTemplate(null);
80
+ setIsEditing(true);
81
+ };
82
+ const handleEditTemplate = (template) => {
83
+ setSelectedTemplate(template);
84
+ setIsEditing(true);
85
+ };
86
+ if (isEditing) {
87
+ return (_jsxs("div", { className: "templates-page", children: [_jsx("div", { className: "page-header", children: _jsx("h2", { children: selectedTemplate ? 'Edit Template' : 'Create Template' }) }), _jsx(TemplateEditor, { template: selectedTemplate || undefined, onSave: handleSaveTemplate, onCancel: () => {
88
+ setIsEditing(false);
89
+ setSelectedTemplate(null);
90
+ }, isLoading: createTemplateMutation.isPending || updateTemplateMutation.isPending })] }));
91
+ }
92
+ return (_jsxs("div", { className: "templates-page", children: [_jsxs("div", { className: "page-header", children: [_jsx("h2", { children: "Email Templates" }), _jsxs("div", { className: "page-header__actions", children: [_jsx("div", { className: "search-bar", children: _jsx("input", { type: "text", placeholder: "Search templates...", value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), className: "form-input" }) }), _jsx("button", { onClick: handleNewTemplate, className: "btn btn--primary", children: "New Template" })] })] }), isLoading ? (_jsx("div", { className: "loading-state", children: "Loading templates..." })) : (_jsx("div", { className: "templates-grid", children: filteredTemplates.map((template) => (_jsxs("div", { className: "template-card", children: [_jsxs("div", { className: "template-card__header", children: [_jsx("h3", { className: "template-card__title", children: template.name }), _jsxs("div", { className: "template-card__actions", children: [_jsx("button", { onClick: () => handleEditTemplate(template), className: "btn btn--small btn--secondary", children: "Edit" }), _jsx("button", { onClick: () => handleDeleteTemplate(template.id), className: "btn btn--small btn--danger", children: "Delete" })] })] }), _jsxs("div", { className: "template-card__content", children: [_jsx("p", { className: "template-card__subject", children: template.subject }), template.category && (_jsx("span", { className: "template-card__category", children: template.category })), _jsx("div", { className: "template-card__tags", children: template.tags?.map((tag) => (_jsx("span", { className: "tag", children: tag }, tag))) }), _jsxs("div", { className: "template-card__variables", children: [_jsx("strong", { children: "Variables:" }), template.variables.length > 0 ? (_jsx("span", { children: template.variables.join(', ') })) : (_jsx("span", { className: "text-muted", children: "None" }))] })] }), _jsxs("div", { className: "template-card__footer", children: [_jsx("span", { className: "template-card__status", children: template.isActive ? '✅ Active' : '❌ Inactive' }), _jsxs("span", { className: "template-card__date", children: ["Updated ", new Date(template.updatedAt).toLocaleDateString()] })] })] }, template.id))) })), filteredTemplates.length === 0 && !isLoading && (_jsxs("div", { className: "empty-state", children: [_jsx("h3", { children: "No templates found" }), _jsx("p", { children: searchTerm
93
+ ? 'No templates match your search criteria.'
94
+ : 'Create your first email template to get started.' }), _jsx("button", { onClick: handleNewTemplate, className: "btn btn--primary", children: "Create Template" })] }))] }));
95
+ };
@@ -0,0 +1,5 @@
1
+ export { ComposePage } from './ComposePage';
2
+ export { TemplatesPage } from './TemplatesPage';
3
+ export { AnalyticsPage } from './AnalyticsPage';
4
+ export { SchedulePage } from './SchedulePage';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pages/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,11 @@
1
+ /*
2
+ Copyright (c) 2025 Bernier LLC
3
+
4
+ This file is licensed to the client under a limited-use license.
5
+ The client may use and modify this code *only within the scope of the project it was delivered for*.
6
+ Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
7
+ */
8
+ export { ComposePage } from './ComposePage';
9
+ export { TemplatesPage } from './TemplatesPage';
10
+ export { AnalyticsPage } from './AnalyticsPage';
11
+ export { SchedulePage } from './SchedulePage';
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "@bernierllc/email-admin-suite",
3
+ "version": "0.1.0",
4
+ "description": "Complete email administration suite with UI components, templates, scheduling, and analytics management",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "dev": "tsc --watch",
11
+ "test": "jest",
12
+ "test:watch": "jest --watch",
13
+ "test:coverage": "jest --coverage",
14
+ "lint": "eslint src/**/*.{ts,tsx}",
15
+ "lint:fix": "eslint src/**/*.{ts,tsx} --fix",
16
+ "clean": "rimraf dist",
17
+ "prebuild": "npm run clean",
18
+ "storybook": "storybook dev -p 6007",
19
+ "build-storybook": "storybook build"
20
+ },
21
+ "keywords": [
22
+ "email",
23
+ "admin",
24
+ "suite",
25
+ "management",
26
+ "ui",
27
+ "templates",
28
+ "scheduling",
29
+ "analytics",
30
+ "bernierllc"
31
+ ],
32
+ "author": "Bernier LLC",
33
+ "license": "Bernier LLC",
34
+ "dependencies": {
35
+ "@bernierllc/email-manager": "^0.1.3",
36
+ "@bernierllc/email-ui": "^0.1.0",
37
+ "react": "^18.2.0",
38
+ "react-dom": "^18.2.0",
39
+ "react-router-dom": "^6.15.0",
40
+ "react-query": "^3.39.0",
41
+ "@tanstack/react-query": "^4.32.0",
42
+ "@headlessui/react": "^1.7.0",
43
+ "@heroicons/react": "^2.0.0",
44
+ "clsx": "^2.0.0",
45
+ "date-fns": "^2.30.0",
46
+ "zod": "^3.22.4"
47
+ },
48
+ "peerDependencies": {
49
+ "react": "^18.0.0",
50
+ "react-dom": "^18.0.0"
51
+ },
52
+ "devDependencies": {
53
+ "@types/react": "^18.2.0",
54
+ "@types/react-dom": "^18.2.0",
55
+ "@types/jest": "^29.5.5",
56
+ "@types/node": "^20.6.0",
57
+ "@testing-library/react": "^14.0.0",
58
+ "@testing-library/jest-dom": "^6.1.0",
59
+ "@testing-library/user-event": "^14.4.0",
60
+ "@storybook/react": "^7.4.0",
61
+ "@storybook/addon-essentials": "^7.4.0",
62
+ "jest": "^29.6.4",
63
+ "jest-environment-jsdom": "^29.6.4",
64
+ "ts-jest": "^29.1.1",
65
+ "typescript": "^5.2.2",
66
+ "eslint": "^8.50.0",
67
+ "@typescript-eslint/eslint-plugin": "^6.7.0",
68
+ "@typescript-eslint/parser": "^6.7.0",
69
+ "eslint-plugin-react": "^7.33.0",
70
+ "eslint-plugin-react-hooks": "^4.6.0",
71
+ "rimraf": "^5.0.1"
72
+ },
73
+ "files": [
74
+ "dist/**/*",
75
+ "README.md",
76
+ "LICENSE"
77
+ ],
78
+ "repository": {
79
+ "type": "git",
80
+ "url": "git+https://github.com/bernier-llc/tools.git",
81
+ "directory": "packages/suite/email-admin-suite"
82
+ },
83
+ "publishConfig": {
84
+ "access": "restricted"
85
+ }
86
+ }