@flusys/ng-email 3.0.1 → 4.0.0-rc

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-email-config-list.component-oznfQvxo.mjs","sources":["../../../projects/ng-email/pages/config/email-config-list.component.ts"],"sourcesContent":["import {\n Component,\n computed,\n inject,\n signal,\n} from '@angular/core';\nimport { Router } from '@angular/router';\nimport { firstValueFrom } from 'rxjs';\nimport { APP_CONFIG, DEFAULT_APP_NAME, TRANSLATE_ADAPTER } from '@flusys/ng-core';\nimport { LAYOUT_AUTH_STATE } from '@flusys/ng-layout';\nimport { AngularModule, EMAIL_CONFIG_PERMISSIONS, HasPermissionDirective, PrimeModule, TranslatePipe } from '@flusys/ng-shared';\nimport { ConfirmationService, MessageService } from 'primeng/api';\nimport { EmailProviderEnum } from '../../enums/email-provider.enum';\nimport { IEmailConfig } from '../../interfaces/email-config.interface';\nimport { EmailConfigApiService } from '../../services/email-config-api.service';\n\n/**\n * Email configuration list component\n */\n@Component({\n selector: 'lib-email-config-list',\n imports: [AngularModule, PrimeModule, HasPermissionDirective, TranslatePipe],\n providers: [ConfirmationService, MessageService],\n template: `\n <div class=\"card\">\n <div class=\"flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3 mb-4\">\n <div>\n <h3 class=\"text-lg sm:text-xl font-semibold m-0\">{{ 'email.config.title' | translate }}</h3>\n @if (showCompanyInfo()) {\n <p class=\"text-sm text-muted-color mt-1\">\n {{ 'common.company' | translate }}: {{ currentCompanyName() }}\n </p>\n }\n </div>\n <p-button\n *hasPermission=\"EMAIL_CONFIG_PERMISSIONS.CREATE\"\n [label]=\"'email.config.new' | translate\"\n icon=\"pi pi-plus\"\n (onClick)=\"onCreate()\"\n styleClass=\"w-full sm:w-auto\"\n />\n </div>\n\n <div class=\"overflow-x-auto -mx-4 sm:mx-0\">\n <p-table\n [value]=\"configs()\"\n [loading]=\"isLoading()\"\n [paginator]=\"totalRecords() > 0\"\n [rows]=\"pageSize()\"\n [first]=\"first()\"\n [totalRecords]=\"totalRecords()\"\n [lazy]=\"true\"\n (onLazyLoad)=\"onLazyLoad($event)\"\n [rowsPerPageOptions]=\"[10, 25, 50]\"\n styleClass=\"p-datatable-sm\"\n [tableStyle]=\"{ 'min-width': '50rem' }\"\n >\n <ng-template #header>\n <tr>\n <th>{{ 'common.name' | translate }}</th>\n <th>{{ 'email.config.provider' | translate }}</th>\n <th class=\"hidden md:table-cell\">{{ 'email.config.from.email' | translate }}</th>\n <th>{{ 'common.status' | translate }}</th>\n <th class=\"hidden lg:table-cell\">{{ 'common.created' | translate }}</th>\n <th class=\"w-[120px]\">{{ 'common.actions' | translate }}</th>\n </tr>\n </ng-template>\n\n <ng-template #body let-config>\n <tr>\n <td>\n <i [class]=\"getProviderIcon(config.provider)\" class=\"mr-2 text-muted-color\"></i>\n {{ config.name }}\n @if (config.isDefault) {\n <p-tag [value]=\"'common.default' | translate\" severity=\"contrast\" class=\"ml-2\" />\n }\n </td>\n <td>\n <p-tag\n [value]=\"getProviderLabel(config.provider)\"\n [severity]=\"getProviderSeverity(config.provider)\"\n />\n </td>\n <td class=\"hidden md:table-cell\">{{ config.fromEmail || ('shared.na' | translate) }}</td>\n <td>\n <p-tag\n [value]=\"(config.isActive ? 'common.active' : 'common.inactive') | translate\"\n [severity]=\"config.isActive ? 'success' : 'secondary'\"\n />\n </td>\n <td class=\"hidden lg:table-cell\">{{ config.createdAt | date: 'short' }}</td>\n <td>\n <div class=\"flex gap-1\">\n <p-button\n *hasPermission=\"EMAIL_CONFIG_PERMISSIONS.UPDATE\"\n icon=\"pi pi-send\"\n [text]=\"true\"\n size=\"small\"\n [pTooltip]=\"'common.test' | translate\"\n (onClick)=\"onTest(config)\"\n />\n <p-button\n *hasPermission=\"EMAIL_CONFIG_PERMISSIONS.UPDATE\"\n icon=\"pi pi-pencil\"\n [text]=\"true\"\n severity=\"secondary\"\n size=\"small\"\n [pTooltip]=\"'common.edit' | translate\"\n (onClick)=\"onEdit(config.id)\"\n />\n <p-button\n *hasPermission=\"EMAIL_CONFIG_PERMISSIONS.DELETE\"\n icon=\"pi pi-trash\"\n [text]=\"true\"\n severity=\"danger\"\n size=\"small\"\n [pTooltip]=\"'common.delete' | translate\"\n (onClick)=\"onDelete(config)\"\n />\n </div>\n </td>\n </tr>\n </ng-template>\n\n <ng-template #emptymessage>\n <tr>\n <td colspan=\"6\" class=\"text-center py-4 text-muted-color\">\n {{ 'email.config.empty' | translate }}\n </td>\n </tr>\n </ng-template>\n </p-table>\n </div>\n </div>\n\n <!-- Test Email Dialog -->\n <p-dialog\n [header]=\"'email.config.test.dialog.title' | translate\"\n [visible]=\"showTestDialog()\"\n (visibleChange)=\"showTestDialog.set($event)\"\n [modal]=\"true\"\n [style]=\"{ width: '95vw', maxWidth: '400px' }\"\n [breakpoints]=\"{ '575px': '95vw' }\"\n >\n <div class=\"grid gap-4\">\n <div class=\"field\">\n <label class=\"block font-medium mb-2\">{{ 'email.config.configuration' | translate }}</label>\n <input\n pInputText\n [value]=\"selectedConfig()?.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.config.provider' | translate }}</label>\n <p-tag\n [value]=\"getProviderLabel(selectedConfig()?.provider || '')\"\n [severity]=\"getProviderSeverity(selectedConfig()?.provider || '')\"\n />\n </div>\n\n <div class=\"field\">\n <label class=\"block font-medium mb-2\">{{ 'email.config.recipient.email' | translate }} *</label>\n <input\n pInputText\n [ngModel]=\"testRecipient()\"\n (ngModelChange)=\"testRecipient.set($event)\"\n class=\"w-full\"\n [placeholder]=\"'email.recipient.example' | translate\"\n type=\"email\"\n />\n <small class=\"text-muted-color mt-1 block\">\n {{ 'email.config.test.dialog.hint' | translate }}\n </small>\n </div>\n </div>\n\n <ng-template #footer>\n <p-button\n [label]=\"'common.cancel' | translate\"\n severity=\"secondary\"\n [outlined]=\"true\"\n (onClick)=\"showTestDialog.set(false)\"\n />\n <p-button\n [label]=\"'email.config.send.test' | translate\"\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 EmailConfigListComponent {\n // Permission constants for template\n readonly EMAIL_CONFIG_PERMISSIONS = EMAIL_CONFIG_PERMISSIONS;\n\n private readonly router = inject(Router);\n private readonly configService = inject(EmailConfigApiService);\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, { optional: true });\n private readonly translateAdapter = inject(TRANSLATE_ADAPTER, { optional: true });\n\n readonly isLoading = signal(false);\n readonly configs = signal<IEmailConfig[]>([]);\n readonly totalRecords = signal(0);\n readonly pageSize = signal(10);\n readonly first = signal(0);\n\n readonly showCompanyInfo = computed(\n () => this.appConfig.enableCompanyFeature && !!this.companyContext,\n );\n readonly currentCompanyName = computed(\n () => this.companyContext?.currentCompanyInfo()?.name ?? DEFAULT_APP_NAME,\n );\n // Test dialog state (signals for zoneless change detection)\n readonly showTestDialog = signal(false);\n readonly selectedConfig = signal<IEmailConfig | null>(null);\n readonly isSendingTest = signal(false);\n readonly testRecipient = signal('');\n\n onCreate(): void {\n this.router.navigate(['/email/configs/new']);\n }\n\n onEdit(configId: string): void {\n this.router.navigate(['/email/configs', configId]);\n }\n\n async loadConfigs(): Promise<void> {\n this.isLoading.set(true);\n try {\n const response = await firstValueFrom(\n this.configService.getAll('', {\n pagination: {\n currentPage: Math.floor(this.first() / this.pageSize()),\n pageSize: this.pageSize(),\n },\n filter: {},\n select: [],\n sort: {},\n }),\n );\n\n if (response.success) {\n this.configs.set(response.data ?? []);\n this.totalRecords.set(response.meta?.total ?? 0);\n }\n } catch {\n // Error toast handled by global interceptor\n } finally {\n this.isLoading.set(false);\n }\n }\n\n onLazyLoad(event: { first?: number | null; rows?: number | null }): void {\n this.first.set(event.first ?? 0);\n this.pageSize.set(event.rows ?? 10);\n this.loadConfigs();\n }\n\n private readonly providerMeta: Record<\n string,\n { icon: string; labelKey: string; severity: 'success' | 'secondary' | 'info' | 'warn' | 'danger' | 'contrast' }\n > = {\n [EmailProviderEnum.SMTP]: { icon: 'pi pi-server', labelKey: 'email.providers.smtp', severity: 'info' },\n [EmailProviderEnum.SENDGRID]: { icon: 'pi pi-cloud', labelKey: 'email.providers.sendgrid', severity: 'success' },\n [EmailProviderEnum.MAILGUN]: { icon: 'pi pi-cloud', labelKey: 'email.providers.mailgun', severity: 'warn' },\n };\n\n private readonly defaultProviderMeta = { icon: 'pi pi-envelope', labelKey: '', severity: 'secondary' as const };\n\n getProviderIcon(provider: EmailProviderEnum): string {\n return this.providerMeta[provider]?.icon ?? this.defaultProviderMeta.icon;\n }\n\n getProviderLabel(provider: EmailProviderEnum | string): string {\n const labelKey = this.providerMeta[provider]?.labelKey;\n if (labelKey) {\n return this.translate(labelKey);\n }\n // Fallback to dynamic key for unknown providers\n return provider ? this.translate(`email.providers.${provider}`) : '';\n }\n\n getProviderSeverity(provider: EmailProviderEnum | string) {\n return this.providerMeta[provider]?.severity ?? this.defaultProviderMeta.severity;\n }\n\n onTest(config: IEmailConfig): void {\n this.selectedConfig.set(config);\n this.testRecipient.set('');\n this.showTestDialog.set(true);\n }\n\n async sendTestEmail(): Promise<void> {\n const config = this.selectedConfig();\n const recipient = this.testRecipient();\n if (!config || !recipient) {\n this.messageService.add({\n severity: 'warn',\n summary: this.translate('common.validation'),\n detail: this.translate('email.config.enter.recipient'),\n });\n return;\n }\n\n this.isSendingTest.set(true);\n try {\n const response = await firstValueFrom(\n this.configService.sendTest(config.id, recipient),\n );\n\n if (response.data?.success) {\n this.messageService.add({\n severity: 'success',\n summary: this.translate('common.success'),\n detail: this.translate('email.config.test.sent.success', { messageId: response.data.messageId ?? '' }),\n });\n this.showTestDialog.set(false);\n } else {\n this.messageService.add({\n severity: 'error',\n summary: this.translate('common.error'),\n detail: response.data?.error || this.translate('email.config.test.sent.failed'),\n });\n }\n } catch {\n // Error toast handled by global interceptor\n } finally {\n this.isSendingTest.set(false);\n }\n }\n\n onDelete(config: IEmailConfig): void {\n this.confirmationService.confirm({\n message: this.translate('email.config.delete.confirm', { name: config.name }),\n header: this.translate('email.config.delete.title'),\n icon: 'pi pi-exclamation-triangle',\n acceptButtonStyleClass: 'p-button-danger',\n accept: async () => {\n try {\n await this.configService.deleteAsync({ id: config.id, type: 'delete' });\n this.messageService.add({\n severity: 'success',\n summary: this.translate('common.success'),\n detail: this.translate('email.config.deleted'),\n });\n this.loadConfigs();\n } catch {\n // Error toast handled by global interceptor\n }\n },\n });\n }\n\n private translate(key: string, variables?: Record<string, string | number>): string {\n return this.translateAdapter?.translate(key, variables) ?? key;\n }\n}\n"],"names":["i6","i7","i8","i9"],"mappings":"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"}
@@ -1,12 +1,12 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, signal, computed, effect, ChangeDetectionStrategy, Component } from '@angular/core';
2
+ import { inject, signal, computed, effect, Component } from '@angular/core';
3
3
  import { toSignal } from '@angular/core/rxjs-interop';
4
4
  import { form, required, FormField } from '@angular/forms/signals';
5
5
  import * as i2 from '@angular/router';
6
6
  import { ActivatedRoute, Router } from '@angular/router';
7
- import { APP_CONFIG, DEFAULT_APP_NAME } from '@flusys/ng-core';
7
+ import { APP_CONFIG, TRANSLATE_ADAPTER, DEFAULT_APP_NAME } from '@flusys/ng-core';
8
8
  import { LAYOUT_AUTH_STATE } from '@flusys/ng-layout';
9
- import { AngularModule, PrimeModule } from '@flusys/ng-shared';
9
+ import { AngularModule, PrimeModule, TranslatePipe } from '@flusys/ng-shared';
10
10
  import { MessageService } from 'primeng/api';
11
11
  import { EmailTemplateApiService, EmailBuilderStateService } from './flusys-ng-email.mjs';
12
12
  import * as i1 from '@angular/forms';
@@ -28,6 +28,7 @@ class TemplateFormComponent {
28
28
  messageService = inject(MessageService);
29
29
  appConfig = inject(APP_CONFIG);
30
30
  companyContext = inject(LAYOUT_AUTH_STATE, { optional: true });
31
+ translateAdapter = inject(TRANSLATE_ADAPTER, { optional: true });
31
32
  state = inject(EmailBuilderStateService);
32
33
  // Route params as signal
33
34
  routeParams = toSignal(this.route.paramMap);
@@ -51,9 +52,9 @@ class TemplateFormComponent {
51
52
  }, ...(ngDevMode ? [{ debugName: "formModel" }] : []));
52
53
  /** Form with validation using Angular Signal Forms */
53
54
  templateForm = form(this.formModel, (f) => {
54
- required(f.name, { message: 'Template name is required' });
55
- required(f.slug, { message: 'Slug is required' });
56
- required(f.subject, { message: 'Subject is required' });
55
+ required(f.name);
56
+ required(f.slug);
57
+ required(f.subject);
57
58
  });
58
59
  /** Check if form is valid */
59
60
  isFormValid = computed(() => {
@@ -101,6 +102,9 @@ class TemplateFormComponent {
101
102
  this.state.loadSchema(template.schema);
102
103
  }
103
104
  }
105
+ catch {
106
+ // Error toast handled by global interceptor
107
+ }
104
108
  finally {
105
109
  this.isLoading.set(false);
106
110
  }
@@ -163,8 +167,8 @@ class TemplateFormComponent {
163
167
  if (!this.isFormValid()) {
164
168
  this.messageService.add({
165
169
  severity: 'warn',
166
- summary: 'Validation',
167
- detail: 'Please fill in all required fields.',
170
+ summary: this.translate('common.validation'),
171
+ detail: this.translate('common.fill.required.fields'),
168
172
  });
169
173
  return;
170
174
  }
@@ -184,7 +188,7 @@ class TemplateFormComponent {
184
188
  name: model.name,
185
189
  subject: model.subject,
186
190
  },
187
- htmlContent: model.htmlContent || '<p>Email content</p>',
191
+ htmlContent: model.htmlContent || `<p>${this.translate('email.template.default.content')}</p>`,
188
192
  textContent: textContent || undefined,
189
193
  isActive: model.isActive,
190
194
  isHtml: model.isHtml,
@@ -197,8 +201,8 @@ class TemplateFormComponent {
197
201
  }
198
202
  this.messageService.add({
199
203
  severity: 'success',
200
- summary: 'Success',
201
- detail: `Template ${this.isEditMode() ? 'updated' : 'created'} successfully.`,
204
+ summary: this.translate('common.success'),
205
+ detail: this.translate(this.isEditMode() ? 'email.template.updated' : 'email.template.created'),
202
206
  });
203
207
  this.router.navigate(['../'], { relativeTo: this.route });
204
208
  }
@@ -209,6 +213,9 @@ class TemplateFormComponent {
209
213
  this.isLoading.set(false);
210
214
  }
211
215
  }
216
+ translate(key, variables) {
217
+ return this.translateAdapter?.translate(key, variables) ?? key;
218
+ }
212
219
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: TemplateFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
213
220
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: TemplateFormComponent, isStandalone: true, selector: "lib-template-form", providers: [MessageService, EmailBuilderStateService], ngImport: i0, template: `
214
221
  <div class="card mb-4">
@@ -216,11 +223,11 @@ class TemplateFormComponent {
216
223
  <div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3 mb-4">
217
224
  <div>
218
225
  <h3 class="text-lg sm:text-xl font-semibold m-0">
219
- {{ isEditMode() ? 'Edit Template' : 'New Template' }}
226
+ {{ (isEditMode() ? 'email.template.edit.title' : 'email.template.new.title') | translate }}
220
227
  </h3>
221
228
  @if (showCompanyInfo()) {
222
229
  <p class="text-sm text-muted-color mt-1">
223
- Company: {{ currentCompanyName() }}
230
+ {{ 'common.company' | translate }}: {{ currentCompanyName() }}
224
231
  </p>
225
232
  }
226
233
  </div>
@@ -229,53 +236,53 @@ class TemplateFormComponent {
229
236
  <!-- Form Fields -->
230
237
  <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
231
238
  <div class="field">
232
- <label for="name" class="block font-medium mb-2">Template Name *</label>
239
+ <label for="name" class="block font-medium mb-2">{{ 'email.template.name' | translate }} *</label>
233
240
  <input
234
241
  pInputText
235
242
  id="name"
236
243
  [formField]="templateForm.name"
237
244
  class="w-full"
238
- placeholder="e.g., Welcome Email"
245
+ [placeholder]="'email.template.name.example' | translate"
239
246
  />
240
247
  </div>
241
248
 
242
249
  <div class="field">
243
- <label for="slug" class="block font-medium mb-2">Slug *</label>
250
+ <label for="slug" class="block font-medium mb-2">{{ 'email.template.slug' | translate }} *</label>
244
251
  <input
245
252
  pInputText
246
253
  id="slug"
247
254
  [formField]="templateForm.slug"
248
255
  class="w-full"
249
- placeholder="e.g., welcome-email"
256
+ [placeholder]="'email.template.slug.example' | translate"
250
257
  />
251
258
  </div>
252
259
 
253
260
  <div class="field md:col-span-2">
254
- <label for="subject" class="block font-medium mb-2">Subject *</label>
261
+ <label for="subject" class="block font-medium mb-2">{{ 'email.template.subject' | translate }} *</label>
255
262
  <input
256
263
  pInputText
257
264
  id="subject"
258
265
  [formField]="templateForm.subject"
259
266
  class="w-full"
260
- [placeholder]="'e.g., Welcome to ' + '{{' + 'appName' + '}}' + '!'"
267
+ [placeholder]="'email.template.subject.example' | translate"
261
268
  />
262
- <small class="text-muted-color">Use {{ '{{variableName}}' }} for dynamic content</small>
269
+ <small class="text-muted-color">{{ 'email.template.variable.hint' | translate }}</small>
263
270
  </div>
264
271
 
265
272
  <div class="field">
266
- <label for="description" class="block font-medium mb-2">Description</label>
273
+ <label for="description" class="block font-medium mb-2">{{ 'common.description' | translate }}</label>
267
274
  <input
268
275
  pInputText
269
276
  id="description"
270
277
  [formField]="templateForm.description"
271
278
  class="w-full"
272
- placeholder="Brief description of the template"
279
+ [placeholder]="'email.template.desc.placeholder' | translate"
273
280
  />
274
281
  </div>
275
282
 
276
283
  <div class="field flex items-center gap-2">
277
284
  <p-toggleswitch [formField]="templateForm.isActive" />
278
- <label>Active</label>
285
+ <label>{{ 'common.active' | translate }}</label>
279
286
  </div>
280
287
  </div>
281
288
 
@@ -284,17 +291,17 @@ class TemplateFormComponent {
284
291
  <!-- Editor Panel -->
285
292
  <div class="card">
286
293
  <div class="flex justify-between items-center mb-3">
287
- <h3 class="font-semibold">Content</h3>
294
+ <h3 class="font-semibold">{{ 'email.template.content' | translate }}</h3>
288
295
  <div class="flex gap-1">
289
296
  <p-button
290
- label="HTML"
297
+ [label]="'email.template.html' | translate"
291
298
  [outlined]="editorMode() !== 'html'"
292
299
  [severity]="editorMode() === 'html' ? 'primary' : 'secondary'"
293
300
  size="small"
294
301
  (onClick)="setEditorMode('html')"
295
302
  />
296
303
  <p-button
297
- label="Plain Text"
304
+ [label]="'email.template.plain.text' | translate"
298
305
  [outlined]="editorMode() !== 'text'"
299
306
  [severity]="editorMode() === 'text' ? 'primary' : 'secondary'"
300
307
  size="small"
@@ -310,7 +317,7 @@ class TemplateFormComponent {
310
317
  [ngModel]="formModel().htmlContent"
311
318
  (ngModelChange)="updateFormField('htmlContent', $event)"
312
319
  class="w-full h-full font-mono text-sm resize-none overflow-auto border border-surface rounded"
313
- placeholder="<html>...</html>"
320
+ [placeholder]="'email.template.html.placeholder' | translate"
314
321
  wrap="off"
315
322
  ></textarea>
316
323
  } @else {
@@ -319,7 +326,7 @@ class TemplateFormComponent {
319
326
  [ngModel]="formModel().textContent"
320
327
  (ngModelChange)="updateFormField('textContent', $event)"
321
328
  class="w-full h-full font-mono text-sm resize-none overflow-auto border border-surface rounded"
322
- placeholder="Enter plain text content for email clients that don't support HTML"
329
+ [placeholder]="'email.template.text.placeholder' | translate"
323
330
  wrap="off"
324
331
  ></textarea>
325
332
  }
@@ -327,7 +334,7 @@ class TemplateFormComponent {
327
334
  @if (editorMode() === 'text') {
328
335
  <small class="text-muted-color mt-2 block">
329
336
  <i class="pi pi-info-circle mr-1"></i>
330
- Plain text version sent to email clients without HTML support
337
+ {{ 'email.template.plain.text.hint' | translate }}
331
338
  </small>
332
339
  }
333
340
  </div>
@@ -336,8 +343,8 @@ class TemplateFormComponent {
336
343
  @if (editorMode() === 'html') {
337
344
  <div class="card">
338
345
  <div class="flex flex-wrap justify-between items-center gap-2 mb-3">
339
- <h3 class="font-semibold m-0">Preview</h3>
340
- <p-tag value="Live Preview" severity="info" />
346
+ <h3 class="font-semibold m-0">{{ 'email.template.preview' | translate }}</h3>
347
+ <p-tag [value]="'email.template.live.preview' | translate" severity="info" />
341
348
  </div>
342
349
  <div class="border border-surface rounded bg-surface-0 dark:bg-surface-900 h-[400px] overflow-auto">
343
350
  @if (formModel().htmlContent) {
@@ -350,7 +357,7 @@ class TemplateFormComponent {
350
357
  <div class="flex items-center justify-center h-full text-muted-color">
351
358
  <div class="text-center p-4">
352
359
  <i class="pi pi-eye text-4xl mb-2"></i>
353
- <p class="m-0">Enter HTML content to see preview</p>
360
+ <p class="m-0">{{ 'email.template.enter.html.preview' | translate }}</p>
354
361
  </div>
355
362
  </div>
356
363
  }
@@ -362,13 +369,13 @@ class TemplateFormComponent {
362
369
  <!-- Form Actions -->
363
370
  <div class="flex justify-end gap-2 mt-6 pt-4 border-t border-surface">
364
371
  <p-button
365
- label="Cancel"
372
+ [label]="'common.cancel' | translate"
366
373
  severity="secondary"
367
374
  [outlined]="true"
368
375
  routerLink="../"
369
376
  />
370
377
  <p-button
371
- [label]="isEditMode() ? 'Update' : 'Create'"
378
+ [label]="(isEditMode() ? 'common.update' : 'common.create') | translate"
372
379
  icon="pi pi-save"
373
380
  [loading]="isLoading()"
374
381
  [disabled]="!isFormValid()"
@@ -378,14 +385,13 @@ class TemplateFormComponent {
378
385
  </div>
379
386
 
380
387
  <p-toast />
381
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i2$1.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i5.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "component", type: i8.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "directive", type: i6.Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["pTextareaPT", "pTextareaUnstyled", "autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { kind: "component", type: i8$1.Toast, selector: "p-toast", inputs: ["key", "autoZIndex", "baseZIndex", "life", "styleClass", "position", "preventOpenDuplicates", "preventDuplicates", "showTransformOptions", "hideTransformOptions", "showTransitionOptions", "hideTransitionOptions", "motionOptions", "breakpoints"], outputs: ["onClose"] }, { kind: "component", type: i8$2.ToggleSwitch, selector: "p-toggleswitch, p-toggleSwitch, p-toggle-switch", inputs: ["styleClass", "tabindex", "inputId", "readonly", "trueValue", "falseValue", "ariaLabel", "size", "ariaLabelledBy", "autofocus"], outputs: ["onChange"] }, { kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
388
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i2$1.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i5.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "component", type: i8.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "directive", type: i6.Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["pTextareaPT", "pTextareaUnstyled", "autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { kind: "component", type: i8$1.Toast, selector: "p-toast", inputs: ["key", "autoZIndex", "baseZIndex", "life", "styleClass", "position", "preventOpenDuplicates", "preventDuplicates", "showTransformOptions", "hideTransformOptions", "showTransitionOptions", "hideTransitionOptions", "motionOptions", "breakpoints"], outputs: ["onClose"] }, { kind: "component", type: i8$2.ToggleSwitch, selector: "p-toggleswitch, p-toggleSwitch, p-toggle-switch", inputs: ["styleClass", "tabindex", "inputId", "readonly", "trueValue", "falseValue", "ariaLabel", "size", "ariaLabelledBy", "autofocus"], outputs: ["onChange"] }, { kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
382
389
  }
383
390
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: TemplateFormComponent, decorators: [{
384
391
  type: Component,
385
392
  args: [{
386
393
  selector: 'lib-template-form',
387
- changeDetection: ChangeDetectionStrategy.OnPush,
388
- imports: [AngularModule, PrimeModule, FormField],
394
+ imports: [AngularModule, PrimeModule, FormField, TranslatePipe],
389
395
  providers: [MessageService, EmailBuilderStateService],
390
396
  template: `
391
397
  <div class="card mb-4">
@@ -393,11 +399,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
393
399
  <div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3 mb-4">
394
400
  <div>
395
401
  <h3 class="text-lg sm:text-xl font-semibold m-0">
396
- {{ isEditMode() ? 'Edit Template' : 'New Template' }}
402
+ {{ (isEditMode() ? 'email.template.edit.title' : 'email.template.new.title') | translate }}
397
403
  </h3>
398
404
  @if (showCompanyInfo()) {
399
405
  <p class="text-sm text-muted-color mt-1">
400
- Company: {{ currentCompanyName() }}
406
+ {{ 'common.company' | translate }}: {{ currentCompanyName() }}
401
407
  </p>
402
408
  }
403
409
  </div>
@@ -406,53 +412,53 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
406
412
  <!-- Form Fields -->
407
413
  <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
408
414
  <div class="field">
409
- <label for="name" class="block font-medium mb-2">Template Name *</label>
415
+ <label for="name" class="block font-medium mb-2">{{ 'email.template.name' | translate }} *</label>
410
416
  <input
411
417
  pInputText
412
418
  id="name"
413
419
  [formField]="templateForm.name"
414
420
  class="w-full"
415
- placeholder="e.g., Welcome Email"
421
+ [placeholder]="'email.template.name.example' | translate"
416
422
  />
417
423
  </div>
418
424
 
419
425
  <div class="field">
420
- <label for="slug" class="block font-medium mb-2">Slug *</label>
426
+ <label for="slug" class="block font-medium mb-2">{{ 'email.template.slug' | translate }} *</label>
421
427
  <input
422
428
  pInputText
423
429
  id="slug"
424
430
  [formField]="templateForm.slug"
425
431
  class="w-full"
426
- placeholder="e.g., welcome-email"
432
+ [placeholder]="'email.template.slug.example' | translate"
427
433
  />
428
434
  </div>
429
435
 
430
436
  <div class="field md:col-span-2">
431
- <label for="subject" class="block font-medium mb-2">Subject *</label>
437
+ <label for="subject" class="block font-medium mb-2">{{ 'email.template.subject' | translate }} *</label>
432
438
  <input
433
439
  pInputText
434
440
  id="subject"
435
441
  [formField]="templateForm.subject"
436
442
  class="w-full"
437
- [placeholder]="'e.g., Welcome to ' + '{{' + 'appName' + '}}' + '!'"
443
+ [placeholder]="'email.template.subject.example' | translate"
438
444
  />
439
- <small class="text-muted-color">Use {{ '{{variableName}}' }} for dynamic content</small>
445
+ <small class="text-muted-color">{{ 'email.template.variable.hint' | translate }}</small>
440
446
  </div>
441
447
 
442
448
  <div class="field">
443
- <label for="description" class="block font-medium mb-2">Description</label>
449
+ <label for="description" class="block font-medium mb-2">{{ 'common.description' | translate }}</label>
444
450
  <input
445
451
  pInputText
446
452
  id="description"
447
453
  [formField]="templateForm.description"
448
454
  class="w-full"
449
- placeholder="Brief description of the template"
455
+ [placeholder]="'email.template.desc.placeholder' | translate"
450
456
  />
451
457
  </div>
452
458
 
453
459
  <div class="field flex items-center gap-2">
454
460
  <p-toggleswitch [formField]="templateForm.isActive" />
455
- <label>Active</label>
461
+ <label>{{ 'common.active' | translate }}</label>
456
462
  </div>
457
463
  </div>
458
464
 
@@ -461,17 +467,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
461
467
  <!-- Editor Panel -->
462
468
  <div class="card">
463
469
  <div class="flex justify-between items-center mb-3">
464
- <h3 class="font-semibold">Content</h3>
470
+ <h3 class="font-semibold">{{ 'email.template.content' | translate }}</h3>
465
471
  <div class="flex gap-1">
466
472
  <p-button
467
- label="HTML"
473
+ [label]="'email.template.html' | translate"
468
474
  [outlined]="editorMode() !== 'html'"
469
475
  [severity]="editorMode() === 'html' ? 'primary' : 'secondary'"
470
476
  size="small"
471
477
  (onClick)="setEditorMode('html')"
472
478
  />
473
479
  <p-button
474
- label="Plain Text"
480
+ [label]="'email.template.plain.text' | translate"
475
481
  [outlined]="editorMode() !== 'text'"
476
482
  [severity]="editorMode() === 'text' ? 'primary' : 'secondary'"
477
483
  size="small"
@@ -487,7 +493,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
487
493
  [ngModel]="formModel().htmlContent"
488
494
  (ngModelChange)="updateFormField('htmlContent', $event)"
489
495
  class="w-full h-full font-mono text-sm resize-none overflow-auto border border-surface rounded"
490
- placeholder="<html>...</html>"
496
+ [placeholder]="'email.template.html.placeholder' | translate"
491
497
  wrap="off"
492
498
  ></textarea>
493
499
  } @else {
@@ -496,7 +502,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
496
502
  [ngModel]="formModel().textContent"
497
503
  (ngModelChange)="updateFormField('textContent', $event)"
498
504
  class="w-full h-full font-mono text-sm resize-none overflow-auto border border-surface rounded"
499
- placeholder="Enter plain text content for email clients that don't support HTML"
505
+ [placeholder]="'email.template.text.placeholder' | translate"
500
506
  wrap="off"
501
507
  ></textarea>
502
508
  }
@@ -504,7 +510,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
504
510
  @if (editorMode() === 'text') {
505
511
  <small class="text-muted-color mt-2 block">
506
512
  <i class="pi pi-info-circle mr-1"></i>
507
- Plain text version sent to email clients without HTML support
513
+ {{ 'email.template.plain.text.hint' | translate }}
508
514
  </small>
509
515
  }
510
516
  </div>
@@ -513,8 +519,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
513
519
  @if (editorMode() === 'html') {
514
520
  <div class="card">
515
521
  <div class="flex flex-wrap justify-between items-center gap-2 mb-3">
516
- <h3 class="font-semibold m-0">Preview</h3>
517
- <p-tag value="Live Preview" severity="info" />
522
+ <h3 class="font-semibold m-0">{{ 'email.template.preview' | translate }}</h3>
523
+ <p-tag [value]="'email.template.live.preview' | translate" severity="info" />
518
524
  </div>
519
525
  <div class="border border-surface rounded bg-surface-0 dark:bg-surface-900 h-[400px] overflow-auto">
520
526
  @if (formModel().htmlContent) {
@@ -527,7 +533,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
527
533
  <div class="flex items-center justify-center h-full text-muted-color">
528
534
  <div class="text-center p-4">
529
535
  <i class="pi pi-eye text-4xl mb-2"></i>
530
- <p class="m-0">Enter HTML content to see preview</p>
536
+ <p class="m-0">{{ 'email.template.enter.html.preview' | translate }}</p>
531
537
  </div>
532
538
  </div>
533
539
  }
@@ -539,13 +545,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
539
545
  <!-- Form Actions -->
540
546
  <div class="flex justify-end gap-2 mt-6 pt-4 border-t border-surface">
541
547
  <p-button
542
- label="Cancel"
548
+ [label]="'common.cancel' | translate"
543
549
  severity="secondary"
544
550
  [outlined]="true"
545
551
  routerLink="../"
546
552
  />
547
553
  <p-button
548
- [label]="isEditMode() ? 'Update' : 'Create'"
554
+ [label]="(isEditMode() ? 'common.update' : 'common.create') | translate"
549
555
  icon="pi pi-save"
550
556
  [loading]="isLoading()"
551
557
  [disabled]="!isFormValid()"
@@ -560,4 +566,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
560
566
  }], ctorParameters: () => [] });
561
567
 
562
568
  export { TemplateFormComponent };
563
- //# sourceMappingURL=flusys-ng-email-template-form.component-vlfHz6VE.mjs.map
569
+ //# sourceMappingURL=flusys-ng-email-template-form.component-CgJZiMCa.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flusys-ng-email-template-form.component-CgJZiMCa.mjs","sources":["../../../projects/ng-email/pages/template/template-form.component.ts"],"sourcesContent":["import {\n Component,\n computed,\n effect,\n inject,\n signal,\n} from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { form, FormField, required } from '@angular/forms/signals';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { APP_CONFIG, DEFAULT_APP_NAME, TRANSLATE_ADAPTER } from '@flusys/ng-core';\nimport { LAYOUT_AUTH_STATE } from '@flusys/ng-layout';\nimport { AngularModule, PrimeModule, TranslatePipe } from '@flusys/ng-shared';\nimport { MessageService } from 'primeng/api';\nimport { EmailTemplateApiService } from '../../services/email-template-api.service';\nimport { EmailBuilderStateService } from '../../services/email-builder-state.service';\nimport { IEmailTemplate } from '../../interfaces/email-template.interface';\n\n/** Form model interface for email template */\ninterface IEmailTemplateFormModel {\n id: string;\n name: string;\n slug: string;\n description: string;\n subject: string;\n htmlContent: string;\n textContent: string;\n isActive: boolean;\n isHtml: boolean;\n}\n\n/**\n * Email template form component (create/edit)\n * Uses Angular Signal Forms for reactive form handling\n */\n@Component({\n selector: 'lib-template-form',\n imports: [AngularModule, PrimeModule, FormField, TranslatePipe],\n providers: [MessageService, EmailBuilderStateService],\n template: `\n <div class=\"card mb-4\">\n <!-- Header -->\n <div class=\"flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3 mb-4\">\n <div>\n <h3 class=\"text-lg sm:text-xl font-semibold m-0\">\n {{ (isEditMode() ? 'email.template.edit.title' : 'email.template.new.title') | translate }}\n </h3>\n @if (showCompanyInfo()) {\n <p class=\"text-sm text-muted-color mt-1\">\n {{ 'common.company' | translate }}: {{ currentCompanyName() }}\n </p>\n }\n </div>\n </div>\n\n <!-- Form Fields -->\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div class=\"field\">\n <label for=\"name\" class=\"block font-medium mb-2\">{{ 'email.template.name' | translate }} *</label>\n <input\n pInputText\n id=\"name\"\n [formField]=\"templateForm.name\"\n class=\"w-full\"\n [placeholder]=\"'email.template.name.example' | translate\"\n />\n </div>\n\n <div class=\"field\">\n <label for=\"slug\" class=\"block font-medium mb-2\">{{ 'email.template.slug' | translate }} *</label>\n <input\n pInputText\n id=\"slug\"\n [formField]=\"templateForm.slug\"\n class=\"w-full\"\n [placeholder]=\"'email.template.slug.example' | translate\"\n />\n </div>\n\n <div class=\"field md:col-span-2\">\n <label for=\"subject\" class=\"block font-medium mb-2\">{{ 'email.template.subject' | translate }} *</label>\n <input\n pInputText\n id=\"subject\"\n [formField]=\"templateForm.subject\"\n class=\"w-full\"\n [placeholder]=\"'email.template.subject.example' | translate\"\n />\n <small class=\"text-muted-color\">{{ 'email.template.variable.hint' | translate }}</small>\n </div>\n\n <div class=\"field\">\n <label for=\"description\" class=\"block font-medium mb-2\">{{ 'common.description' | translate }}</label>\n <input\n pInputText\n id=\"description\"\n [formField]=\"templateForm.description\"\n class=\"w-full\"\n [placeholder]=\"'email.template.desc.placeholder' | translate\"\n />\n </div>\n\n <div class=\"field flex items-center gap-2\">\n <p-toggleswitch [formField]=\"templateForm.isActive\" />\n <label>{{ 'common.active' | translate }}</label>\n </div>\n </div>\n\n <!-- Content Editor & Preview -->\n <div class=\"grid grid-cols-1 gap-4\" [class.lg:grid-cols-2]=\"editorMode() === 'html'\">\n <!-- Editor Panel -->\n <div class=\"card\">\n <div class=\"flex justify-between items-center mb-3\">\n <h3 class=\"font-semibold\">{{ 'email.template.content' | translate }}</h3>\n <div class=\"flex gap-1\">\n <p-button\n [label]=\"'email.template.html' | translate\"\n [outlined]=\"editorMode() !== 'html'\"\n [severity]=\"editorMode() === 'html' ? 'primary' : 'secondary'\"\n size=\"small\"\n (onClick)=\"setEditorMode('html')\"\n />\n <p-button\n [label]=\"'email.template.plain.text' | translate\"\n [outlined]=\"editorMode() !== 'text'\"\n [severity]=\"editorMode() === 'text' ? 'primary' : 'secondary'\"\n size=\"small\"\n (onClick)=\"setEditorMode('text')\"\n />\n </div>\n </div>\n\n <div class=\"h-[400px]\">\n @if (editorMode() === 'html') {\n <textarea\n pTextarea\n [ngModel]=\"formModel().htmlContent\"\n (ngModelChange)=\"updateFormField('htmlContent', $event)\"\n class=\"w-full h-full font-mono text-sm resize-none overflow-auto border border-surface rounded\"\n [placeholder]=\"'email.template.html.placeholder' | translate\"\n wrap=\"off\"\n ></textarea>\n } @else {\n <textarea\n pTextarea\n [ngModel]=\"formModel().textContent\"\n (ngModelChange)=\"updateFormField('textContent', $event)\"\n class=\"w-full h-full font-mono text-sm resize-none overflow-auto border border-surface rounded\"\n [placeholder]=\"'email.template.text.placeholder' | translate\"\n wrap=\"off\"\n ></textarea>\n }\n </div>\n @if (editorMode() === 'text') {\n <small class=\"text-muted-color mt-2 block\">\n <i class=\"pi pi-info-circle mr-1\"></i>\n {{ 'email.template.plain.text.hint' | translate }}\n </small>\n }\n </div>\n\n <!-- Preview Panel (HTML mode only) -->\n @if (editorMode() === 'html') {\n <div class=\"card\">\n <div class=\"flex flex-wrap justify-between items-center gap-2 mb-3\">\n <h3 class=\"font-semibold m-0\">{{ 'email.template.preview' | translate }}</h3>\n <p-tag [value]=\"'email.template.live.preview' | translate\" severity=\"info\" />\n </div>\n <div class=\"border border-surface rounded bg-surface-0 dark:bg-surface-900 h-[400px] overflow-auto\">\n @if (formModel().htmlContent) {\n <iframe\n [srcdoc]=\"getPreviewHtml()\"\n class=\"w-full h-full border-0\"\n sandbox=\"allow-same-origin\"\n ></iframe>\n } @else {\n <div class=\"flex items-center justify-center h-full text-muted-color\">\n <div class=\"text-center p-4\">\n <i class=\"pi pi-eye text-4xl mb-2\"></i>\n <p class=\"m-0\">{{ 'email.template.enter.html.preview' | translate }}</p>\n </div>\n </div>\n }\n </div>\n </div>\n }\n </div>\n\n <!-- Form Actions -->\n <div class=\"flex justify-end gap-2 mt-6 pt-4 border-t border-surface\">\n <p-button\n [label]=\"'common.cancel' | translate\"\n severity=\"secondary\"\n [outlined]=\"true\"\n routerLink=\"../\"\n />\n <p-button\n [label]=\"(isEditMode() ? 'common.update' : 'common.create') | translate\"\n icon=\"pi pi-save\"\n [loading]=\"isLoading()\"\n [disabled]=\"!isFormValid()\"\n (onClick)=\"onSave()\"\n />\n </div>\n </div>\n\n <p-toast />\n `,\n})\nexport class TemplateFormComponent {\n private readonly route = inject(ActivatedRoute);\n private readonly router = inject(Router);\n private readonly templateService = inject(EmailTemplateApiService);\n private readonly messageService = inject(MessageService);\n private readonly appConfig = inject(APP_CONFIG);\n private readonly companyContext = inject(LAYOUT_AUTH_STATE, { optional: true });\n private readonly translateAdapter = inject(TRANSLATE_ADAPTER, { optional: true });\n readonly state = inject(EmailBuilderStateService);\n\n // Route params as signal\n private readonly routeParams = toSignal(this.route.paramMap);\n\n readonly isLoading = signal(false);\n readonly existingTemplate = signal<IEmailTemplate | null>(null);\n readonly isEditMode = computed(() => !!this.existingTemplate());\n readonly editorMode = signal<'html' | 'text'>('html');\n\n readonly showCompanyInfo = computed(\n () => this.appConfig.enableCompanyFeature && !!this.companyContext,\n );\n readonly currentCompanyName = computed(\n () => this.companyContext?.currentCompanyInfo()?.name ?? DEFAULT_APP_NAME,\n );\n\n /** Form model using signal */\n readonly formModel = signal<IEmailTemplateFormModel>({\n id: '',\n name: '',\n slug: '',\n description: '',\n subject: '',\n htmlContent: '',\n textContent: '',\n isActive: true,\n isHtml: true,\n });\n\n /** Form with validation using Angular Signal Forms */\n readonly templateForm = form(this.formModel, (f) => {\n required(f.name);\n required(f.slug);\n required(f.subject);\n });\n\n /** Check if form is valid */\n readonly isFormValid = computed(() => {\n const model = this.formModel();\n return (\n model.name.trim().length > 0 &&\n model.slug.trim().length > 0 &&\n model.subject.trim().length > 0\n );\n });\n\n /** Update a single form field */\n updateFormField<K extends keyof IEmailTemplateFormModel>(\n field: K,\n value: IEmailTemplateFormModel[K],\n ): void {\n this.formModel.update((m) => ({ ...m, [field]: value }));\n }\n\n constructor() {\n // Effect to handle route-based initialization\n effect(() => {\n const params = this.routeParams();\n if (!params) return;\n\n const id = params.get('id');\n if (id) {\n this.loadTemplate(id);\n }\n });\n }\n\n async loadTemplate(id: string): Promise<void> {\n this.isLoading.set(true);\n try {\n const response = await this.templateService.findByIdAsync(id);\n if (response.success && response.data) {\n const template = response.data;\n this.existingTemplate.set(template);\n this.formModel.set({\n id: template.id,\n name: template.name,\n slug: template.slug,\n description: template.description || '',\n subject: template.subject,\n htmlContent: template.htmlContent,\n textContent: template.textContent || '',\n isActive: template.isActive,\n isHtml: template.isHtml ?? true,\n });\n // Set editor mode based on saved template type\n this.editorMode.set(template.isHtml ? 'html' : 'text');\n this.state.loadSchema(template.schema);\n }\n } catch {\n // Error toast handled by global interceptor\n } finally {\n this.isLoading.set(false);\n }\n }\n\n setEditorMode(mode: 'html' | 'text'): void {\n const currentMode = this.editorMode();\n const model = this.formModel();\n\n // Sync content when switching modes\n if (currentMode === 'text' && mode === 'html') {\n // Switching from text to html - convert text to basic HTML if htmlContent is empty\n if (!model.htmlContent && model.textContent) {\n this.formModel.update((m) => ({ ...m, htmlContent: this.textToHtml(m.textContent) }));\n }\n } else if (currentMode === 'html' && mode === 'text') {\n // Switching from html to text - generate text from HTML if textContent is empty\n if (!model.textContent && model.htmlContent) {\n this.formModel.update((m) => ({ ...m, textContent: this.htmlToPlainText(m.htmlContent) }));\n }\n }\n\n this.editorMode.set(mode);\n this.formModel.update((m) => ({ ...m, isHtml: mode === 'html' }));\n }\n\n /** Convert plain text to basic HTML */\n private textToHtml(text: string): string {\n if (!text) return '';\n // Convert line breaks to paragraphs\n return text\n .split(/\\n\\n+/)\n .map((p) => `<p>${p.replace(/\\n/g, '<br>')}</p>`)\n .join('\\n');\n }\n\n /** Convert HTML to plain text (using DOMParser for safe parsing) */\n private htmlToPlainText(html: string): string {\n if (!html) return '';\n // Use DOMParser for safe HTML parsing (prevents XSS)\n const parser = new DOMParser();\n const doc = parser.parseFromString(html, 'text/html');\n // Get text content and clean up whitespace\n return (doc.body.textContent || '').replace(/\\s+/g, ' ').trim();\n }\n\n /** Get HTML for preview iframe */\n getPreviewHtml(): string {\n const baseStyles = `\n <style>\n body {\n font-family: Arial, sans-serif;\n margin: 16px;\n background-color: #ffffff;\n color: #333333;\n }\n img { max-width: 100%; height: auto; }\n </style>\n `;\n return baseStyles + (this.formModel().htmlContent || '');\n }\n\n async onSave(): Promise<void> {\n if (!this.isFormValid()) {\n this.messageService.add({\n severity: 'warn',\n summary: this.translate('common.validation'),\n detail: this.translate('common.fill.required.fields'),\n });\n return;\n }\n\n this.isLoading.set(true);\n try {\n const model = this.formModel();\n const schema = this.state.schema();\n // Use user-provided plain text, or auto-generate from HTML if empty\n const textContent = model.textContent || this.htmlToPlainText(model.htmlContent);\n\n const data = {\n name: model.name,\n slug: model.slug,\n description: model.description || undefined,\n subject: model.subject,\n schema: {\n ...schema,\n name: model.name,\n subject: model.subject,\n },\n htmlContent: model.htmlContent || `<p>${this.translate('email.template.default.content')}</p>`,\n textContent: textContent || undefined,\n isActive: model.isActive,\n isHtml: model.isHtml,\n };\n\n if (this.isEditMode()) {\n await this.templateService.updateAsync({ id: model.id, ...data });\n } else {\n await this.templateService.insertAsync(data);\n }\n\n this.messageService.add({\n severity: 'success',\n summary: this.translate('common.success'),\n detail: this.translate(this.isEditMode() ? 'email.template.updated' : 'email.template.created'),\n });\n\n this.router.navigate(['../'], { relativeTo: this.route });\n } catch {\n // Error toast handled by global interceptor\n } finally {\n this.isLoading.set(false);\n }\n }\n\n private translate(key: string, variables?: Record<string, string | number>): string {\n return this.translateAdapter?.translate(key, variables) ?? key;\n }\n}\n"],"names":["i3","i4","i5","i7","i8"],"mappings":"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"}