import { Injectable } from '@angular/core'; import { MixpanelService } from '@core/services/mixpanel.service'; import { PortalDeterminationService } from '@core/services/portal-determination.service'; import { SSOService } from '@core/services/sso.service'; import { StorageService } from '@core/services/storage.service'; import { TokenService } from '@core/services/token/token.service'; import currencies from '@core/static-assets/currencies'; import languages from '@core/static-assets/languages'; import { APIAdminClient, ConfigureSettingsMap, CurrencySetting } from '@core/typings/api/admin-client.typing'; import { BrandingColors, ClientBrandingForUi, ClientBrandingFromApi, ColorPaletteType, ColorPurpose, ColorScheme, ColorSchemePayload, SaveBrandingForApi } from '@core/typings/branding.typing'; import { TokenResponse } from '@core/typings/token.typing'; import { environment } from '@environment'; import { UserService } from '@features/users/user.service'; import { ArrayHelpersService } from '@yourcause/common'; import { I18nService } from '@yourcause/common/i18n'; import { LogService } from '@yourcause/common/logging'; import { NotifierService } from '@yourcause/common/notifier'; import { AttachYCState, BaseYCService } from '@yourcause/common/state'; import { ClientSettingsResources } from './client-settings.resources'; import { ClientSettingsState } from './client-settings.state'; export const DEFAULT_PRIMARY_COLOR = '#1870b8'; export const DEFAULT_SECONDARY_COLOR = '#00b4f1'; export const DEFAULT_UTILITY_COLOR = '#72bf44'; export const DEFAULT_CHART_PRIMARY_COLOR = '#936daf'; export const DEFAULT_CHART_SECONDARY_COLOR = '#00b795'; export const DEFAULT_CHART_UTILITY_COLOR = '#f47521'; @AttachYCState(ClientSettingsState) @Injectable({ providedIn: 'root' }) export class ClientSettingsService extends BaseYCService { defaultBranding: BrandingColors = { brandPrimary: '#4d5259', brandSecondary: '#22b6bc', brandUtility: '#79c55a' }; constructor ( private logger: LogService, private clientSettingsResources: ClientSettingsResources, private storageService: StorageService, private tokenService: TokenService, private userService: UserService, private ssoService: SSOService, private notifier: NotifierService, private i18n: I18nService, private portal: PortalDeterminationService, private mixpanel: MixpanelService, private arrayHelper: ArrayHelpersService ) { super(); } get isManager () { return this.portal.isManager; } get langKeys () { return this.get('langKeys'); } get currencies () { return this.get('currencies'); } get clientBranding () { return this.get('clientBranding'); } get configureSettingsMap () { return this.get('configureSettingsMap'); } get clientSettings () { return this.get('clientSettings'); } get selectedLanguages () { return this.get('selectedLanguages'); } get selectedCurrencies () { return this.get('selectedCurrencies') || []; } get clientIntAuthorities () { return this.get('clientIntAuthorities'); } get noSelectedLanguages () { return !this.selectedLanguages || !this.selectedLanguages.length || this.selectedLanguages.length === 1; // just a default lang, no other options } get selectedCurrenciesForForm () { const _currencies = this.get('selectedCurrencies'); const arrayOfCurrencies = _currencies.map((currency) => { return currency.code; }); return arrayOfCurrencies; } get defaultCurrency () { return this.get('defaultCurrency') || 'USD'; } get defaultLanguage () { return this.get('defaultLanguage') || 'en-US'; } get domesticRegAuthFilters () { return this.get('domesticRegAuthFilters'); } get showIsEmployeeOfClientCheckbox () { return this.clientSettings?.applicantTypesSupported === APIAdminClient.SupportedApplicantTypes.EmployeesAndNonprofits; } /** * Returns the correct value to send when saving the 'isEmployeeOfClient' setting * * @param formVal: Answer from form control * @returns the value to send in the payload when saving */ getIsEmployeeOfClientForPayload (formVal: boolean) { let returnVal = false; const setting = this.clientSettings.applicantTypesSupported; if (setting === APIAdminClient.SupportedApplicantTypes.EmployeesAndNonprofits) { returnVal = formVal; } else if (setting === APIAdminClient.SupportedApplicantTypes.EmployeeOnly) { returnVal = true; } return returnVal; } /** * * @param branding: branding to set */ setClientBranding (branding: ClientBrandingForUi) { this.set('clientBranding', branding); } /** * Sets all langs */ setAllLangs () { const keys = languages.map((lang) => lang.culture); this.set('langKeys', keys); } /** * Sets all currencies */ setAllCurrencies () { this.set('currencies', currencies); } /** * * @returns all currency options */ getAllCurrencyOptions () { this.setAllCurrencies(); return this.arrayHelper.sort((this.currencies || []).map((currency) => { return { label: `${currency.displayName} (${currency.code})`, value: currency.code }; }), 'label'); } /** * Set Language Settings */ async setLanguageSettings () { const _languages = await this.clientSettingsResources.getSelectedLanguages(); const defaultLang = _languages.find((lang) => { return lang.default; }); const defaultLangCode = defaultLang ? defaultLang.culture : 'en-US'; this.set( 'defaultLanguage', defaultLangCode ); let langs = _languages.map((lang) => lang.culture); if (langs.length === 0) { langs = ['en-US']; } if (!langs.includes(defaultLangCode)) { langs.push(defaultLangCode); } this.set('selectedLanguages', langs); } /** * Set Currency Settings */ async setCurrencySettings () { const _currencies = await this.clientSettingsResources.getSelectedCurrencies(); this.setSelectedCurrencies(_currencies); } /** * Sets Selected Currencies * * @param _currencies: currencies to set */ setSelectedCurrencies (_currencies: CurrencySetting[]) { this.set('selectedCurrencies', _currencies); this.setDefaultCurrency(); } /** * Set Default Currency */ setDefaultCurrency () { const hasUSD = this.selectedCurrencies.find((curr) => { return curr.code === 'USD'; }); const firstCurrencyFound = this.selectedCurrencies[0] ? this.selectedCurrencies[0].code : 'USD'; const found = this.selectedCurrencies.find((curr) => { return curr.default; }); const defaultCurrency = found ? found.code : (hasUSD ? 'USD' : firstCurrencyFound); this.set('defaultCurrency', defaultCurrency); } /** * Set Client Settings */ async setClientSettings () { const settings = await this.clientSettingsResources.getClientSettings(); const clientSettings = { ...settings }; this.set('clientSettings', clientSettings); } /** * Set Client Settings for Applicant * * @param clientId: client ID */ async setClientSettingsForApplicant (clientId: number) { const settings = await this.clientSettingsResources.getClientSettingsForApplicant( clientId ); const clientSettings = { ...settings, clientId }; this.set('clientSettings', clientSettings); } /** * Set Client Int Authorities */ async setClientIntAuthorities () { if (!this.clientIntAuthorities) { const country = this.get('clientSettings').country; const regAuthorities = await this.clientSettingsResources.getRegistrationAuthoritiees( country ); this.set('clientIntAuthorities', regAuthorities); this.setDomesticRegAuthFilters(); } } /** * Set Configure Settings Map */ configureClientSettings () { this.set('configureSettingsMap', { colors: { primary: this.clientBranding.brandPrimary, secondary: this.clientBranding.brandSecondary, utility: this.clientBranding.brandUtility, chartPrimary: this.clientBranding.chartPrimary || this.clientBranding.brandPrimary, chartSecondary: this.clientBranding.chartSecondary || this.clientBranding.brandSecondary, chartUtility: this.clientBranding.chartUtility || this.clientBranding.brandUtility, colorPalette: this.clientBranding.colorPalette || ColorPaletteType.SHADES }, logoUrl: this.clientBranding.logoUrl }); } /** * Set Configure Map * * @param map: map to set */ setConfigureMap (map: ConfigureSettingsMap) { this.set('configureSettingsMap', map); } /** * Set Available Applicant Currencies * * @param programId: the program ID */ async setAvailableApplicantCurrencies (programId: number) { const response = await this.clientSettingsResources.getAvailableApplicantCurrencies( programId ); this.setSelectedCurrencies(response); } /** * Set Domestic Reg Auth Filters */ setDomesticRegAuthFilters () { if (this.clientIntAuthorities.domesticRegistrationAuthorities) { const domesticAuths = this.clientIntAuthorities.domesticRegistrationAuthorities .map((regAuth) => { return { filterName: 'RegistrationAuthority', filterValue: regAuth.name }; }); this.set('domesticRegAuthFilters', domesticAuths); } } /** * Save Branding * * @param brandingPayload: branding to save * @param colorSchemePayload: color scheme to save */ async saveBranding ( brandingPayload: SaveBrandingForApi, colorSchemePayload: ColorSchemePayload ) { try { await Promise.all([ this.clientSettingsResources.saveBranding( brandingPayload ), this.clientSettingsResources.saveColorScheme( colorSchemePayload ) ]); this.setClientBranding({ ...this.get('clientBranding'), ...brandingPayload, ...this.getChartColors( colorSchemePayload.colorScheme, brandingPayload ) }); } catch (e) { this.notifier.error(this.i18n.translate( 'BRAND:textErrorUpdatingClientSettings', {}, 'There was an error updating client settings' )); } } /** * Update Language Settings * * @param payload: payoad with updates */ async updateLanguageSettings (payload: string[]) { try { await this.clientSettingsResources.updateClientLanguages(payload); this.notifier.success(this.i18n.translate( 'BRAND:textSuccessfullyUpdatedLanguages', {}, 'Successfully updated supported languages' )); await this.setLanguageSettings(); } catch (e) { this.logger.error(e); this.notifier.error(this.i18n.translate( 'BRAND:textErrorUpdatingLanguages', {}, 'There was an error updating supported languages' )); } } /** * Update Currency Settings * * @param _currencies: currencies */ async updateCurrencySettings (_currencies: string[]) { const payload = { codes: _currencies, default: this.defaultCurrency }; try { await this.clientSettingsResources.updateClientCurrencies(payload); this.notifier.success(this.i18n.translate( 'BRAND:textSuccessfullyUpdatedCurrencies', {}, 'Sucessfully updated supported currencies' )); await this.setCurrencySettings(); } catch (e) { this.logger.error(e); this.notifier.error(this.i18n.translate( 'BRAND:textErrorUpdatingCurrencies', {}, 'There was an error updating supported currencies' )); } } /** * Set Branding and Handle Subdomain */ async setBrandingAndHandleSubdomain () { await this.setBranding(); await this.handleSubdomain(); } /** * Set Branding */ async setBranding () { this.storageService.clearClientBranding(); const branding = await this.clientSettingsResources.getBranding(); const schemes = branding.clientColorSchemes || []; const brandPrimary = branding.brandPrimary || DEFAULT_PRIMARY_COLOR; const brandSecondary = branding.brandSecondary || DEFAULT_SECONDARY_COLOR; const brandUtility = branding.brandUtility || DEFAULT_UTILITY_COLOR; this.set('clientBranding', { ...branding, brandPrimary, brandSecondary, brandUtility, ...this.getChartColors( schemes, branding ) }); this.mixpanel.register({ 'Client Name': branding.name, 'Site Id': branding.siteId }); this.storageService.setClientBranding(branding); } /** * Get Chart Colors * * @param schemes: the color schemes * @param branding: the branding * @returns chart colors */ getChartColors ( schemes: ColorScheme[], branding: ClientBrandingForUi|ClientBrandingFromApi|SaveBrandingForApi ) { const primary = schemes.find((scheme) => { return scheme.colorPurpose === ColorPurpose.ChartPrimary; }); return { chartPrimary: this.getChartColor( schemes, ColorPurpose.ChartPrimary ) || branding.brandPrimary || DEFAULT_CHART_PRIMARY_COLOR, chartSecondary: this.getChartColor( schemes, ColorPurpose.ChartSecondary ) || branding.brandSecondary || DEFAULT_CHART_SECONDARY_COLOR, chartUtility: this.getChartColor( schemes, ColorPurpose.ChartUtility ) || branding.brandUtility || DEFAULT_CHART_UTILITY_COLOR, colorPalette: primary?.colorPalette || ColorPaletteType.SHADES }; } /** * Get Chart Color * * @param colorSchemes: color schemes * @param colorPurpose: color purpose * @returns the chart color */ getChartColor ( colorSchemes: ColorScheme[], colorPurpose: ColorPurpose ) { const found = colorSchemes.find((scheme) => { return scheme.colorPurpose === colorPurpose; }); return found?.color; } /** * Handle Subdomain */ async handleSubdomain () { const branding = this.clientBranding; if ( environment.supportsSubdomains && branding.subDomain && !location.hostname.toLowerCase().includes(branding.subDomain.toLowerCase()) ) { const nextPath = '/management/home/my-workspace'; const subdomain = branding.subDomain; const userToken = await this.tokenService.getLatestToken(true) as TokenResponse; const user = this.userService.user; const clientIdentifier = this.tokenService.getIdentifier(); this.tokenService.logout(false, false); await this.ssoService.handOffToSubdomain( subdomain, nextPath, userToken, user, clientIdentifier, this.ssoService.getIdToken() ); } } /** * Get Reg Authorities per Country * * @param country: country * @returns registration authorities for country */ async getRegAuthoritiesPerCountry (country: string) { const regAuthorities = await this.clientSettingsResources.getRegistrationAuthoritiees( country ); return regAuthorities; } }