import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; import { AbstractControl, Validators } from '@angular/forms'; import { AddressFormGroup } from '@core/components/address-block/address-block.component'; import { FormBuilderFactoryService } from '@core/services/form-builder-factory.service'; import { TimeZoneService } from '@core/services/time-zone.service'; import { APIAdminClient, ClientSettingsScenario } from '@core/typings/api/admin-client.typing'; import { LoginBehaviors } from '@core/typings/login-behaviors.typing'; import { ProcessingTypes } from '@core/typings/payment.typing'; import { ClientSettingsService } from '@features/client-settings/client-settings.service'; import { ArrayHelpersService, EmailExtensionValidator, PanelTypes, TypeaheadSelectOption, TypeSafeFormBuilder, TypeSafeFormGroup } from '@yourcause/common'; import { I18nService } from '@yourcause/common/i18n'; import { Subscription } from 'rxjs'; import { AdminClientService } from '../admin-client.service'; @Component({ selector: 'gc-client-settings-page', templateUrl: './client-settings-page.component.html', styleUrls: ['./client-settings-page.component.scss'] }) export class ClientSettingsPageComponent implements OnDestroy, OnInit { @Input() isEdit = false; @Input() client: APIAdminClient.Client; @Output() clientChange = new EventEmitter(); @Output() validityChange = new EventEmitter(); scenario: ClientSettingsScenario; formGroup: TypeSafeFormGroup; addressFormGroup: TypeSafeFormGroup; PanelTypes = PanelTypes; timeZoneOptions: TypeaheadSelectOption[]; timeZones = this.timeZoneService.getTimeZones(); clientListOptions: TypeaheadSelectOption[]; loginBehaviorList: TypeaheadSelectOption[]; clientProcessingTypeList: TypeaheadSelectOption[]; defaultCurrencyList: TypeaheadSelectOption[]; csrClients: APIAdminClient.CsrClientNotInGc[]; ClientSettingsScenario = ClientSettingsScenario; applicantTypeOptions = this.adminClientService.getApplicantTypeOptions(); sub = new Subscription(); constructor ( private i18n: I18nService, private formBuilder: TypeSafeFormBuilder, private adminClientService: AdminClientService, private formBuilderFactory: FormBuilderFactoryService, private clientSettingsService: ClientSettingsService, private timeZoneService: TimeZoneService, private arrayHelper: ArrayHelpersService ) { } get clients (): APIAdminClient.Client[] { return this.adminClientService.clients || []; } get langKeys (): string[] { return this.clientSettingsService.langKeys; } async ngOnInit () { await this.setCsrClients(); this.setTimezoneOptions(); this.setClientOptions(); this.setClientProcessingTypes(); this.setCurrencies(); this.setLoginBehaviors(); this.setFormGroup(); } setFormGroup () { let address = {} as AddressFormGroup; if (this.client) { address = { name: this.client?.name ?? '', address1: this.client?.address1 ?? '', address2: this.client?.address2 ?? '', city: this.client?.city ?? '', postalCode: this.client?.postalCode ?? '', countryCode: this.client?.country ?? '', stateProvRegCode: this.client?.state ?? '' }; } this.addressFormGroup = this.formBuilderFactory.createAddressGroup(address); this.formGroup = this.formBuilder.group({ name: [ this.client?.name ?? '', [Validators.required, Validators.maxLength(50)] ], csrClient: this.client?.affiliateId ?? '', rootFirstName: [ this.client?.rootUser?.firstName ?? '', [!this.isEdit ? Validators.required : undefined].filter((item) => !!item) ], rootLastName: [ this.client?.rootUser?.lastName ?? '', [!this.isEdit ? Validators.required : undefined].filter((item) => !!item) ], rootJobTitle: this.client?.rootUser?.jobTitle ?? '', rootEmail: [ this.client?.rootUser?.email ?? '', [ !this.isEdit ? Validators.required : undefined, EmailExtensionValidator ].filter((item) => !!item) ], subDomain: [ this.client?.subDomain ?? '', [ Validators.required, this.validateSubDomain.bind(this), this.validateSubDomainText.bind(this) ] ], loginBehavior: [ this.client?.loginBehavior ?? null, Validators.required ], clientProcessingType: [ this.client?.clientProcessingType ?? null, Validators.required ], defaultCurrency: [ this.client?.defaultCurrency ?? null, Validators.required ], defaultLanguage: [ this.client?.defaultLanguage ?? null, Validators.required ], defaultTimezone: [ this.client?.defaultTimezone ?? null, Validators.required ], overrideEmails: this.client ? this.client.overrideEmails : '', applicantTypesSupported: [ this.client?.applicantTypesSupported ?? APIAdminClient.SupportedApplicantTypes.EmployeesAndNonprofits, Validators.required ] }); this.validityChange.emit(this.formGroup.valid); this.sub.add(this.formGroup.valueChanges.subscribe(() => { this.validityChange.emit(this.formGroup.valid); this.handleChange(); })); } validateSubDomain (control: AbstractControl) { const matchingClients = this.clients.filter((matchingClient) => { return matchingClient.subDomain === control.value && (!this.client || matchingClient.clientId !== this.client.clientId); }); if (matchingClients.length > 0) { return { badSubdomain: { i18nKey: 'ADMIN:textSubDomainTaken', defaultValue: 'Subdomain is in use. Please enter a new name.' }}; } else { return null; } } validateSubDomainText (control: AbstractControl) { const domainName = control.value; const correctFormat = (/^[a-z1-9-]+$/).test(domainName); if (!correctFormat) { return { incorrectSubmdomainFormat: { i18nKey: 'ADMIN:textSubdomainFormattingHelp2', defaultValue: 'Subdomain must have no spaces and contain only lowercase letters, numbers, and dashes.' } }; } else { return null; } } setTimezoneOptions () { this.timeZoneOptions = this.timeZones.map((timeZone) => { return { label: timeZone.displayName, value: timeZone.id }; }); } setClientOptions () { // csrClients here represents clients that are not in GC yet, // we still want to show this option as selected on Edit and View if (this.client && this.client.affiliateId) { this.clientListOptions = [{ label: this.client.csrClientName, value: this.client.affiliateId }]; } else { this.clientListOptions = this.arrayHelper.sort(this.csrClients.map(client => { return { label: client.name, value: client.id }; }), 'label'); } } setLoginBehaviors () { this.loginBehaviorList = [ { label: this.i18n.translate( 'ADMIN:textSSO', {}, 'SSO' ), value: LoginBehaviors.SSO }, { label: this.i18n.translate( 'ADMIN:textSSOAndPlainLogin', {}, 'SSO and plain login' ), value: LoginBehaviors.SSOAndPlainLogin }, { label: this.i18n.translate( 'ADMIN:textPlainLogin', {}, 'Plain login' ), value: LoginBehaviors.PlainLogin } ]; } setClientProcessingTypes () { this.clientProcessingTypeList = [{ label: this.i18n.translate( 'ADMIN:textClientAndYourCause', {}, 'Client and YourCause' ), value: ProcessingTypes.Both }, { label: this.i18n.translate( 'common:labelClient', {}, 'Client' ), value: ProcessingTypes.Client }, { label: 'YourCause', value: ProcessingTypes.YourCause }]; } setCurrencies () { this.defaultCurrencyList = this.arrayHelper.sort( this.clientSettingsService.currencies.map((currency) => { return { label: this.i18n.translate( 'currencies:lblCurrencyOption' + currency.code, {}, currency.code + ' (' + currency.displayName + ')' ), value: currency.code }; }), 'label' ); } changeClient (selectedClientId: number) { if (!this.formGroup.value.subDomain && selectedClientId) { const clientSelected = this.csrClients.find((client) => { return client.id === selectedClientId; }); this.formGroup.get('subDomain').setValue( clientSelected.corpName ); } } async setCsrClients () { this.csrClients = await this.adminClientService.getCSRClientsNotInGC(); } handleChange () { const formGroupVal = this.formGroup.value; const addressFormGroup = this.addressFormGroup.value; const payload: APIAdminClient.Client = { ...this.client, clientId: this.client?.clientId ?? null, // adapt for backend affiliateId: +formGroupVal.csrClient || null, rootUser: { firstName: formGroupVal.rootFirstName, lastName: formGroupVal.rootLastName, jobTitle: formGroupVal.rootJobTitle, email: formGroupVal.rootEmail }, // must spread before props below or else country/state will be overwritten ...addressFormGroup, // adapt for backend country: addressFormGroup.countryCode, state: addressFormGroup.stateProvRegCode, ...formGroupVal }; this.clientChange.emit(payload); } ngOnDestroy () { this.sub.unsubscribe(); } }