From 6d7f932180334d9f27df896d7a84b7ef62889908 Mon Sep 17 00:00:00 2001 From: Francisco de la Vega Date: Tue, 21 Jul 2026 16:49:07 +0200 Subject: [PATCH] Update admin section to configure contact emails --- src/app/models/contact-us.constants.ts | 5 + .../pages/admin/email/email.component.html | 31 ++++- .../pages/admin/email/email.component.spec.ts | 128 +++++++++++++++++- src/app/pages/admin/email/email.component.ts | 126 ++++++++++++----- .../contact-us/contact-us-form.component.ts | 3 +- src/assets/i18n/en.json | 16 +++ src/assets/i18n/es.json | 16 +++ 7 files changed, 288 insertions(+), 37 deletions(-) create mode 100644 src/app/models/contact-us.constants.ts diff --git a/src/app/models/contact-us.constants.ts b/src/app/models/contact-us.constants.ts new file mode 100644 index 00000000..3a1378cf --- /dev/null +++ b/src/app/models/contact-us.constants.ts @@ -0,0 +1,5 @@ +export const CONTACT_US_SUPPORT_TYPES = ['general', 'technical', 'onboarding', 'legal'] as const; + +export type ContactUsSupportType = typeof CONTACT_US_SUPPORT_TYPES[number]; + +export type ContactUsDestinations = Record; diff --git a/src/app/pages/admin/email/email.component.html b/src/app/pages/admin/email/email.component.html index a44976ca..cfc03150 100644 --- a/src/app/pages/admin/email/email.component.html +++ b/src/app/pages/admin/email/email.component.html @@ -16,7 +16,7 @@ - {{ 'ADMIN._verification' | translate }} + {{ 'ADMIN._email' | translate }} @@ -26,7 +26,7 @@
-

{{ 'ADMIN._verification' | translate }}

+

{{ 'ADMIN._email' | translate }}


@@ -42,7 +42,7 @@

