import { Component, OnDestroy, OnInit } from '@angular/core'; import { AbstractControl } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import { SpinnerService } from '@core/services/spinner.service'; import { TimeZoneService } from '@core/services/time-zone.service'; import { FooterState } from '@core/states/footer.state'; import { APIAdminClient, CreateClientResponse } 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, FooterStates, PanelTypes, Tab, TypeaheadSelectOption } from '@yourcause/common'; import { AnalyticsService, EventType } from '@yourcause/common/analytics'; import { I18nService } from '@yourcause/common/i18n'; import { Subscription } from 'rxjs'; import { AdminClientService } from '../admin-client.service'; enum ClientTabOptions { SETTINGS, FEATURES } @Component({ selector: 'gc-manage-client-wrapper', templateUrl: './manage-client-wrapper.component.html', styleUrls: ['./manage-client-wrapper.component.scss'] }) export class ManageClientWrapperComponent implements OnDestroy, OnInit { isEdit = this.activatedRoute.snapshot.data.isEdit; ClientTabOptions = ClientTabOptions; activeTab: ClientTabOptions; pageHeader = this.i18n.translate( 'ADMIN:textCreateClient', {}, 'Create Client' ); id = this.activatedRoute.snapshot.params.id; client: APIAdminClient.Client; PanelTypes = PanelTypes; timeZoneOptions: TypeaheadSelectOption[]; timeZones = this.timeZoneService.getTimeZones(); clientListOptions: TypeaheadSelectOption[]; loginBehaviorList: TypeaheadSelectOption[]; clientProcessingTypeList: TypeaheadSelectOption[]; csrClients: APIAdminClient.CsrClientNotInGc[]; sub = new Subscription(); tabs: Tab[] = [{ link: `../settings`, labelKey: 'GLOBAL:textSettings', active: true, label: 'Settings', context: ClientTabOptions.SETTINGS }, { link: `../features`, labelKey: 'GLOBAL:textFeatures', label: 'Features', context: ClientTabOptions.FEATURES }]; constructor ( private activatedRoute: ActivatedRoute, private footerState: FooterState, private i18n: I18nService, private router: Router, private adminClientService: AdminClientService, private clientSettingsService: ClientSettingsService, private timeZoneService: TimeZoneService, private arrayHelper: ArrayHelpersService, private spinnerService: SpinnerService, private analyticsService: AnalyticsService ) { this.footerState.set('footerState', FooterStates.ACTION); this.footerState.set('footerActionLabel', i18n.translate('common:btnSave')); // default to disabled for init this.footerState.set('footerActionDisabled', true); this.footerState.setCancelLabel(this.i18n.translate('common:btnCancel')); this.footerState.setCancelAction(() => this.navigateBack()); this.footerState.set('footerAction', this.save); } get clients (): APIAdminClient.Client[] { return this.adminClientService.clients || []; } get langKeys (): string[] { return this.clientSettingsService.langKeys; } async ngOnInit () { this.setScenario(); this.id = +this.activatedRoute.snapshot.params.id; this.setActiveTab(ClientTabOptions.SETTINGS); this.setClientData(); } setScenario () { if (!this.isEdit) { this.footerState.clearAll(); } } setClientData () { this.client = this.adminClientService.clients.find((client) => { return client.clientId === +this.activatedRoute.snapshot.params.id; }); this.pageHeader = this.client?.name ?? this.pageHeader; } setActiveTab (tabIndex: number) { this.activeTab = this.tabs[tabIndex].context; } 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 () { if ( this.isEdit && !(this.client && this.client.csrClientName) ) { this.clientListOptions = this.arrayHelper.sort(this.csrClients.map(client => { return { label: client.name, value: client.id }; }), 'label'); } else { this.clientListOptions = [{ label: this.client.csrClientName, value: this.client.affiliateId }]; } } 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 }]; } navigateBack () { this.router.navigate(['/platform/client-management/clients']); this.analyticsService.emitEvent({ eventName: 'Cancel client config', eventType: EventType.Click, extras: null }); } save = async () => { await this.handleSave(this.client); this.analyticsService.emitEvent({ eventName: 'Save client config', eventType: EventType.Click, extras: null }); }; async handleSave (payload: APIAdminClient.SaveClient) { this.spinnerService.startSpinner(); const response = await this.adminClientService.createOrUpdateClient( payload ); this.spinnerService.stopSpinner(); if (response === CreateClientResponse.SUCCESS) { this.navigateBack(); } } handleValidityChange (valid: boolean) { this.footerState.set('footerActionDisabled', !valid); } ngOnDestroy () { this.sub.unsubscribe(); this.footerState.clearAll(); } }