@flusys/ng-email 1.1.0-beta

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 @@
1
+ {"version":3,"file":"flusys-ng-email-template-list.component-CGJxfZMT.mjs","sources":["../../../projects/ng-email/pages/template/template-list.component.ts"],"sourcesContent":["import { Component, ChangeDetectionStrategy, inject, signal, OnInit, computed } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { FormsModule } from '@angular/forms';\nimport { firstValueFrom } from 'rxjs';\nimport { ConfirmationService, MessageService } from 'primeng/api';\nimport { APP_CONFIG, DEFAULT_APP_NAME } from '@flusys/ng-core';\nimport { LAYOUT_AUTH_STATE } from '@flusys/ng-layout';\nimport { AngularModule, PrimeModule } from '@flusys/ng-shared';\nimport { EmailTemplateApiService } from '../../services/email-template-api.service';\nimport { EmailConfigApiService } from '../../services/email-config-api.service';\nimport { EmailSendService } from '../../services/email-send.service';\nimport { IEmailTemplate } from '../../interfaces/email-template.interface';\nimport { IEmailConfig } from '../../interfaces/email-config.interface';\n\n/**\n * Email template list component\n */\n@Component({\n selector: 'lib-template-list',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [AngularModule, PrimeModule, FormsModule],\n providers: [ConfirmationService, MessageService],\n template: `\n <div class=\"card\">\n <div class=\"flex justify-between items-center mb-4\">\n <div>\n <h3 class=\"text-xl font-semibold\">Email Templates</h3>\n @if (showCompanyInfo()) {\n <p class=\"text-sm text-gray-600 mt-1\">\n Company: {{ currentCompanyName() }}\n </p>\n }\n </div>\n <p-button\n label=\"New Template\"\n icon=\"pi pi-plus\"\n (onClick)=\"onCreate()\"\n />\n </div>\n\n <p-table\n [value]=\"templates()\"\n [loading]=\"isLoading()\"\n [paginator]=\"true\"\n [rows]=\"pageSize()\"\n [totalRecords]=\"totalRecords()\"\n [lazy]=\"true\"\n (onLazyLoad)=\"onPageChange($event)\"\n styleClass=\"p-datatable-sm\"\n >\n <ng-template #header>\n <tr>\n <th>Name</th>\n <th>Slug</th>\n <th>Subject</th>\n <th>Type</th>\n <th>Status</th>\n <th>Created</th>\n <th style=\"width: 150px\">Actions</th>\n </tr>\n </ng-template>\n\n <ng-template #body let-template>\n <tr>\n <td>\n <i class=\"pi pi-envelope mr-2\"></i>\n {{ template.name }}\n </td>\n <td>\n <code class=\"text-sm bg-gray-100 px-2 py-1 rounded\">{{ template.slug }}</code>\n </td>\n <td>{{ template.subject }}</td>\n <td>\n <p-tag\n [value]=\"template.isHtml ? 'HTML' : 'Text'\"\n [severity]=\"template.isHtml ? 'info' : 'secondary'\"\n />\n </td>\n <td>\n <p-tag\n [value]=\"template.isActive ? 'Active' : 'Inactive'\"\n [severity]=\"template.isActive ? 'success' : 'secondary'\"\n />\n </td>\n <td>{{ template.createdAt | date: 'short' }}</td>\n <td>\n <p-button\n icon=\"pi pi-send\"\n [text]=\"true\"\n size=\"small\"\n pTooltip=\"Test Send\"\n (onClick)=\"onTestSend(template)\"\n />\n <p-button\n icon=\"pi pi-pencil\"\n [text]=\"true\"\n severity=\"secondary\"\n size=\"small\"\n (onClick)=\"onEdit(template.id)\"\n />\n <p-button\n icon=\"pi pi-trash\"\n [text]=\"true\"\n severity=\"danger\"\n size=\"small\"\n (onClick)=\"onDelete(template)\"\n />\n </td>\n </tr>\n </ng-template>\n\n <ng-template #emptymessage>\n <tr>\n <td colspan=\"7\" class=\"text-center py-4\">\n No email templates found. Create your first template to get started.\n </td>\n </tr>\n </ng-template>\n </p-table>\n </div>\n\n <!-- Test Send Dialog -->\n <p-dialog\n header=\"Test Send Email\"\n [(visible)]=\"showTestDialog\"\n [modal]=\"true\"\n [style]=\"{ width: '500px', maxHeight: '80vh' }\"\n >\n <div class=\"grid gap-4\">\n <div class=\"field\">\n <label class=\"block font-medium mb-2\">Template</label>\n <input\n pInputText\n [value]=\"selectedTemplate()?.name || ''\"\n class=\"w-full\"\n [disabled]=\"true\"\n />\n </div>\n\n <div class=\"field\">\n <label class=\"block font-medium mb-2\">Email Configuration *</label>\n <p-select\n [(ngModel)]=\"testSendModel.configId\"\n [options]=\"configOptions()\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select configuration\"\n class=\"w-full\"\n [loading]=\"isLoadingConfigs()\"\n />\n </div>\n\n <div class=\"field\">\n <label class=\"block font-medium mb-2\">Recipient Email *</label>\n <input\n pInputText\n [(ngModel)]=\"testSendModel.recipient\"\n class=\"w-full\"\n placeholder=\"recipient@example.com\"\n />\n </div>\n\n <!-- Dynamic Variables -->\n @if (templateVariables().length > 0) {\n <div class=\"border-t pt-4 mt-2\">\n <h4 class=\"font-medium mb-3 flex items-center gap-2\">\n <i class=\"pi pi-code\"></i>\n Template Variables\n </h4>\n <div class=\"grid gap-3\">\n @for (variable of templateVariables(); track variable) {\n <div class=\"field\">\n <label class=\"block text-sm font-medium mb-1\">\n <code class=\"text-primary\">{{ '{{' + variable + '}}' }}</code>\n </label>\n <input\n pInputText\n [ngModel]=\"variableValues[variable] || ''\"\n (ngModelChange)=\"variableValues[variable] = $event\"\n class=\"w-full\"\n [placeholder]=\"'Enter value for ' + variable\"\n />\n </div>\n }\n </div>\n </div>\n }\n </div>\n\n <ng-template #footer>\n <p-button\n label=\"Cancel\"\n [text]=\"true\"\n (onClick)=\"showTestDialog = false\"\n />\n <p-button\n label=\"Send Test\"\n icon=\"pi pi-send\"\n [loading]=\"isSendingTest()\"\n (onClick)=\"sendTestEmail()\"\n />\n </ng-template>\n </p-dialog>\n\n <p-confirmDialog />\n <p-toast />\n `,\n})\nexport class TemplateListComponent implements OnInit {\n private readonly router = inject(Router);\n private readonly templateService = inject(EmailTemplateApiService);\n private readonly configService = inject(EmailConfigApiService);\n private readonly emailSendService = inject(EmailSendService);\n private readonly confirmationService = inject(ConfirmationService);\n private readonly messageService = inject(MessageService);\n private readonly appConfig = inject(APP_CONFIG);\n private readonly companyContext = inject(LAYOUT_AUTH_STATE);\n\n readonly isLoading = signal(false);\n readonly templates = signal<IEmailTemplate[]>([]);\n readonly totalRecords = signal(0);\n readonly pageSize = signal(10);\n readonly currentPage = signal(1);\n\n readonly showCompanyInfo = computed(() => this.appConfig.enableCompanyFeature);\n readonly currentCompanyName = computed(\n () => this.companyContext.currentCompanyInfo()?.name ?? DEFAULT_APP_NAME,\n );\n\n // Test send dialog\n showTestDialog = false;\n readonly selectedTemplate = signal<IEmailTemplate | null>(null);\n readonly configs = signal<IEmailConfig[]>([]);\n readonly isLoadingConfigs = signal(false);\n readonly isSendingTest = signal(false);\n\n // Form-bound properties (ngModel requires plain properties)\n testSendModel = { configId: '', recipient: '' };\n variableValues: Record<string, string> = {};\n\n readonly configOptions = computed(() =>\n this.configs().map((c) => ({\n label: `${c.name} (${c.provider})`,\n value: c.id,\n })),\n );\n\n /** Extract variables from template content */\n readonly templateVariables = computed(() => {\n const template = this.selectedTemplate();\n if (!template) return [];\n\n // Combine all content sources to extract variables\n const content = [\n template.subject,\n template.htmlContent,\n template.textContent || '',\n ].join(' ');\n\n // Find all {{variableName}} patterns\n const matches = content.matchAll(/\\{\\{(\\w+)\\}\\}/g);\n const variables = new Set<string>();\n\n for (const match of matches) {\n variables.add(match[1]);\n }\n\n return Array.from(variables).sort();\n });\n\n ngOnInit(): void {\n this.loadTemplates();\n }\n\n onCreate(): void {\n this.router.navigate(['/email/templates/new']);\n }\n\n onEdit(templateId: string): void {\n this.router.navigate(['/email/templates', templateId]);\n }\n\n async loadTemplates(): Promise<void> {\n this.isLoading.set(true);\n try {\n const response = await firstValueFrom(\n this.templateService.getAll('', {\n pagination: {\n currentPage: this.currentPage() - 1,\n pageSize: this.pageSize(),\n },\n filter: {},\n select: [],\n sort: {},\n }),\n );\n\n if (response.success) {\n this.templates.set(response.data ?? []);\n this.totalRecords.set(response.meta?.total ?? 0);\n }\n } finally {\n this.isLoading.set(false);\n }\n }\n\n onPageChange(event: any): void {\n this.currentPage.set(event.first / event.rows + 1);\n this.pageSize.set(event.rows);\n this.loadTemplates();\n }\n\n async onTestSend(template: IEmailTemplate): Promise<void> {\n this.selectedTemplate.set(template);\n this.testSendModel = { configId: '', recipient: '' };\n this.variableValues = {}; // Reset variable values\n this.showTestDialog = true;\n await this.loadConfigs();\n }\n\n async loadConfigs(): Promise<void> {\n this.isLoadingConfigs.set(true);\n try {\n const response = await firstValueFrom(\n this.configService.getAll('', {\n pagination: { currentPage: 0, pageSize: 100 },\n filter: { isActive: true },\n select: [],\n sort: {},\n }),\n );\n if (response.success) {\n this.configs.set(response.data ?? []);\n }\n } finally {\n this.isLoadingConfigs.set(false);\n }\n }\n\n async sendTestEmail(): Promise<void> {\n const template = this.selectedTemplate();\n if (!template || !this.testSendModel.configId || !this.testSendModel.recipient) {\n this.messageService.add({\n severity: 'warn',\n summary: 'Validation',\n detail: 'Please select a configuration and enter recipient email.',\n });\n return;\n }\n\n this.isSendingTest.set(true);\n try {\n // Build variables object (only include non-empty values)\n const variables: Record<string, string> = {};\n for (const [key, value] of Object.entries(this.variableValues)) {\n if (value) {\n variables[key] = value;\n }\n }\n\n const response = await firstValueFrom(\n this.emailSendService.sendTemplate({\n templateId: template.id,\n to: this.testSendModel.recipient,\n emailConfigId: this.testSendModel.configId,\n variables: Object.keys(variables).length > 0 ? variables : undefined,\n }),\n );\n\n if (response.data?.success) {\n this.messageService.add({\n severity: 'success',\n summary: 'Success',\n detail: `Test email sent! Message ID: ${response.data.messageId}`,\n });\n this.showTestDialog = false;\n } else {\n this.messageService.add({\n severity: 'error',\n summary: 'Error',\n detail: response.data?.error || 'Failed to send test email.',\n });\n }\n } catch (error: any) {\n this.messageService.add({\n severity: 'error',\n summary: 'Error',\n detail: error?.message || 'Failed to send test email.',\n });\n } finally {\n this.isSendingTest.set(false);\n }\n }\n\n onDelete(template: IEmailTemplate): void {\n this.confirmationService.confirm({\n message: `Are you sure you want to delete \"${template.name}\"?`,\n header: 'Delete Template',\n icon: 'pi pi-exclamation-triangle',\n acceptButtonStyleClass: 'p-button-danger',\n accept: async () => {\n try {\n await this.templateService.deleteAsync({ id: template.id, type: 'delete' });\n this.messageService.add({\n severity: 'success',\n summary: 'Success',\n detail: 'Template deleted successfully.',\n });\n this.loadTemplates();\n } catch (error) {\n this.messageService.add({\n severity: 'error',\n summary: 'Error',\n detail: 'Failed to delete template.',\n });\n }\n },\n });\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,571 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, Injectable, signal, computed, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { HttpClient } from '@angular/common/http';
4
+ import { APP_CONFIG } from '@flusys/ng-core';
5
+ import { ApiResourceService } from '@flusys/ng-shared';
6
+ import { CommonModule } from '@angular/common';
7
+ import { RouterOutlet, RouterLink, RouterLinkActive } from '@angular/router';
8
+
9
+ /**
10
+ * Supported email provider types
11
+ */
12
+ var EmailProviderEnum;
13
+ (function (EmailProviderEnum) {
14
+ EmailProviderEnum["SMTP"] = "smtp";
15
+ EmailProviderEnum["SENDGRID"] = "sendgrid";
16
+ EmailProviderEnum["MAILGUN"] = "mailgun";
17
+ })(EmailProviderEnum || (EmailProviderEnum = {}));
18
+
19
+ /**
20
+ * Email content block types
21
+ */
22
+ var EmailBlockType;
23
+ (function (EmailBlockType) {
24
+ EmailBlockType["TEXT"] = "text";
25
+ EmailBlockType["IMAGE"] = "image";
26
+ EmailBlockType["BUTTON"] = "button";
27
+ EmailBlockType["DIVIDER"] = "divider";
28
+ EmailBlockType["HTML"] = "html";
29
+ })(EmailBlockType || (EmailBlockType = {}));
30
+
31
+ /**
32
+ * Default email settings
33
+ */
34
+ const DEFAULT_EMAIL_SETTINGS = {
35
+ maxWidth: '600px',
36
+ backgroundColor: '#f5f5f5',
37
+ fontFamily: 'Arial, sans-serif',
38
+ baseTextColor: '#333333',
39
+ };
40
+ /**
41
+ * Create a new email schema
42
+ */
43
+ function createEmailSchema(partial = {}) {
44
+ return {
45
+ id: crypto.randomUUID(),
46
+ version: '1.0.0',
47
+ name: partial.name || 'Untitled Template',
48
+ subject: partial.subject || '',
49
+ sections: partial.sections || [
50
+ { id: crypto.randomUUID(), type: 'header', name: 'Header', blocks: [], order: 0 },
51
+ { id: crypto.randomUUID(), type: 'body', name: 'Body', blocks: [], order: 1 },
52
+ { id: crypto.randomUUID(), type: 'footer', name: 'Footer', blocks: [], order: 2 },
53
+ ],
54
+ settings: { ...DEFAULT_EMAIL_SETTINGS, ...partial.settings },
55
+ variables: partial.variables || [],
56
+ ...partial,
57
+ };
58
+ }
59
+
60
+ /**
61
+ * Email Config API Service
62
+ * Handles email configuration CRUD operations
63
+ * Endpoint: POST /email/email-config/*
64
+ */
65
+ class EmailConfigApiService extends ApiResourceService {
66
+ apiBaseUrl;
67
+ constructor() {
68
+ const http = inject(HttpClient);
69
+ super('email/email-config', http);
70
+ this.apiBaseUrl = inject(APP_CONFIG).apiBaseUrl;
71
+ }
72
+ /**
73
+ * Send test email to verify configuration
74
+ */
75
+ sendTest(configId, recipient) {
76
+ return this.http.post(`${this.apiBaseUrl}/email/send/test`, { emailConfigId: configId, recipient });
77
+ }
78
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: EmailConfigApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
79
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: EmailConfigApiService, providedIn: 'root' });
80
+ }
81
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: EmailConfigApiService, decorators: [{
82
+ type: Injectable,
83
+ args: [{
84
+ providedIn: 'root',
85
+ }]
86
+ }], ctorParameters: () => [] });
87
+
88
+ /**
89
+ * Email Template API Service
90
+ * Handles email template CRUD operations
91
+ * Endpoint: POST /email/email-template/*
92
+ */
93
+ class EmailTemplateApiService extends ApiResourceService {
94
+ constructor() {
95
+ const http = inject(HttpClient);
96
+ super('email/email-template', http);
97
+ }
98
+ /**
99
+ * Get template by slug (POST-only RPC pattern)
100
+ */
101
+ getBySlug(slug) {
102
+ return this.http.post(`${this.baseUrl}/get-by-slug`, { slug });
103
+ }
104
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: EmailTemplateApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
105
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: EmailTemplateApiService, providedIn: 'root' });
106
+ }
107
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: EmailTemplateApiService, decorators: [{
108
+ type: Injectable,
109
+ args: [{
110
+ providedIn: 'root',
111
+ }]
112
+ }], ctorParameters: () => [] });
113
+
114
+ /**
115
+ * Email Send Service
116
+ * Handles sending emails directly or using templates
117
+ */
118
+ class EmailSendService {
119
+ http = inject(HttpClient);
120
+ baseUrl = inject(APP_CONFIG).apiBaseUrl;
121
+ /**
122
+ * Send email directly (without template)
123
+ */
124
+ sendDirect(dto) {
125
+ return this.http.post(`${this.baseUrl}/email/send/direct`, dto);
126
+ }
127
+ /**
128
+ * Send email using template
129
+ */
130
+ sendTemplate(dto) {
131
+ return this.http.post(`${this.baseUrl}/email/send/template`, dto);
132
+ }
133
+ /**
134
+ * Send test email (for testing configuration)
135
+ */
136
+ sendTest(dto) {
137
+ return this.http.post(`${this.baseUrl}/email/send/test`, { emailConfigId: dto.configId, recipient: dto.recipient });
138
+ }
139
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: EmailSendService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
140
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: EmailSendService, providedIn: 'root' });
141
+ }
142
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: EmailSendService, decorators: [{
143
+ type: Injectable,
144
+ args: [{
145
+ providedIn: 'root',
146
+ }]
147
+ }] });
148
+
149
+ /**
150
+ * Manages the email builder UI state including schema, selection, and UI toggles.
151
+ * Should be provided at the EmailBuilder component level (not root).
152
+ */
153
+ class EmailBuilderStateService {
154
+ // Schema state
155
+ _schema = signal(createEmailSchema(), ...(ngDevMode ? [{ debugName: "_schema" }] : []));
156
+ _isDirty = signal(false, ...(ngDevMode ? [{ debugName: "_isDirty" }] : []));
157
+ // Selection state
158
+ _selectedSectionId = signal(null, ...(ngDevMode ? [{ debugName: "_selectedSectionId" }] : []));
159
+ _selectedBlockId = signal(null, ...(ngDevMode ? [{ debugName: "_selectedBlockId" }] : []));
160
+ // View mode
161
+ _viewMode = signal('visual', ...(ngDevMode ? [{ debugName: "_viewMode" }] : []));
162
+ // Public readonly signals
163
+ schema = this._schema.asReadonly();
164
+ isDirty = this._isDirty.asReadonly();
165
+ selectedSectionId = this._selectedSectionId.asReadonly();
166
+ selectedBlockId = this._selectedBlockId.asReadonly();
167
+ viewMode = this._viewMode.asReadonly();
168
+ // Computed signals
169
+ sections = computed(() => this._schema().sections, ...(ngDevMode ? [{ debugName: "sections" }] : []));
170
+ templateName = computed(() => this._schema().name, ...(ngDevMode ? [{ debugName: "templateName" }] : []));
171
+ subject = computed(() => this._schema().subject, ...(ngDevMode ? [{ debugName: "subject" }] : []));
172
+ settings = computed(() => this._schema().settings ?? DEFAULT_EMAIL_SETTINGS, ...(ngDevMode ? [{ debugName: "settings" }] : []));
173
+ variables = computed(() => this._schema().variables ?? [], ...(ngDevMode ? [{ debugName: "variables" }] : []));
174
+ headerSection = computed(() => {
175
+ return this.sections().find((s) => s.type === 'header') ?? null;
176
+ }, ...(ngDevMode ? [{ debugName: "headerSection" }] : []));
177
+ bodySection = computed(() => {
178
+ return this.sections().find((s) => s.type === 'body') ?? null;
179
+ }, ...(ngDevMode ? [{ debugName: "bodySection" }] : []));
180
+ footerSection = computed(() => {
181
+ return this.sections().find((s) => s.type === 'footer') ?? null;
182
+ }, ...(ngDevMode ? [{ debugName: "footerSection" }] : []));
183
+ selectedSection = computed(() => {
184
+ const sectionId = this._selectedSectionId();
185
+ if (!sectionId)
186
+ return null;
187
+ return this.sections().find((s) => s.id === sectionId) ?? null;
188
+ }, ...(ngDevMode ? [{ debugName: "selectedSection" }] : []));
189
+ selectedBlock = computed(() => {
190
+ const blockId = this._selectedBlockId();
191
+ if (!blockId)
192
+ return null;
193
+ for (const section of this.sections()) {
194
+ const block = section.blocks.find((b) => b.id === blockId);
195
+ if (block)
196
+ return block;
197
+ }
198
+ return null;
199
+ }, ...(ngDevMode ? [{ debugName: "selectedBlock" }] : []));
200
+ allBlocks = computed(() => {
201
+ return this.sections().flatMap((s) => s.blocks);
202
+ }, ...(ngDevMode ? [{ debugName: "allBlocks" }] : []));
203
+ // =========================================
204
+ // Schema Operations
205
+ // =========================================
206
+ /**
207
+ * Load an existing schema into the builder
208
+ */
209
+ loadSchema(schema) {
210
+ const cloned = structuredClone(schema);
211
+ this._schema.set(cloned);
212
+ this._isDirty.set(false);
213
+ this._selectedSectionId.set(null);
214
+ this._selectedBlockId.set(null);
215
+ }
216
+ /**
217
+ * Create a new empty schema
218
+ */
219
+ createNewSchema(name = 'Untitled Template') {
220
+ this._schema.set(createEmailSchema({ name }));
221
+ this._isDirty.set(true);
222
+ this._selectedSectionId.set(null);
223
+ this._selectedBlockId.set(null);
224
+ }
225
+ /**
226
+ * Update template metadata
227
+ */
228
+ updateSchemaMeta(updates) {
229
+ this._schema.update((schema) => ({
230
+ ...schema,
231
+ ...updates,
232
+ }));
233
+ this._isDirty.set(true);
234
+ }
235
+ /**
236
+ * Mark schema as saved (clears dirty flag)
237
+ */
238
+ markAsSaved() {
239
+ this._isDirty.set(false);
240
+ }
241
+ /**
242
+ * Set view mode (visual or html)
243
+ */
244
+ setViewMode(mode) {
245
+ this._viewMode.set(mode);
246
+ }
247
+ // =========================================
248
+ // Section Operations
249
+ // =========================================
250
+ /**
251
+ * Update section properties
252
+ */
253
+ updateSection(sectionId, updates) {
254
+ this._schema.update((schema) => ({
255
+ ...schema,
256
+ sections: schema.sections.map((s) => s.id === sectionId ? { ...s, ...updates } : s),
257
+ }));
258
+ this._isDirty.set(true);
259
+ }
260
+ // =========================================
261
+ // Block Operations
262
+ // =========================================
263
+ /**
264
+ * Add a new block to a section
265
+ */
266
+ addBlock(sectionId, blockType, block) {
267
+ const newBlock = {
268
+ id: crypto.randomUUID(),
269
+ type: blockType,
270
+ order: 0,
271
+ content: this.getDefaultContent(blockType),
272
+ ...block,
273
+ };
274
+ this._schema.update((schema) => ({
275
+ ...schema,
276
+ sections: schema.sections.map((s) => {
277
+ if (s.id !== sectionId)
278
+ return s;
279
+ const blocks = [...s.blocks, newBlock];
280
+ // Update order
281
+ blocks.forEach((b, i) => (b.order = i));
282
+ return { ...s, blocks };
283
+ }),
284
+ }));
285
+ this._isDirty.set(true);
286
+ return newBlock.id;
287
+ }
288
+ /**
289
+ * Update block properties
290
+ */
291
+ updateBlock(sectionId, blockId, updates) {
292
+ this._schema.update((schema) => ({
293
+ ...schema,
294
+ sections: schema.sections.map((s) => {
295
+ if (s.id !== sectionId)
296
+ return s;
297
+ return {
298
+ ...s,
299
+ blocks: s.blocks.map((b) => (b.id === blockId ? { ...b, ...updates } : b)),
300
+ };
301
+ }),
302
+ }));
303
+ this._isDirty.set(true);
304
+ }
305
+ /**
306
+ * Delete a block from a section
307
+ */
308
+ deleteBlock(sectionId, blockId) {
309
+ this._schema.update((schema) => ({
310
+ ...schema,
311
+ sections: schema.sections.map((s) => {
312
+ if (s.id !== sectionId)
313
+ return s;
314
+ const blocks = s.blocks.filter((b) => b.id !== blockId);
315
+ // Update order
316
+ blocks.forEach((b, i) => (b.order = i));
317
+ return { ...s, blocks };
318
+ }),
319
+ }));
320
+ this._isDirty.set(true);
321
+ // Clear selection if deleted block was selected
322
+ if (this._selectedBlockId() === blockId) {
323
+ this._selectedBlockId.set(null);
324
+ }
325
+ }
326
+ /**
327
+ * Reorder blocks within a section
328
+ */
329
+ reorderBlocks(sectionId, fromIndex, toIndex) {
330
+ this._schema.update((schema) => ({
331
+ ...schema,
332
+ sections: schema.sections.map((s) => {
333
+ if (s.id !== sectionId)
334
+ return s;
335
+ const blocks = [...s.blocks];
336
+ const [removed] = blocks.splice(fromIndex, 1);
337
+ blocks.splice(toIndex, 0, removed);
338
+ // Update order
339
+ blocks.forEach((b, i) => (b.order = i));
340
+ return { ...s, blocks };
341
+ }),
342
+ }));
343
+ this._isDirty.set(true);
344
+ }
345
+ /**
346
+ * Duplicate a block
347
+ */
348
+ duplicateBlock(sectionId, blockId) {
349
+ const section = this.sections().find((s) => s.id === sectionId);
350
+ const block = section?.blocks.find((b) => b.id === blockId);
351
+ if (!block)
352
+ return null;
353
+ const newBlock = {
354
+ ...structuredClone(block),
355
+ id: crypto.randomUUID(),
356
+ };
357
+ this._schema.update((schema) => ({
358
+ ...schema,
359
+ sections: schema.sections.map((s) => {
360
+ if (s.id !== sectionId)
361
+ return s;
362
+ const blockIndex = s.blocks.findIndex((b) => b.id === blockId);
363
+ const blocks = [...s.blocks];
364
+ blocks.splice(blockIndex + 1, 0, newBlock);
365
+ // Update order
366
+ blocks.forEach((b, i) => (b.order = i));
367
+ return { ...s, blocks };
368
+ }),
369
+ }));
370
+ this._isDirty.set(true);
371
+ return newBlock.id;
372
+ }
373
+ // =========================================
374
+ // Selection Operations
375
+ // =========================================
376
+ /**
377
+ * Select a section
378
+ */
379
+ selectSection(sectionId) {
380
+ this._selectedSectionId.set(sectionId);
381
+ this._selectedBlockId.set(null);
382
+ }
383
+ /**
384
+ * Select a block
385
+ */
386
+ selectBlock(blockId) {
387
+ if (blockId) {
388
+ // Find the section containing this block
389
+ for (const section of this.sections()) {
390
+ if (section.blocks.some((b) => b.id === blockId)) {
391
+ this._selectedSectionId.set(section.id);
392
+ break;
393
+ }
394
+ }
395
+ }
396
+ this._selectedBlockId.set(blockId);
397
+ }
398
+ /**
399
+ * Clear all selection
400
+ */
401
+ clearSelection() {
402
+ this._selectedSectionId.set(null);
403
+ this._selectedBlockId.set(null);
404
+ }
405
+ // =========================================
406
+ // Helper Methods
407
+ // =========================================
408
+ /**
409
+ * Get default content for a block type
410
+ */
411
+ getDefaultContent(type) {
412
+ switch (type) {
413
+ case EmailBlockType.TEXT:
414
+ return { text: 'Enter your text here...', html: '<p>Enter your text here...</p>' };
415
+ case EmailBlockType.IMAGE:
416
+ return { src: '', alt: 'Image' };
417
+ case EmailBlockType.BUTTON:
418
+ return {
419
+ text: 'Click Here',
420
+ link: 'https://',
421
+ backgroundColor: '#007bff',
422
+ textColor: '#ffffff',
423
+ borderRadius: '4px',
424
+ };
425
+ case EmailBlockType.DIVIDER:
426
+ return { height: '1px', color: '#cccccc', style: 'solid' };
427
+ case EmailBlockType.HTML:
428
+ return { html: '<!-- Custom HTML -->' };
429
+ default:
430
+ return {};
431
+ }
432
+ }
433
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: EmailBuilderStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
434
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: EmailBuilderStateService });
435
+ }
436
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: EmailBuilderStateService, decorators: [{
437
+ type: Injectable
438
+ }] });
439
+
440
+ /**
441
+ * Email module routes
442
+ */
443
+ const EMAIL_ROUTES = [
444
+ {
445
+ path: '',
446
+ loadComponent: () => Promise.resolve().then(function () { return emailContainer_component; }).then((m) => m.EmailContainerComponent),
447
+ children: [
448
+ // Templates
449
+ {
450
+ path: 'templates',
451
+ children: [
452
+ {
453
+ path: '',
454
+ loadComponent: () => import('./flusys-ng-email-template-list.component-CGJxfZMT.mjs').then((m) => m.TemplateListComponent),
455
+ },
456
+ {
457
+ path: 'new',
458
+ loadComponent: () => import('./flusys-ng-email-template-form.component-DjzG_lG1.mjs').then((m) => m.TemplateFormComponent),
459
+ },
460
+ {
461
+ path: ':id',
462
+ loadComponent: () => import('./flusys-ng-email-template-form.component-DjzG_lG1.mjs').then((m) => m.TemplateFormComponent),
463
+ },
464
+ ],
465
+ },
466
+ // Configs
467
+ {
468
+ path: 'configs',
469
+ children: [
470
+ {
471
+ path: '',
472
+ loadComponent: () => import('./flusys-ng-email-email-config-list.component-CJhSPkCZ.mjs').then((m) => m.EmailConfigListComponent),
473
+ },
474
+ {
475
+ path: 'new',
476
+ loadComponent: () => import('./flusys-ng-email-email-config-form.component-8dOBD-Q-.mjs').then((m) => m.EmailConfigFormComponent),
477
+ },
478
+ {
479
+ path: ':id',
480
+ loadComponent: () => import('./flusys-ng-email-email-config-form.component-8dOBD-Q-.mjs').then((m) => m.EmailConfigFormComponent),
481
+ },
482
+ ],
483
+ },
484
+ // Default redirect
485
+ {
486
+ path: '',
487
+ redirectTo: 'templates',
488
+ pathMatch: 'full',
489
+ },
490
+ ],
491
+ },
492
+ ];
493
+
494
+ /**
495
+ * Email container component with tab navigation
496
+ */
497
+ class EmailContainerComponent {
498
+ tabs = [
499
+ { id: 'templates', label: 'Templates', icon: 'pi pi-file', route: 'templates' },
500
+ { id: 'configs', label: 'Configurations', icon: 'pi pi-cog', route: 'configs' },
501
+ ];
502
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: EmailContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
503
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: EmailContainerComponent, isStandalone: true, selector: "lib-email-container", ngImport: i0, template: `
504
+ <div class="email-page p-4">
505
+ <div class="email-header mb-4">
506
+ <h1 class="text-2xl font-bold m-0">Email</h1>
507
+ <p class="text-gray-500 mt-1">Manage email templates and provider configurations</p>
508
+ </div>
509
+
510
+ <div class="email-tabs flex gap-1 mb-4 border-b border-gray-200">
511
+ @for (tab of tabs; track tab.id) {
512
+ <a
513
+ [routerLink]="tab.route"
514
+ routerLinkActive="active"
515
+ class="tab-link flex items-center gap-2 px-4 py-2 rounded-t-lg cursor-pointer transition-colors">
516
+ <i [class]="tab.icon"></i>
517
+ <span>{{ tab.label }}</span>
518
+ </a>
519
+ }
520
+ </div>
521
+
522
+ <div class="email-content">
523
+ <router-outlet />
524
+ </div>
525
+ </div>
526
+ `, isInline: true, styles: [".tab-link{color:var(--text-color-secondary, #6b7280);text-decoration:none;border-bottom:2px solid transparent;margin-bottom:-1px}.tab-link.active{color:var(--primary-color, #3b82f6);border-bottom-color:var(--primary-color, #3b82f6);background-color:var(--surface-ground, #f9fafb)}.tab-link:hover:not(.active){background-color:var(--surface-hover, #f3f4f6)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
527
+ }
528
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: EmailContainerComponent, decorators: [{
529
+ type: Component,
530
+ args: [{ selector: 'lib-email-container', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, RouterOutlet, RouterLink, RouterLinkActive], template: `
531
+ <div class="email-page p-4">
532
+ <div class="email-header mb-4">
533
+ <h1 class="text-2xl font-bold m-0">Email</h1>
534
+ <p class="text-gray-500 mt-1">Manage email templates and provider configurations</p>
535
+ </div>
536
+
537
+ <div class="email-tabs flex gap-1 mb-4 border-b border-gray-200">
538
+ @for (tab of tabs; track tab.id) {
539
+ <a
540
+ [routerLink]="tab.route"
541
+ routerLinkActive="active"
542
+ class="tab-link flex items-center gap-2 px-4 py-2 rounded-t-lg cursor-pointer transition-colors">
543
+ <i [class]="tab.icon"></i>
544
+ <span>{{ tab.label }}</span>
545
+ </a>
546
+ }
547
+ </div>
548
+
549
+ <div class="email-content">
550
+ <router-outlet />
551
+ </div>
552
+ </div>
553
+ `, styles: [".tab-link{color:var(--text-color-secondary, #6b7280);text-decoration:none;border-bottom:2px solid transparent;margin-bottom:-1px}.tab-link.active{color:var(--primary-color, #3b82f6);border-bottom-color:var(--primary-color, #3b82f6);background-color:var(--surface-ground, #f9fafb)}.tab-link:hover:not(.active){background-color:var(--surface-hover, #f3f4f6)}\n"] }]
554
+ }] });
555
+
556
+ var emailContainer_component = /*#__PURE__*/Object.freeze({
557
+ __proto__: null,
558
+ EmailContainerComponent: EmailContainerComponent
559
+ });
560
+
561
+ // =============================================================================
562
+ // @flusys/ng-email - Email Package
563
+ // =============================================================================
564
+ // Enums
565
+
566
+ /**
567
+ * Generated bundle index. Do not edit.
568
+ */
569
+
570
+ export { DEFAULT_EMAIL_SETTINGS, EMAIL_ROUTES, EmailBlockType, EmailBuilderStateService, EmailConfigApiService, EmailContainerComponent, EmailProviderEnum, EmailSendService, EmailTemplateApiService, createEmailSchema };
571
+ //# sourceMappingURL=flusys-ng-email.mjs.map