import { Component, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { ApplicantService } from '@core/services/auth-user/applicant.service'; import { MixpanelService } from '@core/services/mixpanel.service'; import { SpinnerService } from '@core/services/spinner.service'; import { TimeZoneService } from '@core/services/time-zone.service'; import { TranslationService } from '@core/services/translation.service'; import { CyclesAPI } from '@core/typings/api/cycles.typing'; import { TimeZone } from '@core/typings/api/time-zone.typing'; import { MyApplicationFromApi } from '@core/typings/application.typing'; import { ProgramDetail } from '@core/typings/program.typing'; import { ProgramTranslationMap } from '@core/typings/translation.typing'; import { FormTypes } from '@features/configure-forms/form.typing'; import { CyclesService } from '@features/cycles/cycles.service'; import { InvitationService } from '@features/invitations/invitation.service'; import { InvitationApplicantHelpers, InvitationDetailResponse, InvitationResponse } from '@features/invitations/invitation.typing'; import { ProgramService } from '@features/programs/program.service'; import { AutoTableRepository, AutoTableRepositoryFactory, ValueComparisonService } from '@yourcause/common'; import { I18nService } from '@yourcause/common/i18n'; import { ModalFactory } from '@yourcause/common/modals'; import { NotifierService } from '@yourcause/common/notifier'; import moment from 'moment'; import { ApplicationApplicantService } from '../application-applicant.service'; import { CopyApplicationModalComponent } from '../copy-application-modal/copy-application-modal.component'; @Component({ selector: 'gc-public-program-page', templateUrl: './public-program-page.component.html', styleUrls: ['./public-program-page.component.scss'] }) export class PublicProgramPageComponent implements OnInit, OnDestroy { programGuid: string; FormTypes = FormTypes; program: ProgramDetail; programValid: boolean; programNotStarted: boolean; now = moment(); continueText = this.i18n.translate( 'APPLY:textStartNewApplication', {}, 'Start new application' ); loaded = false; repository: AutoTableRepository; isNomination = false; translationMap: ProgramTranslationMap; programTimeZone: TimeZone; programAcceptanceText: string; programDatesText: string; canApply: boolean; programIsAcceptingText = this.i18n.translate( 'APPLY:textProgramIsAcceptingApplications', {}, 'Program is accepting applications' ); programIsAcceptingNominations = this.i18n.translate( 'APPLY:textProgramIsAcceptingNominations', {}, 'Program is accepting nominations' ); programIsNotAcceptingText = this.i18n.translate( 'APPLY:textProgramIsNotAccepting', {}, 'Program is not accepting applications' ); programIsNotAcceptingNominations = this.i18n.translate( 'APPLY:textProgramIsNotAcceptingNominations', {}, 'Program is not accepting nominations' ); invitationRequiredText: string; inviteHelpers: InvitationApplicantHelpers; programAcceptingNoDates: string; programNotAcceptingNoDates: string; alreadyAppliedText: string; onCopyStep = false; programName: string; inviteId: number; constructor ( private mixpanel: MixpanelService, private activatedRoute: ActivatedRoute, private router: Router, private programService: ProgramService, private spinnerService: SpinnerService, private i18n: I18nService, private applicationApplicantService: ApplicationApplicantService, private autoTableFactory: AutoTableRepositoryFactory, private notifierService: NotifierService, private valueComparisonService: ValueComparisonService, private translationService: TranslationService, private timeZoneService: TimeZoneService, private cyclesService: CyclesService, private modalFactory: ModalFactory, private invitationService: InvitationService, private applicantService: ApplicantService ) { } get hasDraftApplication () { if (this.repository && this.repository.currentSet) { return this.repository.currentSet.length > 0; } return false; } async ngOnInit () { const inviteId = this.activatedRoute.snapshot.queryParamMap.get('invite'); this.inviteId = inviteId ? +inviteId : null; this.programGuid = this.activatedRoute.snapshot.params.programId; this.spinnerService.startSpinner(); const [ program, inviteHelpers, invitation ] = await Promise.all([ this.programService.getAndAdaptProgramForApplicant( this.programGuid ), this.programService.canApplicantApplyToProgram( this.programGuid ), this.invitationService.getInvitationDetail(this.inviteId) ]); const affiliateInfo = await this.applicantService.getClientAffiliateInfoWithFriendlyNames( program.clientName ); this.mixpanel.register({ ...affiliateInfo, 'Client Name': program.clientName, 'Program Name': program.grantProgramName }); this.program = program; await this.setClientBranding(); this.programTimeZone = this.timeZoneService.returnTimeZoneFromID( this.program.timezoneId ); this.inviteHelpers = inviteHelpers; this.translationMap = this.translationService.getProgramTranslationMapForApplicant( this.program ); this.programName = this.translationMap.name; this.setNominationAttrs(); this.setRepository(); this.checkProgramCycles(this.program.cycles); this.handleInvitation(invitation); this.loaded = true; this.spinnerService.stopSpinner(); } async setClientBranding () { await this.applicationApplicantService.getClientBranding(this.program.clientId); } handleInvitation (invitation: InvitationDetailResponse) { if (invitation?.response === InvitationResponse.Invitation_Used) { const foundApp = this.applicationApplicantService.myApplications.find((app) => { return app.invitationId === this.inviteId; }); if (foundApp) { const defaultFormId = this.program.form?.formId; const requestForm = foundApp.applicationForms.find((form) => { return form.formId === defaultFormId; }); this.router.navigate([ `/apply/application/${foundApp.applicationId}/forms/${requestForm.formId}` ]); } } else if ( this.programValid && invitation?.response === InvitationResponse.Pass && invitation?.detail.organizationId ) { // If coming from invitation where org is defined, // we navigate to Apply page where app will be created with the selected org this.navigateToApplyNew(invitation.detail.organizationId); } } setNominationAttrs () { this.isNomination = this.program.form.formType === FormTypes.NOMINATION; this.programAcceptingNoDates = this.isNomination ? this.programIsAcceptingNominations : this.programIsAcceptingText; this.programNotAcceptingNoDates = this.isNomination ? this.programIsNotAcceptingNominations : this.programIsNotAcceptingText; if (this.isNomination) { this.continueText = this.i18n.translate( 'APPLY:textStartNewNomination', {}, 'Start new nomination' ); } } checkProgramCycles (cycles: CyclesAPI.BaseProgramCycle[]) { const cyclesForDisplay = this.cyclesService.getCycleDateHelpers(cycles); this.setTextForAcceptingApplications(cyclesForDisplay); } setTextForAcceptingApplications (payload: CyclesAPI.CycleHelpers) { this.canApply = !!payload.currentCycle && this.inviteHelpers.canApply; if (this.inviteHelpers.alreadyUsedInvite && !this.inviteHelpers.canApply) { this.alreadyAppliedText = this.i18n.translate( 'APPLY:textYouHaveMetMaxiumumNumberOfAppsForProgram', {}, 'You have met the maximum number of applications for this program' ); } else if ( !this.inviteHelpers.canApply && !this.inviteHelpers.alreadyUsedInvite && this.program.inviteOnly ) { if (!this.inviteHelpers.grantProgramCycleIdOfNextInvitation) { this.invitationRequiredText = this.i18n.translate( 'APPLY:textItLooksLikeYouWereNotInvited', {}, 'It looks like you were not invited to apply for this program.' ); } } if (this.program.hideCycleDatesInApplicantPortal) { this.programAcceptanceText = this.canApply ? this.programAcceptingNoDates : this.programNotAcceptingNoDates; } else { if (this.canApply) { this.programAcceptanceText = this.i18n.translate( this.isNomination ? 'APPLY:textAcceptingNominationsUntilDate' : 'APPLY:textAcceptingApplicationsUntilDate', { date: this.getDateForDisplay(payload.currentCycle.endDate) }, this.isNomination ? 'Accepting nominations until __date__' : 'Accepting applications until __date__' ); } else { this.programAcceptanceText = this.isNomination ? this.programIsNotAcceptingNominations : this.programIsNotAcceptingText; const nextCycleId = payload.nextCycle && +payload.nextCycle.id; const lastCycleId = payload.lastCycle && +payload.lastCycle.id; const canApplyNextCycle = this.canApplyToCycle(nextCycleId); const canApplyLastCycle = this.canApplyToCycle(lastCycleId); if (payload.nextCycle && canApplyNextCycle) { this.programDatesText = this.i18n.translate( this.isNomination ? 'APPLY:textAcceptingNominationsInFuture' : 'APPLY:textAcceptingApplicationsInFuture', { startDate: this.getDateForDisplay(payload.nextCycle.startDate), endDate: this.getDateForDisplay(payload.nextCycle.endDate) }, this.isNomination ? 'Accepting nominations from __startDate__ until __endDate__' : 'Accepting applications from __startDate__ until __endDate__' ); } else if (payload.lastCycle && canApplyLastCycle) { this.programDatesText = this.i18n.translate( 'APPLY:textProgramClosedOnDate', { endDate: this.getDateForDisplay(payload.lastCycle.endDate) }, 'Program closed on __endDate__' ); } } } this.programValid = this.program.active && this.canApply; } canApplyToCycle (cycleId: number) { if (this.program.inviteOnly) { return !this.inviteHelpers.alreadyUsedInvite && (!this.inviteHelpers.grantProgramCycleIdOfNextInvitation || (+this.inviteHelpers.grantProgramCycleIdOfNextInvitation === cycleId)); } return true; } getDateForDisplay (date: string) { return this.timeZoneService.returnDateTimeAndTZ(moment(date).format('LLL'), 'lll'); } setRepository () { this.repository = this.autoTableFactory.create({ key: 'PROGRAM_APPLICATIONS', columns: [], notifier: this.notifierService, rowsPerPage: 1000, valueComparisonService: this.valueComparisonService, rows: this.applicationApplicantService.getDraftAppsForProgram( this.programGuid, this.program.cycles ) }); } onContinueToApp () { const hasApps = this.applicationApplicantService.checkForExistingSubmittedApps( this.program.grantProgramId ); if (hasApps) { this.handleCopyStep(); } else { this.navigateToApplyNew(); } } selectDraftApp (application: MyApplicationFromApi) { const draftForms = application.applicationForms .filter((form) => !!form.isDraft) .map((form) => form.formId); this.router.navigateByUrl( `/apply/application/${application.applicationId}/forms/${draftForms[0]}` ); } navigateToApplyNew (organizationId?: number) { let queryParams; if (organizationId) { queryParams = { organizationId }; } this.router.navigate( [`/apply/programs/${this.programGuid}/apply`], { queryParams } ); } navigateToCopyOf (copyOf: number) { this.router.navigate([ `/apply/programs/${this.programGuid}/apply` ], { queryParams: { copyOf } }); } async handleCopyStep () { this.onCopyStep = true; const response = await this.modalFactory.open( CopyApplicationModalComponent, { isNomination: this.isNomination, programId: this.program.grantProgramId, programName: this.programName } ); if (response) { if (response.goBack) { this.onCopyStep = false; } else if (response.startNew) { this.navigateToApplyNew(); } else { this.navigateToCopyOf(response.copyOfId); } } } ngOnDestroy () { this.mixpanel.unregister('Program Name'); this.mixpanel.unregister('Client Name'); this.applicationApplicantService.clearClientBranding(); } }