import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges } from '@angular/core'; import { AbstractControl, FormControl, Validators } from '@angular/forms'; import { Router } from '@angular/router'; import { SearchAllOrgsModalComponent } from '@core/components/search-all-orgs-modal/search-all-orgs-modal.component'; import { VettingRequestStatusId } from '@core/typings/application.typing'; import { ProcessingTypes } from '@core/typings/payment.typing'; import { OfflineProgramDetail } from '@core/typings/program.typing'; import { CyclesUI } from '@core/typings/ui/cycles.typing'; import { BudgetService } from '@features/budgets/budget.service'; import { PaymentProcessingService } from '@features/payment-processing/payment-processing.service'; import { ProgramService } from '@features/programs/program.service'; import { OrganizationEligibleForGivingStatus, SearchResult, TypeSafeFormBuilder, TypeSafeFormGroup } from '@yourcause/common'; import { I18nService } from '@yourcause/common/i18n'; import { ModalFactory } from '@yourcause/common/modals'; import { Subscription } from 'rxjs'; interface NonprofitContactGroup { contactFullName: string; contactEmail: string; contactWebsite: string; } export interface VettingInfo { requiresVetting: boolean; latestVettingRequestStatusForOrg: VettingRequestStatusId; vettingRequestContactName: string; vettingRequestContactEmail: string; vettingRequestContactWebsite: string; } @Component({ selector: 'gc-organization', templateUrl: './organization.component.html', styleUrls: ['./organization.component.scss'] }) export class OrganizationComponent implements OnInit, OnChanges, OnDestroy { @Input() selectedOrg: SearchResult; @Input() preventOrgChange: boolean; @Input() program: OfflineProgramDetail; @Input() cycleId: number; @Input() latestVettingInfoForOrg: { orgId: number; latestVettingStatus: VettingRequestStatusId; }; @Output() onSelectedOrgChange = new EventEmitter(); @Output() onVettingChange = new EventEmitter(); @Output() onVettingValidChange = new EventEmitter(); @Output() onOrgModalOpenOrClose = new EventEmitter(); preventOrgChangeMessage = this.i18n.translate( 'common:textUnableToChangeOrganization2', {}, 'The organization cannot be updated due to the application having one or more payments with a status of scheduled, outstanding, cleared, or voided.' ); confirmationText = this.i18n.translate( 'addOrg:textVetFoundOrgHelp', {}, 'This organization has not been vetted. Vetting is a process of determining if an organization is eligible to receive donations and grants. The organization must exist for 5 years to be ED vetted and completing vetting is not a commitment of any funding. Once you submit your application we will begin the vetting process. We do our best to expedite this process. A majority of these vetting requests are initiated in 7-10 days. If we cannot establish communication or the organization does not have the proper documentation readily available, this process can take much longer.' ); domesticVettingText = this.i18n.translate( 'addOrg:textVettingForDomesticOrgConfirm2', {}, 'The organization you have selected has not been vetted. Vetting is a process of determining if an organization is eligible to receive donations and grants. In order to begin the vetting process, a member of the organization must request to become an administrator through YourCause NPOconnect Nonprofit Platform. Once their request has been approved, they will be required to upload specific documentation. Please provide contact information below and we will send an email to the individual instructing them on how to start the vetting process.' ); nonprofitContactGroup: TypeSafeFormGroup; processor: ProcessingTypes; showContactForm: boolean; vettingInfo: VettingInfo; cycle: CyclesUI.ProgramCycle; sub = new Subscription(); constructor ( private modalFactory: ModalFactory, private router: Router, private i18n: I18nService, private formBuilder: TypeSafeFormBuilder, private paymentProcessingService: PaymentProcessingService, private programService: ProgramService, private budgetService: BudgetService ) { } get isNomination () { return location.pathname.includes('nomination'); } get processorIsYourCause () { return this.processor === ProcessingTypes.YourCause; } ngOnInit () { this.setCycleAndProcessor(); this.onOrgSelect(this.selectedOrg, false, true); this.nonprofitContactGroup = this.formBuilder.group({ contactFullName: '', contactEmail: '', contactWebsite: '' }, { validator: [ this.contactFormValidator() ] }); this.sub.add(this.nonprofitContactGroup.statusChanges.subscribe((val) => { this.onVettingValidChange.emit(val === 'VALID'); })); } ngOnChanges (changes: SimpleChanges) { /** If program or cycle changes, need to re-evaluate vetting */ const cycleOrProgramChanged = changes.cycleId || changes.program; if (cycleOrProgramChanged) { this.setCycleAndProcessor(); this.setVettingForOrg(false, false); } } setCycleAndProcessor () { this.cycle = this.program?.cycles.find((cycle) => { return cycle.id === this.cycleId; }); if (this.cycle) { this.processor = this.programService.getProcessorTypeForProgram( this.cycle.isClientProcessing ? ProcessingTypes.Client : ProcessingTypes.YourCause, this.cycle.budgetIds, this.paymentProcessingService.processorType, this.budgetService.get('budgets') ).processor; } else { this.processor = ProcessingTypes.YourCause; } } goToOrg () { this.router.navigate([ `/management/nonprofit/${ this.selectedOrg.document.nonprofitGuid || this.selectedOrg.document.id }/profile` ]); } contactFormValidator () { return (group: AbstractControl) => { let returnVal = null; const email = group.value.contactEmail; if (this.processorIsYourCause) { if (!group.value.contactFullName) { returnVal = { contactFullName: { contactRequired: { i18nKey: 'common:textContactRequired', defaultValue: 'Contact full name is required for creating a vetting request' } } }; } else if (!email) { returnVal = { contactEmail: { contactEmailRequired: { i18nKey: 'common:textContactEmailRequired', defaultValue: 'Contact email is required for creating a vetting request' } } }; } else if (email && Validators.email(new FormControl(email))) { returnVal = { contactEmail: { contactEmailValid: { i18nKey: 'common:textPleaseEnterValidEmail', defaultValue: 'Please enter a valid email' } } }; } } return returnVal; }; } emitOrgChange (org: SearchResult) { this.selectedOrg = org; this.onSelectedOrgChange.emit(this.selectedOrg); } emitVettingChange () { this.onVettingChange.emit(this.vettingInfo); } getShowVettingForm ( isFromInitOrOrgModal: boolean ): boolean { const eligibleForGivingStatusId = this.selectedOrg.document.eligibleForGivingStatusId; let hasOutstandingVettingRequest = false; // Only use the latest vetting status passed in if the org is still the same if ( this.latestVettingInfoForOrg && this.latestVettingInfoForOrg.orgId === +this.selectedOrg.document.id ) { const vettingStatus = this.latestVettingInfoForOrg.latestVettingStatus; hasOutstandingVettingRequest = vettingStatus && vettingStatus !== VettingRequestStatusId.DECLINED_ED; } return this.processorIsYourCause && !this.isNomination && !isFromInitOrOrgModal && !hasOutstandingVettingRequest && eligibleForGivingStatusId !== OrganizationEligibleForGivingStatus.ELIGIBLE; } clearVetting () { this.vettingInfo = { requiresVetting: false, latestVettingRequestStatusForOrg: null, vettingRequestContactName: '', vettingRequestContactEmail: '', vettingRequestContactWebsite: '' }; this.showContactForm = false; } async onOrgSelect ( org: SearchResult, fromOrgModal = false, isFromInit = false ) { this.emitOrgChange(org); this.setVettingForOrg(fromOrgModal, isFromInit); } setVettingForOrg ( fromOrgModal = false, isFromInit = false ) { if (!this.selectedOrg) { // Clear vetting when no org selected this.clearVetting(); } else { this.showContactForm = this.getShowVettingForm(isFromInit || fromOrgModal); if (this.showContactForm) { const orgCountry = this.selectedOrg.document.country; const usOrg = orgCountry.toLowerCase() === 'us' || orgCountry.toLowerCase() === 'united states'; if (usOrg) { // show domestic vetting text if org is not eligible and from US this.confirmationText = this.domesticVettingText; } else { this.confirmationText = this.confirmationText; } this.vettingInfo = { requiresVetting: this.showContactForm, latestVettingRequestStatusForOrg: null, vettingRequestContactName: this.vettingInfo?.vettingRequestContactName ?? '', vettingRequestContactEmail: this.vettingInfo?.vettingRequestContactEmail ?? '', vettingRequestContactWebsite: this.vettingInfo?.vettingRequestContactWebsite ?? '' }; } else { this.clearVetting(); } } this.emitVettingChange(); } setContactName (vettingRequestContactName: string) { this.vettingInfo = { ...this.vettingInfo, vettingRequestContactName }; this.emitVettingChange(); } setContactEmail (vettingRequestContactEmail: string) { this.vettingInfo = { ...this.vettingInfo, vettingRequestContactEmail }; this.emitVettingChange(); } setContactWebsite (vettingRequestContactWebsite: string) { this.vettingInfo = { ...this.vettingInfo, vettingRequestContactWebsite }; this.emitVettingChange(); } async showSelectOrgModal () { this.onOrgModalOpenOrClose.emit(true); const response = await this.modalFactory.open( SearchAllOrgsModalComponent, { saveNewlyCreatedOrgs: false, showPrivateOrgs: this.cycle.isClientProcessing, hideAddOrg: !this.program.allowAddOrg, orgSearchGuidelines: this.program.charityBucketDescription, isNomination: this.isNomination, processorType: this.cycle.isClientProcessing ? ProcessingTypes.Client : ProcessingTypes.YourCause, charityBucketId: this.program.charityBucketId, clientId: this.program.clientId }, { class: 'modal-xl', keyboard: false }); this.onOrgModalOpenOrClose.emit(false); if (response) { this.onOrgSelect(response.selectedOrg, true); } } ngOnDestroy () { this.sub.unsubscribe(); } }