{{ 'ADMIN._ [ngClass]="emailForm.get('smtpPort')?.invalid == true && emailForm.value.smtpPort != '' ? 'border-red-600' : 'border-gray-300'" class="mb-2 bg-gray-50 dark:bg-secondary-300 border border-gray-300 dark:border-secondary-200 dark:text-white text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" /> - + @@ -56,6 +56,15 @@

{{ 'ADMIN._ + +

{{ 'ADMIN._contactUsDestinations' | translate }}

+ + @for(destination of contactUsDestinationFields; track destination.supportType) { + + + }
@@ -74,3 +83,19 @@

{{ 'ADMIN._ @if(showError){ } + +@if(showSuccess){ +
+ +
+} diff --git a/src/app/pages/admin/email/email.component.spec.ts b/src/app/pages/admin/email/email.component.spec.ts index 981e8327..6b30e521 100644 --- a/src/app/pages/admin/email/email.component.spec.ts +++ b/src/app/pages/admin/email/email.component.spec.ts @@ -2,13 +2,30 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TranslateModule } from '@ngx-translate/core'; import { RouterTestingModule } from '@angular/router/testing'; -import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { EmailComponent } from './email.component'; +import { environment } from 'src/environments/environment'; describe('EmailComponent', () => { let component: EmailComponent; let fixture: ComponentFixture; + let httpMock: HttpTestingController; + + const configUrl = `${environment.BASE_URL}/charging/api/orderManagement/notify/config`; + const configResponse = { + smtpServer: 'smtp.example.org', + smtpPort: '587', + email: 'source@example.org', + emailUser: 'smtp-user', + contactUsDestinations: { + general: 'general@example.org', + technical: 'technical@example.org', + onboarding: 'onboarding@example.org', + legal: 'legal@example.org' + } + }; + beforeEach(async () => { await TestBed.configureTestingModule({ schemas: [NO_ERRORS_SCHEMA], @@ -19,10 +36,119 @@ describe('EmailComponent', () => { fixture = TestBed.createComponent(EmailComponent); component = fixture.componentInstance; + httpMock = TestBed.inject(HttpTestingController); fixture.detectChanges(); + + const req = httpMock.expectOne(configUrl); + req.flush(configResponse); + }); + + afterEach(() => { + httpMock.verify(); }); it('should create', () => { expect(component).toBeTruthy(); }); + + it('should load source email and contact-us destinations separately', () => { + expect(component.emailForm.value.email).toBe('source@example.org'); + expect(component.emailForm.value.contactUsGeneralEmail).toBe('general@example.org'); + expect(component.emailForm.value.contactUsTechnicalEmail).toBe('technical@example.org'); + expect(component.emailForm.value.contactUsOnboardingEmail).toBe('onboarding@example.org'); + expect(component.emailForm.value.contactUsLegalEmail).toBe('legal@example.org'); + }); + + it('should leave contact-us destinations empty when loading legacy config', () => { + component.fillData({ + smtpServer: 'legacy-smtp.example.org', + smtpPort: '25', + email: 'source@example.org', + emailUser: 'legacy-user' + }); + + expect(component.emailForm.value.email).toBe('source@example.org'); + expect(component.emailForm.value.contactUsGeneralEmail).toBe(''); + expect(component.emailForm.value.contactUsTechnicalEmail).toBe(''); + expect(component.emailForm.value.contactUsOnboardingEmail).toBe(''); + expect(component.emailForm.value.contactUsLegalEmail).toBe(''); + }); + + it('should post source email and contact-us destinations as separate config fields', () => { + component.emailForm.setValue({ + smtpServer: 'smtp.example.org', + smtpPort: '587', + email: 'source@example.org', + emailUser: 'smtp-user', + emailPassword: 'secret', + contactUsGeneralEmail: 'general@example.org', + contactUsTechnicalEmail: 'technical@example.org', + contactUsOnboardingEmail: 'onboarding@example.org', + contactUsLegalEmail: 'legal@example.org' + }); + + component.addConfig(); + + const req = httpMock.expectOne(configUrl); + expect(req.request.method).toBe('POST'); + expect(req.request.body).toEqual({ + smtpServer: 'smtp.example.org', + smtpPort: '587', + email: 'source@example.org', + emailUser: 'smtp-user', + emailPassword: 'secret', + contactUsDestinations: { + general: 'general@example.org', + technical: 'technical@example.org', + onboarding: 'onboarding@example.org', + legal: 'legal@example.org' + } + }); + req.flush({}); + + expect(component.emailForm.value.smtpServer).toBe('smtp.example.org'); + expect(component.emailForm.value.smtpPort).toBe('587'); + expect(component.emailForm.value.email).toBe('source@example.org'); + expect(component.emailForm.value.emailUser).toBe('smtp-user'); + expect(component.emailForm.value.emailPassword).toBe(''); + expect(component.emailForm.value.contactUsGeneralEmail).toBe('general@example.org'); + expect(component.emailForm.value.contactUsTechnicalEmail).toBe('technical@example.org'); + expect(component.emailForm.value.contactUsOnboardingEmail).toBe('onboarding@example.org'); + expect(component.emailForm.value.contactUsLegalEmail).toBe('legal@example.org'); + expect(component.showSuccess).toBeTrue(); + expect(component.successMessage).toBe('ADMIN._emailConfigUpdated'); + }); + + it('should show an error and keep form values when update fails', () => { + component.emailForm.setValue({ + smtpServer: 'smtp.example.org', + smtpPort: '587', + email: 'source@example.org', + emailUser: 'smtp-user', + emailPassword: 'secret', + contactUsGeneralEmail: 'general@example.org', + contactUsTechnicalEmail: 'technical@example.org', + contactUsOnboardingEmail: 'onboarding@example.org', + contactUsLegalEmail: 'legal@example.org' + }); + + component.addConfig(); + + const req = httpMock.expectOne(configUrl); + req.flush({ error: 'Invalid config' }, { status: 400, statusText: 'Bad Request' }); + + expect(component.showError).toBeTrue(); + expect(component.errorMessage).toBe('Error: Invalid config'); + expect(component.showSuccess).toBeFalse(); + expect(component.emailForm.value.emailPassword).toBe('secret'); + expect(component.emailForm.value.contactUsGeneralEmail).toBe('general@example.org'); + }); + + it('should validate contact-us destination email format', () => { + component.emailForm.controls.contactUsTechnicalEmail.setValue('not-an-email'); + component.emailForm.controls.contactUsTechnicalEmail.updateValueAndValidity(); + + expect(component.emailForm.controls.contactUsTechnicalEmail.invalid).toBeTrue(); + expect(component.emailForm.controls.contactUsTechnicalEmail.hasError('email')).toBeTrue(); + }); }); diff --git a/src/app/pages/admin/email/email.component.ts b/src/app/pages/admin/email/email.component.ts index 566db78c..a8fb644c 100644 --- a/src/app/pages/admin/email/email.component.ts +++ b/src/app/pages/admin/email/email.component.ts @@ -1,9 +1,29 @@ import { HttpClient } from '@angular/common/http'; import { Component } from '@angular/core'; import { FormGroup, FormControl, Validators } from '@angular/forms'; +import { + CONTACT_US_SUPPORT_TYPES, + ContactUsDestinations, + ContactUsSupportType +} from 'src/app/models/contact-us.constants'; import { EventMessageService } from 'src/app/services/event-message.service'; import { environment } from 'src/environments/environment'; +interface EmailConfig { + smtpServer?: string; + smtpPort?: string; + email?: string; + emailUser?: string; + emailPassword?: string; + contactUsDestinations?: Partial; +} + +type ContactUsDestinationControlName = + | 'contactUsGeneralEmail' + | 'contactUsTechnicalEmail' + | 'contactUsOnboardingEmail' + | 'contactUsLegalEmail'; + @Component({ selector: 'email', templateUrl: './email.component.html', @@ -13,13 +33,39 @@ export class EmailComponent { showError: boolean = false; errorMessage: string = ''; + showSuccess: boolean = false; + successMessage: string = ''; + + private readonly contactUsDestinationControlNames: Record = { + general: 'contactUsGeneralEmail', + technical: 'contactUsTechnicalEmail', + onboarding: 'contactUsOnboardingEmail', + legal: 'contactUsLegalEmail' + }; + + private readonly contactUsDestinationLabels: Record = { + general: 'ADMIN._contactUsGeneralEmail', + technical: 'ADMIN._contactUsTechnicalEmail', + onboarding: 'ADMIN._contactUsOnboardingEmail', + legal: 'ADMIN._contactUsLegalEmail' + }; + + readonly contactUsDestinationFields = CONTACT_US_SUPPORT_TYPES.map((supportType) => ({ + supportType, + controlName: this.contactUsDestinationControlNames[supportType], + labelKey: this.contactUsDestinationLabels[supportType] + })); emailForm = new FormGroup({ smtpServer: new FormControl('', [Validators.required]), smtpPort: new FormControl('', [Validators.required]), - email: new FormControl('', [Validators.required]), + email: new FormControl('', [Validators.required, Validators.email]), emailUser: new FormControl('', [Validators.required]), - emailPassword: new FormControl('', [Validators.required]) + emailPassword: new FormControl('', [Validators.required]), + contactUsGeneralEmail: new FormControl('', [Validators.required, Validators.email]), + contactUsTechnicalEmail: new FormControl('', [Validators.required, Validators.email]), + contactUsOnboardingEmail: new FormControl('', [Validators.required, Validators.email]), + contactUsLegalEmail: new FormControl('', [Validators.required, Validators.email]) }); constructor( @@ -35,13 +81,41 @@ export class EmailComponent { this.eventMessage.emitAdminCategories(true); } - fillData(data: any) { + showSuccessMessage(message: string) { + this.showError = false; + this.showSuccess = true; + this.successMessage = message; + setTimeout(() => { + this.showSuccess = false; + }, 3000); + } + + showErrorMessage(message: string) { + this.showSuccess = false; + this.showError = true; + this.errorMessage = message; + setTimeout(() => { + this.showError = false; + }, 3000); + } + + getErrorMessage(error: any, defaultMessage: string): string { + return error?.error?.error ? 'Error: ' + error.error.error : defaultMessage; + } + + fillData(data: EmailConfig) { + const contactUsDestinations = data.contactUsDestinations ?? {}; + this.emailForm.setValue({ - smtpServer: data.smtpServer, - smtpPort: data.smtpPort, - email: data.email, - emailUser: data.emailUser, - emailPassword: '' + smtpServer: data.smtpServer ?? '', + smtpPort: data.smtpPort ?? '', + email: data.email ?? '', + emailUser: data.emailUser ?? '', + emailPassword: '', + contactUsGeneralEmail: contactUsDestinations.general ?? '', + contactUsTechnicalEmail: contactUsDestinations.technical ?? '', + contactUsOnboardingEmail: contactUsDestinations.onboarding ?? '', + contactUsLegalEmail: contactUsDestinations.legal ?? '' }); } @@ -53,47 +127,35 @@ export class EmailComponent { }, error: error => { console.error('There was an error while getting config!', error); - if(error.error.error){ - console.log(error) - this.errorMessage = 'Error: ' + error.error.error; - } else { - this.errorMessage = 'There was an error while getting the config'; - } - this.showError = true; - setTimeout(() => { - this.showError = false; - }, 3000); + this.showErrorMessage(this.getErrorMessage(error, 'There was an error while getting the config')); } }) } addConfig() { - // Get the product specification const url = `${environment.BASE_URL}/charging/api/orderManagement/notify/config`; const body = { "smtpServer": this.emailForm.value.smtpServer, "smtpPort": this.emailForm.value.smtpPort, "email": this.emailForm.value.email, "emailUser": this.emailForm.value.emailUser, - "emailPassword": this.emailForm.value.emailPassword + "emailPassword": this.emailForm.value.emailPassword, + "contactUsDestinations": { + "general": this.emailForm.value.contactUsGeneralEmail, + "technical": this.emailForm.value.contactUsTechnicalEmail, + "onboarding": this.emailForm.value.contactUsOnboardingEmail, + "legal": this.emailForm.value.contactUsLegalEmail + } } return this.http.post(url, body).subscribe({ - next: data => { - this.fillData(data); + next: () => { + this.emailForm.patchValue({ emailPassword: '' }); + this.showSuccessMessage('ADMIN._emailConfigUpdated'); }, error: error => { console.error('There was an error while updating!', error); - if(error.error.error){ - console.log(error) - this.errorMessage = 'Error: ' + error.error.error; - } else { - this.errorMessage = 'There was an error while updating the config'; - } - this.showError = true; - setTimeout(() => { - this.showError = false; - }, 3000); + this.showErrorMessage(this.getErrorMessage(error, 'There was an error while updating the config')); } }) } diff --git a/src/app/pages/contact-us/contact-us-form.component.ts b/src/app/pages/contact-us/contact-us-form.component.ts index a7d914cd..357a5eb2 100644 --- a/src/app/pages/contact-us/contact-us-form.component.ts +++ b/src/app/pages/contact-us/contact-us-form.component.ts @@ -12,6 +12,7 @@ import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { faThumbsUp } from '@fortawesome/pro-regular-svg-icons'; import { TranslateModule } from "@ngx-translate/core"; import { Subject, takeUntil } from 'rxjs'; +import { CONTACT_US_SUPPORT_TYPES } from "src/app/models/contact-us.constants"; import { ContactUsService } from '../../services/contactUs.service'; export interface IContactUs { @@ -54,7 +55,7 @@ export class ContactUsFormComponent implements OnDestroy { submitted = false; submittedSuccessfully = false; - readonly supportOptions: string[] = ["general", "technical", "onboarding", "legal"]; + readonly supportOptions = CONTACT_US_SUPPORT_TYPES; form: FormGroup = this.fb.nonNullable.group({ supportType: ["", [Validators.required]], diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 7370816a..5cd56d45 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -1479,10 +1479,18 @@ "_add": "Add", "_revenue": "Revenue sharing", "_email": "Email", + "_sourceEmail": "Source email", "_smtpServer": "SMTP Server", "_smtpPort": "SMTP Port", "_emailUser": "Email User", "_emailPass": "Email Password", + "_contactUsDestinations": "Contact us destination emails", + "_contactUsGeneralEmail": "General inquiries destination email", + "_contactUsTechnicalEmail": "Technical support destination email", + "_contactUsOnboardingEmail": "Onboarding support destination email", + "_contactUsLegalEmail": "Legal support destination email", + "_updateSuccessTitle": "Update success", + "_emailConfigUpdated": "Email configuration updated successfully.", "_defaultCatalog": "Default catalog", "_defaultCatalogId": "Default catalog ID", "_noDefaultCatalogConfigured": "No default catalog is currently configured.", @@ -2556,10 +2564,18 @@ "_add": "Add", "_revenue": "Revenue sharing", "_email": "Email", + "_sourceEmail": "Source email", "_smtpServer": "SMTP Server", "_smtpPort": "SMTP Port", "_emailUser": "Email User", "_emailPass": "Email Password", + "_contactUsDestinations": "Contact us destination emails", + "_contactUsGeneralEmail": "General inquiries destination email", + "_contactUsTechnicalEmail": "Technical support destination email", + "_contactUsOnboardingEmail": "Onboarding support destination email", + "_contactUsLegalEmail": "Legal support destination email", + "_updateSuccessTitle": "Update success", + "_emailConfigUpdated": "Email configuration updated successfully.", "_defaultCatalog": "Default catalog", "_defaultCatalogId": "Default catalog ID", "_noDefaultCatalogConfigured": "No default catalog is currently configured.", diff --git a/src/assets/i18n/es.json b/src/assets/i18n/es.json index 7370816a..5cd56d45 100644 --- a/src/assets/i18n/es.json +++ b/src/assets/i18n/es.json @@ -1479,10 +1479,18 @@ "_add": "Add", "_revenue": "Revenue sharing", "_email": "Email", + "_sourceEmail": "Source email", "_smtpServer": "SMTP Server", "_smtpPort": "SMTP Port", "_emailUser": "Email User", "_emailPass": "Email Password", + "_contactUsDestinations": "Contact us destination emails", + "_contactUsGeneralEmail": "General inquiries destination email", + "_contactUsTechnicalEmail": "Technical support destination email", + "_contactUsOnboardingEmail": "Onboarding support destination email", + "_contactUsLegalEmail": "Legal support destination email", + "_updateSuccessTitle": "Update success", + "_emailConfigUpdated": "Email configuration updated successfully.", "_defaultCatalog": "Default catalog", "_defaultCatalogId": "Default catalog ID", "_noDefaultCatalogConfigured": "No default catalog is currently configured.", @@ -2556,10 +2564,18 @@ "_add": "Add", "_revenue": "Revenue sharing", "_email": "Email", + "_sourceEmail": "Source email", "_smtpServer": "SMTP Server", "_smtpPort": "SMTP Port", "_emailUser": "Email User", "_emailPass": "Email Password", + "_contactUsDestinations": "Contact us destination emails", + "_contactUsGeneralEmail": "General inquiries destination email", + "_contactUsTechnicalEmail": "Technical support destination email", + "_contactUsOnboardingEmail": "Onboarding support destination email", + "_contactUsLegalEmail": "Legal support destination email", + "_updateSuccessTitle": "Update success", + "_emailConfigUpdated": "Email configuration updated successfully.", "_defaultCatalog": "Default catalog", "_defaultCatalogId": "Default catalog ID", "_noDefaultCatalogConfigured": "No default catalog is currently configured.",