Remove enterprise angular code

This commit is contained in:
Oliver Günther
2025-06-11 16:20:43 +02:00
parent fcb8f8e3be
commit 299d611d27
25 changed files with 0 additions and 1611 deletions
-8
View File
@@ -178,11 +178,6 @@ import {
import {
WorkPackageOverviewGraphComponent,
} from 'core-app/shared/components/work-package-graphs/overview/wp-overview-graph.component';
import {
EEActiveSavedTrialComponent,
} from 'core-app/features/enterprise/enterprise-active-trial/ee-active-saved-trial.component';
import { FreeTrialButtonComponent } from 'core-app/features/enterprise/free-trial-button/free-trial-button.component';
import { EnterpriseBaseComponent } from 'core-app/features/enterprise/enterprise-base.component';
import { NoResultsComponent } from 'core-app/shared/components/no-results/no-results.component';
import {
OpNonWorkingDaysListComponent,
@@ -440,9 +435,6 @@ export class OpenProjectModule implements DoBootstrap {
registerCustomElement('opce-time-entry-trigger-actions', TriggerActionsEntryComponent, { injector });
registerCustomElement('opce-wp-overview-graph', WorkPackageOverviewGraphComponent, { injector });
registerCustomElement('opce-header-project-select', OpHeaderProjectSelectComponent, { injector });
registerCustomElement('opce-enterprise-active-saved-trial', EEActiveSavedTrialComponent, { injector });
registerCustomElement('opce-free-trial-button', FreeTrialButtonComponent, { injector });
registerCustomElement('opce-enterprise-base', EnterpriseBaseComponent, { injector });
registerCustomElement('opce-no-results', NoResultsComponent, { injector });
registerCustomElement('opce-non-working-days-list', OpNonWorkingDaysListComponent, { injector });
registerCustomElement('opce-main-menu-toggle', MainMenuToggleComponent, { injector });
@@ -1,78 +0,0 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See COPYRIGHT and LICENSE files for more details.
//++
import { ChangeDetectionStrategy, Component, ElementRef } from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { EEActiveTrialBase } from 'core-app/features/enterprise/enterprise-active-trial/ee-active-trial.base';
@Component({
selector: 'opce-enterprise-active-saved-trial',
templateUrl: './ee-active-trial.component.html',
styleUrls: ['./ee-active-trial.component.sass'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class EEActiveSavedTrialComponent extends EEActiveTrialBase {
public subscriber = this.elementRef.nativeElement.dataset.subscriber;
public email = this.elementRef.nativeElement.dataset.email;
public company = this.elementRef.nativeElement.dataset.company;
public domain = this.elementRef.nativeElement.dataset.domain;
public userCount = this.elementRef.nativeElement.dataset.userCount;
public startsAt = this.elementRef.nativeElement.dataset.startsAt;
public expiresAt = this.elementRef.nativeElement.dataset.expiresAt;
public isExpired:boolean = this.elementRef.nativeElement.dataset.isExpired === 'true';
public reprieveDaysLeft = this.elementRef.nativeElement.dataset.reprieveDaysLeft;
public plan:string = this.elementRef.nativeElement.dataset.plan;
public additionalFeatures:string = this.elementRef.nativeElement.dataset.additionalFeatures;
constructor(
readonly elementRef:ElementRef,
readonly I18n:I18nService,
) {
super(I18n);
}
public get expiredWarningText():string {
let warning = this.text.text_expired;
if (this.reprieveDaysLeft && this.reprieveDaysLeft > 0) {
warning = `${warning}: ${this.text.text_reprieve_days_left(this.reprieveDaysLeft)}`;
}
return warning;
}
}
@@ -1,50 +0,0 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See COPYRIGHT and LICENSE files for more details.
//++
import { UntilDestroyedMixin } from 'core-app/shared/helpers/angular/until-destroyed.mixin';
import { I18nService } from 'core-app/core/i18n/i18n.service';
export class EEActiveTrialBase extends UntilDestroyedMixin {
public text = {
label_email: this.I18n.t('js.label_email'),
label_expires_at: this.I18n.t('js.admin.enterprise.trial.form.label_expires_at'),
label_maximum_users: this.I18n.t('js.admin.enterprise.trial.form.label_maximum_users'),
label_company: this.I18n.t('js.admin.enterprise.trial.form.label_company'),
label_domain: this.I18n.t('js.admin.enterprise.trial.form.label_domain'),
label_starts_at: this.I18n.t('js.admin.enterprise.trial.form.label_starts_at'),
label_subscriber: this.I18n.t('js.admin.enterprise.trial.form.label_subscriber'),
label_plan: this.I18n.t('js.admin.enterprise.trial.form.label_plan'),
label_additional_features: this.I18n.t('js.admin.enterprise.trial.form.label_additional_features'),
text_expired: this.I18n.t('js.admin.enterprise.text_expired'),
text_reprieve_days_left: (days:number) => this.I18n.t('js.admin.enterprise.text_reprieve_days_left', { days }),
};
constructor(readonly I18n:I18nService) {
super();
}
}
@@ -1,85 +0,0 @@
<div data-test-selector="op-enterprise--active-token" class="op-enterprise--active-token">
<div class="attributes-group">
<div class="attributes-group--attributes">
<div class="attributes-key-value">
<div class="attributes-key-value--key" [textContent]="text.label_subscriber"></div>
<div class="attributes-key-value--value-container">
<div class="attributes-key-value--value -text">
<span [textContent]="subscriber"></span>
</div>
</div>
</div>
<div class="attributes-key-value">
<div class="attributes-key-value--key" [textContent]="text.label_email"></div>
<div
class="attributes-key-value--value-container"
data-test-selector="ee-active-trial-email"
>
<div class="attributes-key-value--value -text">
<span [textContent]="email"></span>
</div>
</div>
</div>
<div class="attributes-key-value" *ngIf="company">
<div class="attributes-key-value--key" [textContent]="text.label_company"></div>
<div class="attributes-key-value--value-container">
<div class="attributes-key-value--value -text">
<span [textContent]="company"></span>
</div>
</div>
</div>
<div class="attributes-key-value" *ngIf="domain">
<div class="attributes-key-value--key" [textContent]="text.label_domain"></div>
<div
class="attributes-key-value--value-container"
data-test-selector="ee-active-trial-domain"
>
<div class="attributes-key-value--value -text">
<span [textContent]="domain"></span>
</div>
</div>
</div>
<div class="attributes-key-value" *ngIf="userCount">
<div class="attributes-key-value--key" [textContent]="text.label_maximum_users"></div>
<div class="attributes-key-value--value-container">
<div class="attributes-key-value--value -text">
<span [textContent]="userCount"></span>
</div>
</div>
</div>
<div class="attributes-key-value" *ngIf="startsAt">
<div class="attributes-key-value--key" [textContent]="text.label_starts_at"></div>
<div class="attributes-key-value--value-container">
<div class="attributes-key-value--value -text">
<span [textContent]="startsAt"></span>
</div>
</div>
</div>
<div class="attributes-key-value" *ngIf="expiresAt">
<div class="attributes-key-value--key" [textContent]="text.label_expires_at"></div>
<div class="attributes-key-value--value-container">
<div class="attributes-key-value--value -text">
<span [textContent]="expiresAt"></span>
<span *ngIf="isExpired" [textContent]="expiredWarningText" class="expired-warning"></span>
</div>
</div>
</div>
<div class="attributes-key-value" *ngIf="plan">
<div class="attributes-key-value--key" [textContent]="text.label_plan"></div>
<div class="attributes-key-value--value-container">
<div class="attributes-key-value--value -text">
<span [textContent]="plan"></span>
</div>
</div>
</div>
<div class="attributes-key-value" *ngIf="additionalFeatures">
<div class="attributes-key-value--key" [textContent]="text.label_additional_features"></div>
<div class="attributes-key-value--value-container">
<div class="attributes-key-value--value -text">
<span [textContent]="additionalFeatures"></span>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -1,8 +0,0 @@
.op-enterprise--active-token
.attributes-group
margin-bottom: 1.25rem
.expired-warning
margin-left: 0.4rem
color: red
font-weight: var(--base-text-weight-bold)
@@ -1,92 +0,0 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See COPYRIGHT and LICENSE files for more details.
//++
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnInit } from '@angular/core';
import { distinctUntilChanged } from 'rxjs/operators';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { EnterpriseTrialService } from 'core-app/features/enterprise/enterprise-trial.service';
import { HttpClient } from '@angular/common/http';
import { EEActiveTrialBase } from 'core-app/features/enterprise/enterprise-active-trial/ee-active-trial.base';
import { IEnterpriseData } from 'core-app/features/enterprise/enterprise-trial.model';
@Component({
selector: 'enterprise-active-trial',
templateUrl: './ee-active-trial.component.html',
styleUrls: ['./ee-active-trial.component.sass'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class EEActiveTrialComponent extends EEActiveTrialBase implements OnInit {
public subscriber:string;
public email:string;
public company:string;
public domain:string;
public userCount:string;
public startsAt:string;
public expiresAt:string;
public plan:string;
public additionalFeatures:string;
constructor(
readonly elementRef:ElementRef,
readonly cdRef:ChangeDetectorRef,
readonly I18n:I18nService,
readonly http:HttpClient,
public eeTrialService:EnterpriseTrialService,
) {
super(I18n);
}
ngOnInit() {
if (!this.subscriber) {
void this.eeTrialService.userData$
.pipe(
distinctUntilChanged(),
this.untilDestroyed(),
)
.subscribe((data:IEnterpriseData) => {
this.formatUserData(data);
this.cdRef.detectChanges();
});
}
}
private formatUserData(userForm:IEnterpriseData) {
this.subscriber = `${userForm.first_name} ${userForm.last_name}`;
this.email = userForm.email;
this.company = userForm.company;
this.domain = userForm.domain;
}
}
@@ -1,23 +0,0 @@
<div class="op-enterprise-base">
<div *ngIf="noTrialRequested; else alreadyRequested">
<p>{{ text.enterprise_edition }}</p>
<p class="-bold">{{ text.confidence }}</p>
<p>
<span class="-bold">{{ text.become_hero }}</span><br>
<span>{{ text.you_contribute }}</span>
</p>
<button class="button -primary" (click)="openTrialModal()">
{{ text.button_trial }}
</button>
</div>
<ng-template #alreadyRequested>
<enterprise-trial-waiting
[trialKey]="trialKey"
[trialCreatedAt]="trialCreatedAt"
></enterprise-trial-waiting>
<p class="op-enterprise-base--confirmation-hint">{{ text.email_not_received }}
<a (click)="openTrialModal()">{{ text.try_another_email }}</a>
</p>
</ng-template>
</div>
@@ -1,8 +0,0 @@
.op-enterprise-base
.button
float: left
&--confirmation-hint
font-size: 0.9em
font-style: italic
margin-bottom: 2rem
@@ -1,89 +0,0 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See COPYRIGHT and LICENSE files for more details.
//++
import { ChangeDetectionStrategy, Component, ElementRef, Injector, Input, OnInit } from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { EnterpriseTrialModalComponent } from 'core-app/features/enterprise/enterprise-modal/enterprise-trial.modal';
import { OpModalService } from 'core-app/shared/components/modal/modal.service';
import { EnterpriseTrialService } from 'core-app/features/enterprise/enterprise-trial.service';
import { populateInputsFromDataset } from 'core-app/shared/components/dataset-inputs';
@Component({
// eslint-disable-next-line @angular-eslint/component-selector
selector: 'enterprise-base',
templateUrl: './enterprise-base.component.html',
styleUrls: ['./enterprise-base.component.sass'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class EnterpriseBaseComponent implements OnInit {
@Input() public trialKey:string|undefined;
@Input() public trialCreatedAt:string|undefined;
@Input() public augurUrl:string;
@Input() public tokenVersion:string;
public text = {
button_trial: this.I18n.t('js.admin.enterprise.upsell.button_start_trial'),
button_book: this.I18n.t('js.admin.enterprise.upsell.button_book_now'),
link_quote: this.I18n.t('js.admin.enterprise.upsell.link_quote'),
become_hero: this.I18n.t('js.admin.enterprise.upsell.become_hero'),
you_contribute: this.I18n.t('js.admin.enterprise.upsell.you_contribute'),
email_not_received: this.I18n.t('js.admin.enterprise.trial.email_not_received'),
enterprise_edition: this.I18n.t('js.admin.enterprise.upsell.text'),
confidence: this.I18n.t('js.admin.enterprise.upsell.confidence'),
try_another_email: this.I18n.t('js.admin.enterprise.trial.try_another_email'),
};
constructor(
readonly elementRef:ElementRef<HTMLElement>,
protected I18n:I18nService,
protected opModalService:OpModalService,
readonly injector:Injector,
public eeTrialService:EnterpriseTrialService,
) {
populateInputsFromDataset(this);
}
ngOnInit() {
this.eeTrialService.baseUrlAugur = this.augurUrl;
this.eeTrialService.tokenVersion = this.tokenVersion;
this.eeTrialService.setTrialKey(this.trialKey);
}
public openTrialModal() {
// cancel request and open first modal window
this.eeTrialService.store.update({ cancelled: true, modalOpen: true });
this.opModalService.show(EnterpriseTrialModalComponent, this.injector);
}
public get noTrialRequested() {
return !this.trialKey;
}
}
@@ -1,100 +0,0 @@
<form id="enterprise-trial-form" class="form" [formGroup]="trialForm">
<div class="form--field -wide-label -required">
<label class="form--label" for="trial-company-name">{{ text.label_company }}</label>
<div class="form--field-container">
<div class="form--text-field-container">
<input type="text"
id="trial-company-name"
class="form--text-field"
formControlName="company">
</div>
</div>
</div>
<div class="form--field -wide-label -required">
<label class="form--label" for="trial-first-name">{{ text.label_first_name }}</label>
<div class="form--field-container">
<div class="form--text-field-container">
<input type="text"
id="trial-first-name"
class="form--text-field"
formControlName="first_name">
</div>
</div>
</div>
<div class="form--field -wide-label -required">
<label class="form--label" for="trial-last-name">{{ text.label_last_name }}</label>
<div class="form--field-container">
<div class="form--text-field-container">
<input type="text"
id="trial-last-name"
class="form--text-field"
formControlName="last_name">
</div>
</div>
</div>
<div
*ngIf="{ emailError: (eeTrialService.emailError$ | async) } as field"
class="form--field -wide-label -required">
<label class="form--label" for="trial-email">{{ text.label_email }}</label>
<div class="form--field-container">
<div class="form--text-field-container"
[ngClass]="{ '-required-highlighting' : field.emailError }">
<input type="email"
class="form--text-field"
id="trial-email"
formControlName="email" (blur)="checkMailField()">
</div>
</div>
<div
*ngIf="field.emailError"
[textContent]="field.emailError"
class="form--field-instructions op-ee-trial-email-error"
></div>
</div>
<div
*ngIf="{ domainError: (eeTrialService.domainTaken$ | async) } as field"
class="form--field -wide-label -required">
<label class="form--label" for="trial-domain-name">{{ text.label_domain }}</label>
<div class="form--field-container">
<div class="form--text-field-container"
[ngClass]="{ '-required-highlighting' : field.domainError }">
<input type="text"
id="trial-domain-name"
class="form--text-field"
formControlName="domain"
disabled>
</div>
</div>
<div class="form--field-instructions">
<p *ngIf="requestHost !== configuredHost" [textContent]="text.domain_mismatch"></p>
<p *ngIf="field.domainError" [textContent]="field.domainError"></p>
</div>
</div>
<div class="form--field -required">
<div class="form--field-container">
<label class="form--label-with-check-box -no-ellipsis" for="trial-general-consent">
<div class="form--check-box-container">
<input type="checkbox"
id="trial-general-consent"
class="form--check-box"
formControlName="general_consent"
required>
</div>
<span [innerHTML]="text.general_consent"></span>
</label>
</div>
</div>
<div class="form--field">
<div class="form--field-container">
<label class="form--label-with-check-box -no-ellipsis" for="trial-newsletter-consent">
<div class="form--check-box-container">
<input type="checkbox"
id="trial-newsletter-consent"
class="form--check-box"
formControlName="newsletter_consent">
</div>
<span [innerHtml]="text.receive_newsletter"></span>
</label>
</div>
</div>
</form>
@@ -1,2 +0,0 @@
.op-ee-trial-email-error
width: min-content
@@ -1,117 +0,0 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See COPYRIGHT and LICENSE files for more details.
//++
import {
ChangeDetectionStrategy,
Component,
ElementRef,
} from '@angular/core';
import {
UntypedFormBuilder,
Validators,
} from '@angular/forms';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { EnterpriseTrialService } from 'core-app/features/enterprise/enterprise-trial.service';
import { CurrentUserService } from 'core-app/core/current-user/current-user.service';
import { localizeLink } from 'core-app/shared/helpers/i18n/localized-link';
import { ConfigurationService } from 'core-app/core/config/configuration.service';
import { IEnterpriseData } from 'core-app/features/enterprise/enterprise-trial.model';
const newsletterURL = 'https://www.openproject.org/newsletter/';
@Component({
selector: 'enterprise-trial-form',
templateUrl: './ee-trial-form.component.html',
styleUrls: ['./ee-trial-form.component.sass'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class EETrialFormComponent {
// Retain used values
userData:Partial<IEnterpriseData> = this.eeTrialService.current.data || {};
// The current request host
requestHost = window.location.host;
// The configured host name
configuredHost = this.configurationService.hostName;
trialForm = this.formBuilder.group({
company: [this.userData.company, Validators.required],
first_name: [this.userData.first_name, Validators.required],
last_name: [this.userData.last_name, Validators.required],
email: ['', [Validators.required, Validators.email]],
domain: [this.userData.domain || this.configuredHost, Validators.required],
general_consent: [null, Validators.required],
newsletter_consent: null,
language: this.currentUserService.language,
});
public text = {
general_consent: this.I18n.t('js.admin.enterprise.trial.form.general_consent', {
link_terms: localizeLink({
en: 'https://www.openproject.org/terms-of-service/',
de: 'https://www.openproject.org/de/nutzungsbedingungen/',
}),
link_privacy: localizeLink({
en: 'https://www.openproject.org/data-privacy-and-security/',
de: 'https://www.openproject.org/de/datenschutz/',
}),
}),
label_test_ee: this.I18n.t('js.admin.enterprise.trial.form.test_ee'),
label_company: this.I18n.t('js.admin.enterprise.trial.form.label_company'),
label_first_name: this.I18n.t('js.admin.enterprise.trial.form.label_first_name'),
label_last_name: this.I18n.t('js.admin.enterprise.trial.form.label_last_name'),
label_email: this.I18n.t('js.label_email'),
label_domain: this.I18n.t('js.admin.enterprise.trial.form.label_domain'),
domain_mismatch: this.I18n.t('js.admin.enterprise.trial.form.domain_mismatch'),
privacy_policy: this.I18n.t('js.admin.enterprise.trial.form.privacy_policy'),
receive_newsletter: this.I18n.t('js.admin.enterprise.trial.form.receive_newsletter', { link: newsletterURL }),
terms_of_service: this.I18n.t('js.admin.enterprise.trial.form.terms_of_service'),
};
constructor(
readonly elementRef:ElementRef,
readonly I18n:I18nService,
readonly formBuilder:UntypedFormBuilder,
readonly currentUserService:CurrentUserService,
readonly configurationService:ConfigurationService,
readonly eeTrialService:EnterpriseTrialService,
) {
}
// checks if mail is valid after input field was edited by the user
// displays message for user
public checkMailField():void {
const data = this.trialForm.value as IEnterpriseData;
if (data.email !== '' && this.trialForm.controls.email.errors) {
this.eeTrialService.store.update({ emailInvalid: true });
} else {
this.eeTrialService.store.update({ emailInvalid: false, error: undefined });
}
}
}
@@ -1,80 +0,0 @@
<div class="spot-modal op-enterprise-modal">
<div id="spotModalTitle" class="spot-modal--header" [textContent]="headerText$ | async"></div>
<div class="spot-divider"></div>
<div [ngSwitch]="showState$ | async" class="spot-modal--body spot-container">
<!-- first modal window -->
<div *ngSwitchCase="'trial-form'">
<enterprise-trial-form></enterprise-trial-form>
</div>
<!-- second modal window -->
<div *ngSwitchCase="'trial-waiting'">
<enterprise-trial-waiting></enterprise-trial-waiting>
</div>
<!-- third modal window -->
<div *ngSwitchCase="'overview'">
<div class="onboarding--video-block">
<div class="onboarding--video-text">
<span>{{ text.quick_overview }}</span>
</div>
<div class="onboarding--video iframe-target-wrapper">
<iframe frameborder="0"
height="400"
width="100%"
[src]="trustedEEVideoURL"
allowfullscreen>
</iframe>
</div>
</div>
</div>
</div>
<div
class="spot-action-bar"
*ngIf="{ status: eeTrialService.status$ | async, cancelled: eeTrialService.cancelled$ | async, confirmed: eeTrialService.confirmed$ | async } as context"
>
<div class="spot-action-bar--right">
<div *ngIf="!context.status || context.cancelled; else mailSubmitted"
class="spot-action-bar--right">
<button
class="button button_no-margin spot-action-bar--action spot-modal--cancel-button"
data-test-selector="confirmation-modal--cancel"
(click)="closeModal($event)"
[textContent]="text.button_cancel"
[attr.title]="text.button_cancel"
></button>
<button
class="button button_no-margin -primary spot-action-bar--action"
data-test-selector="confirmation-modal--confirmed"
(click)="onSubmit()"
[disabled]="!trialForm || trialForm.invalid"
[textContent]="text.button_submit"
[attr.title]="text.button_submit"
[hidden]="context.status === 'mailSubmitted' && !context.cancelled"
></button>
</div>
<ng-template #mailSubmitted>
<div class="spot-action-bar--right">
<button
class="button button_no-margin -primary spot-action-bar--action"
data-test-selector="confirmation-modal--confirmed"
(click)="startEnterpriseTrial()"
[textContent]="text.button_continue"
[attr.title]="text.button_continue"
[disabled]="!context.confirmed"
[hidden]="context.status === 'startTrial'"
></button>
<button
*ngIf="context.status === 'startTrial'"
class="button button_no-margin -primary spot-action-bar--action"
data-test-selector="confirmation-modal--confirmed"
(click)="closeModal($event)"
[textContent]="text.button_continue"
[attr.title]="text.button_continue"
></button>
</div>
</ng-template>
</div>
</div>
</div>
@@ -1,3 +0,0 @@
.op-enterprise-modal
.onboarding--video-text
margin-bottom: 1.25rem
@@ -1,156 +0,0 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See COPYRIGHT and LICENSE files for more details.
//++
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
Inject,
Input,
ViewChild,
} from '@angular/core';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
import { UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { OpModalComponent } from 'core-app/shared/components/modal/modal.component';
import { OpModalLocalsToken } from 'core-app/shared/components/modal/modal.service';
import { OpModalLocalsMap } from 'core-app/shared/components/modal/modal.types';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { EETrialFormComponent } from 'core-app/features/enterprise/enterprise-modal/enterprise-trial-form/ee-trial-form.component';
import { EnterpriseTrialService } from 'core-app/features/enterprise/enterprise-trial.service';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
export const eeOnboardingVideoURL = 'https://www.youtube.com/embed/zLMSydhFSkw?autoplay=1';
@Component({
selector: 'enterprise-trial-modal',
templateUrl: './enterprise-trial.modal.html',
styleUrls: ['./enterprise-trial.modal.sass'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class EnterpriseTrialModalComponent extends OpModalComponent implements AfterViewInit {
@ViewChild(EETrialFormComponent, { static: false }) formComponent:EETrialFormComponent;
@Input() public opReferrer:string;
public trialForm:UntypedFormGroup;
public trustedEEVideoURL:SafeResourceUrl;
public text = {
button_submit: this.I18n.t('js.modals.button_submit'),
button_cancel: this.I18n.t('js.modals.button_cancel'),
button_continue: this.I18n.t('js.button_continue'),
close_popup: this.I18n.t('js.close_popup_title'),
heading_confirmation: this.I18n.t('js.admin.enterprise.trial.confirmation'),
heading_next_steps: this.I18n.t('js.admin.enterprise.trial.next_steps'),
heading_test_ee: this.I18n.t('js.admin.enterprise.trial.test_ee'),
quick_overview: this.I18n.t('js.admin.enterprise.trial.quick_overview'),
};
headerText$:Observable<string> = this
.eeTrialService
.observe$
.pipe(
map(({ status }) => {
if (status === 'mailSubmitted') {
return this.text.heading_confirmation;
}
if (status === 'startTrial') {
return this.text.heading_next_steps;
}
return this.text.heading_test_ee;
}),
);
showState$ = this
.eeTrialService
.observe$
.pipe(
map(({ status, cancelled }) => {
if (!status || cancelled) {
return 'trial-form';
}
if (status === 'mailSubmitted' && !cancelled) {
return 'trial-waiting';
}
return 'overview';
}),
);
constructor(readonly elementRef:ElementRef,
@Inject(OpModalLocalsToken) public locals:OpModalLocalsMap,
readonly cdRef:ChangeDetectorRef,
readonly I18n:I18nService,
readonly domSanitizer:DomSanitizer,
public eeTrialService:EnterpriseTrialService) {
super(locals, cdRef, elementRef);
this.trustedEEVideoURL = this.trustedURL(eeOnboardingVideoURL);
}
ngAfterViewInit():void {
this.trialForm = this.formComponent.trialForm;
}
// checks if form is valid and submits it
public onSubmit():void {
if (this.trialForm.valid) {
this.trialForm.addControl('_type', new UntypedFormControl('enterprise-trial'));
void this.eeTrialService.sendForm(this.trialForm);
}
}
public startEnterpriseTrial():void {
// open onboarding modal screen
this.eeTrialService.setStartTrialStatus();
}
public closeModal(event:Event):void {
this.closeMe(event);
const { status, confirmed } = this.eeTrialService.current;
// refresh page to show enterprise trial
if (status === 'startTrial' || confirmed) {
window.location.reload();
} else if (status === 'mailSubmitted') {
window.location.reload();
return;
}
this.eeTrialService.store.update({ modalOpen: false });
}
public trustedURL(url:string):SafeResourceUrl {
return this.domSanitizer.bypassSecurityTrustResourceUrl(url);
}
}
@@ -1,32 +0,0 @@
<div
class="op-ee-trial-waiting"
*ngIf="{ confirmed: (eeTrialService.confirmed$ | async), cancelled: (eeTrialService.cancelled$ | async) } as context"
>
<enterprise-active-trial></enterprise-active-trial>
<p>{{ text.confirmation_info(created, email) }}</p>
<p>
<span>{{ text.status_label }} </span>
<span *ngIf="!context.confirmed; else confirmedStatus" class="op-ee-trial-waiting-status--waiting"
data-test-selector="op-ee-trial-waiting-status--waiting">
{{ text.status_waiting }}
<!-- <a id="op-ee-trial-waiting-resend-link"
data-test-selector='op-ee-trial-waiting-resend-link' (click)="resendMail()">{{ text.resend }}</a> -->
<button
*ngIf="resendLink$ | async"
class="spot-link op-ee-trial-waiting-resend-link"
data-test-selector='op-ee-trial-waiting-resend-link'
(click)="resendMail()">
{{ text.resend }}</button>
</span>
<span *ngIf="context.cancelled">{{ text.session_timeout }}</span>
<ng-template #confirmedStatus>
<span
class="op-ee-trial-waiting-status--confirmed icon-yes"
data-test-selector="op-ee-trial-waiting-status--confirmed"> {{ text.status_confirmed }}
</span>
</ng-template>
</p>
</div>
@@ -1,6 +0,0 @@
.op-ee-trial-waiting
&-status--confirmed
color: var(--button--primary-background-color)
&-status--waiting
color: orange
@@ -1,130 +0,0 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See COPYRIGHT and LICENSE files for more details.
//++
import {
Component,
ElementRef, Input,
OnInit,
} from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { EnterpriseTrialService } from 'core-app/features/enterprise/enterprise-trial.service';
import { HttpClient } from '@angular/common/http';
import { ToastService } from 'core-app/shared/components/toaster/toast.service';
import { distinctUntilChanged } from 'rxjs/operators';
import { TimezoneService } from 'core-app/core/datetime/timezone.service';
import { EXTERNAL_REQUEST_HEADER } from 'core-app/features/hal/http/openproject-header-interceptor';
import { IEnterpriseData } from 'core-app/features/enterprise/enterprise-trial.model';
@Component({
selector: 'enterprise-trial-waiting',
templateUrl: './ee-trial-waiting.component.html',
styleUrls: ['./ee-trial-waiting.component.sass'],
})
export class EETrialWaitingComponent implements OnInit {
@Input() public trialKey:string|undefined;
@Input() public trialCreatedAt:string|undefined;
created = this.timezoneService.formattedDate(new Date().toString());
resendLink$ = this.eeTrialService.resendLink$;
email = '';
public text = {
confirmation_info: (date:string, email:string) => this.I18n.t('js.admin.enterprise.trial.confirmation_info', {
date,
email,
}),
resend: this.I18n.t('js.admin.enterprise.trial.resend_link'),
resend_success: this.I18n.t('js.admin.enterprise.trial.resend_success'),
resend_warning: this.I18n.t('js.admin.enterprise.trial.resend_warning'),
session_timeout: this.I18n.t('js.admin.enterprise.trial.session_timeout'),
status_confirmed: this.I18n.t('js.admin.enterprise.trial.status_confirmed'),
status_label: this.I18n.t('js.admin.enterprise.trial.status_label'),
status_waiting: this.I18n.t('js.admin.enterprise.trial.status_waiting'),
};
constructor(
readonly elementRef:ElementRef,
readonly I18n:I18nService,
protected http:HttpClient,
protected toastService:ToastService,
public eeTrialService:EnterpriseTrialService,
readonly timezoneService:TimezoneService) {
}
ngOnInit():void {
if (this.trialCreatedAt) {
this.created = this.timezoneService.formattedDate(this.trialCreatedAt);
}
this.eeTrialService
.userData$
.pipe(
distinctUntilChanged(),
)
.subscribe((data:IEnterpriseData) => {
this.email = data.email;
});
}
// resend mail if resend link has been clicked
public resendMail():void {
const { resendLink } = this.eeTrialService.store.getValue();
if (!resendLink) {
return;
}
this.eeTrialService.store.update({ cancelled: false });
this.http.post(
resendLink,
{},
{
headers: {
[EXTERNAL_REQUEST_HEADER]: 'true',
},
},
)
.toPromise()
.then(() => {
this.toastService.addSuccess(this.text.resend_success);
this.eeTrialService.retryConfirmation();
})
.catch(() => {
const { trialLink } = this.eeTrialService.store.getValue();
if (trialLink) {
// Check whether the mail has been confirmed by now
this.eeTrialService.getToken();
} else {
this.toastService.addError(this.text.resend_warning);
}
});
}
}
@@ -1,43 +0,0 @@
import { HttpErrorResponse } from '@angular/common/http';
import {
IHalOptionalTitledLink,
IHalResourceLinks,
} from 'core-app/core/state/hal-resource';
export interface IEnterpriseData {
id?:string;
company:string;
first_name:string;
last_name:string;
email:string;
domain:string;
general_consent?:boolean;
newsletter_consent?:boolean;
}
export interface IEnterpriseTrial {
trialLink?:string;
resendLink?:string;
modalOpen:boolean;
confirmed:boolean;
cancelled:boolean;
status?:'mailSubmitted'|'startTrial';
error?:HttpErrorResponse;
emailInvalid:boolean;
data?:IEnterpriseData;
}
export interface EnterpriseTrialHalResource {
_links:IHalResourceLinks;
}
export interface EnterpriseTrialErrorHalResource {
identifier:string;
message?:string;
description?:string;
_links:{
self:IHalOptionalTitledLink;
resend:IHalOptionalTitledLink;
};
}
@@ -1,320 +0,0 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See COPYRIGHT and LICENSE files for more details.
//++
import { Injectable } from '@angular/core';
import { UntypedFormGroup } from '@angular/forms';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Query } from '@datorama/akita';
import { filter, map, shareReplay } from 'rxjs/operators';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
import { ToastService } from 'core-app/shared/components/toaster/toast.service';
import { EXTERNAL_REQUEST_HEADER } from 'core-app/features/hal/http/openproject-header-interceptor';
import { EnterpriseTrialStore } from 'core-app/features/enterprise/enterprise-trial.store';
import {
EnterpriseTrialErrorHalResource,
EnterpriseTrialHalResource,
IEnterpriseData,
IEnterpriseTrial,
} from 'core-app/features/enterprise/enterprise-trial.model';
import isDefinedEntity from 'core-app/core/state/is-defined-entity';
@Injectable()
export class EnterpriseTrialService {
readonly store = new EnterpriseTrialStore();
private query = new Query(this.store);
resendLink$ = this.query.select('resendLink');
confirmed$ = this.query.select('confirmed');
cancelled$ = this.query.select('cancelled');
status$ = this.query.select('status');
observe$ = this.query.select();
userData$ = this.query
.select('data')
.pipe(filter(isDefinedEntity));
emailError$ = this
.query
.select()
.pipe(
map(({ error, emailInvalid }) => {
if (emailInvalid) {
return this.text.invalid_email;
}
const errorResponse = error?.error as { identifier?:string };
if (error && errorResponse.identifier === 'user_already_created_trial') {
return this.text.taken_email;
}
return null;
}),
shareReplay(1),
);
domainTaken$ = this
.query
.select()
.pipe(
map(({ error }) => {
const errorResponse = error?.error as { identifier?:string };
if (error && errorResponse.identifier === 'domain_taken') {
return this.text.taken_domain;
}
return null;
}),
shareReplay(1),
);
public baseUrlAugur:string;
public tokenVersion:string;
public trialKey:string|undefined;
public text = {
invalid_email: this.I18n.t('js.admin.enterprise.trial.form.invalid_email'),
taken_email: this.I18n.t('js.admin.enterprise.trial.form.taken_email'),
taken_domain: this.I18n.t('js.admin.enterprise.trial.form.taken_domain'),
};
constructor(
readonly I18n:I18nService,
protected http:HttpClient,
readonly pathHelper:PathHelperService,
protected toastService:ToastService,
) {
}
public setTrialKey(trialKey:string|undefined):void {
this.trialKey = trialKey;
if (this.trialKey) {
const trialLink = `${this.baseUrlAugur}/public/v1/trials/${this.trialKey}`;
this.store.update({ trialLink });
this.getUserDataFromAugur(trialLink);
this.setMailSubmittedStatus();
}
}
// send POST request with form object
// receive an enterprise trial link to access a token
public sendForm(form:UntypedFormGroup):Promise<unknown> {
const request:unknown = { ...form.value, token_version: this.tokenVersion };
return this.http
.post(
`${this.baseUrlAugur}/public/v1/trials`,
request,
{
headers: {
[EXTERNAL_REQUEST_HEADER]: 'true',
},
},
)
.toPromise()
.then((enterpriseTrial:EnterpriseTrialHalResource) => {
const trialLink = enterpriseTrial._links.self.href;
const data = form.value as IEnterpriseData;
this.store.update({
trialLink,
data,
cancelled: false,
});
void this.saveTrialKey(trialLink);
this.retryConfirmation();
})
.catch((error:HttpErrorResponse) => {
// mail is invalid or user already created a trial
if (error.status === 422 || error.status === 400) {
this.store.update({ error });
} else {
const description = (error.error as { description?:string }).description;
this.toastService.addWarning(description || I18n.t('js.error.internal'));
}
});
}
// use the trial key saved in the db
// to get the user data from Augur
public getUserDataFromAugur(trialLink:string) {
this
.http
.get(
`${trialLink}/details`,
{
headers: {
[EXTERNAL_REQUEST_HEADER]: 'true',
},
},
)
.toPromise()
.then((data:IEnterpriseData) => {
this.store.update({ data });
this.retryConfirmation();
})
.catch(() => {
// Check whether the mail has been confirmed by now
this.getToken();
});
}
// get a token from the trial link if user confirmed mail
public getToken():void {
// 2) GET /public/v1/trials/:id
this.http.get(
this.store.getValue().trialLink as string,
{
headers: {
[EXTERNAL_REQUEST_HEADER]: 'true',
},
},
)
.toPromise()
.then(async (res:{ token_retrieved?:boolean, token:string }) => {
// show confirmed status and enable continue btn
this.store.update({ confirmed: true });
// returns token if mail was confirmed
// -> if token is new (token_retrieved: false) save token in backend
if (!res.token_retrieved) {
await this.saveToken(res.token);
}
})
.catch((error:HttpErrorResponse) => {
const errorResponse = error.error as EnterpriseTrialErrorHalResource;
// returns error 422 while waiting of confirmation
if (error.status === 422 && errorResponse.identifier === 'waiting_for_email_verification') {
// get resend button link
const resendLink = errorResponse._links.resend.href;
this.store.update({ resendLink });
const { status, cancelled } = this.store.getValue();
// save a key for the requested trial
if (!status || cancelled) { // only do it once
void this.saveTrialKey(resendLink);
}
// open next modal window -> status waiting
this.setMailSubmittedStatus();
this.store.update({ confirmed: false });
} else if (errorResponse?.message) {
this.toastService.addError(errorResponse.message);
} else {
this.toastService.addError(String(error.error) || I18n.t('js.error.internal'));
}
});
}
// save a part of the resend link in db
// which allows to remember if a user has already requested a trial token
// and to ask for the corresponding user data saved in Augur
private saveTrialKey(resendlink:string):Promise<unknown> {
// extract token from resend link
const trialKey = resendlink.split('/')[6];
return this.http.post(
`${this.pathHelper.appBasePath}/admin/enterprise_tokens/save_trial_key`,
{ trial_key: trialKey },
{ withCredentials: true },
)
.toPromise()
.catch((e:HttpErrorResponse) => {
const errorResponse = e.error as EnterpriseTrialErrorHalResource;
this.toastService.addError(errorResponse.message || e.message || e);
});
}
// save received token in controller
private saveToken(token:string) {
return this.http.post(
`${this.pathHelper.appBasePath}/admin/enterprise_tokens`,
{ enterprise_token: { encoded_token: token } },
{ withCredentials: true },
)
.toPromise()
.then(() => {
const { modalOpen } = this.store.getValue();
// load page if mail was confirmed and modal window is not open
if (!modalOpen) {
setTimeout(() => { // display confirmed status before reloading
window.location.reload();
}, 500);
}
})
.catch((error:HttpErrorResponse) => {
// Delete the trial key as the token could not be saved and thus something is wrong with the token.
// Without this deletion, we run into an endless loop of an confirmed mail, but no saved token.
void this.http
.delete(
`${this.pathHelper.api.v3.apiV3Base}/admin/enterprise_tokens/delete_trial_key`,
{ withCredentials: true },
)
.toPromise();
const errorResponse = error.error as EnterpriseTrialErrorHalResource;
this.toastService.addError(errorResponse.description || I18n.t('js.error.internal'));
});
}
// retry request while waiting for mail confirmation
public retryConfirmation(delay = 5000, retries = 60):void {
const { cancelled, confirmed } = this.store.getValue();
if (cancelled || confirmed) {
// no need to retry
return;
}
if (retries === 0) {
this.store.update({ cancelled: true });
} else {
this.getToken();
setTimeout(() => {
this.retryConfirmation(delay, retries - 1);
}, delay);
}
}
public setStartTrialStatus():void {
this.store.update({ status: 'startTrial' });
}
public setMailSubmittedStatus():void {
this.store.update({ status: 'mailSubmitted' });
}
public get current():IEnterpriseTrial {
return this.store.getValue();
}
}
@@ -1,21 +0,0 @@
import {
Store,
StoreConfig,
} from '@datorama/akita';
import { IEnterpriseTrial } from 'core-app/features/enterprise/enterprise-trial.model';
export function createInitialState():IEnterpriseTrial {
return {
modalOpen: false,
confirmed: false,
cancelled: false,
emailInvalid: false,
};
}
@StoreConfig({ name: 'enterprise-trial' })
export class EnterpriseTrialStore extends Store<IEnterpriseTrial> {
constructor() {
super(createInitialState());
}
}
@@ -1,12 +0,0 @@
<div
[attr.data-tooltip]="trialRequested ? text.confirmation_info(created, email) : ''"
[ngClass]="{'tooltip--right': trialRequested}">
<button
type="button"
[disabled]="trialRequested"
class="Button--secondary Button--medium Button"
(click)="openTrialModal()"
>
{{ text.button_trial }}
</button>
</div>
@@ -1,122 +0,0 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See COPYRIGHT and LICENSE files for more details.
//++
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
Injector,
Input,
OnInit,
} from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { EnterpriseTrialModalComponent } from 'core-app/features/enterprise/enterprise-modal/enterprise-trial.modal';
import { OpModalService } from 'core-app/shared/components/modal/modal.service';
import { EnterpriseTrialService } from 'core-app/features/enterprise/enterprise-trial.service';
import { TimezoneService } from 'core-app/core/datetime/timezone.service';
import { distinctUntilChanged } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http';
import { GonService } from 'core-app/core/gon/gon.service';
import { IEnterpriseData } from 'core-app/features/enterprise/enterprise-trial.model';
import { populateInputsFromDataset } from 'core-app/shared/components/dataset-inputs';
@Component({
selector: 'opce-free-trial-button',
templateUrl: './free-trial-button.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FreeTrialButtonComponent implements OnInit {
@Input() public trialKey:string|undefined;
@Input() public trialCreatedAt:string|undefined;
@Input() public augurUrl:string;
@Input() public tokenVersion:string;
created = this.timezoneService.formattedDate(new Date().toString());
email = '';
public text = {
button_trial: this.I18n.t('js.admin.enterprise.upsell.button_start_trial'),
confirmation_info: (date:string, email:string):string => this.I18n.t('js.admin.enterprise.trial.confirmation_info', {
date,
email,
}),
};
constructor(
readonly elementRef:ElementRef<HTMLElement>,
protected I18n:I18nService,
protected opModalService:OpModalService,
readonly injector:Injector,
readonly http:HttpClient,
readonly cdRef:ChangeDetectorRef,
readonly Gon:GonService,
public eeTrialService:EnterpriseTrialService,
readonly timezoneService:TimezoneService,
) {
populateInputsFromDataset(this);
}
ngOnInit():void {
this.eeTrialService.baseUrlAugur = this.augurUrl;
this.eeTrialService.tokenVersion = this.tokenVersion;
this.eeTrialService.setTrialKey(this.trialKey);
this.eeTrialService
.userData$
.pipe(
distinctUntilChanged(),
)
.subscribe((userForm:IEnterpriseData) => {
this.email = userForm.email;
this.cdRef.detectChanges();
});
this.initialize();
}
private initialize():void {
if (this.trialCreatedAt) {
this.created = this.timezoneService.formattedDate(this.trialCreatedAt);
}
}
public openTrialModal():void {
// cancel request and open first modal window
this.eeTrialService.store.update({ cancelled: true, modalOpen: true });
this.opModalService.show(EnterpriseTrialModalComponent, this.injector);
}
public get trialRequested():boolean {
return !!this.trialKey;
}
}
@@ -29,36 +29,17 @@
import { NgModule } from '@angular/core';
import { OpSharedModule } from 'core-app/shared/shared.module';
import { OpenprojectModalModule } from 'core-app/shared/components/modal/modal.module';
import { EnterpriseTrialService } from 'core-app/features/enterprise/enterprise-trial.service';
import { EnterpriseBaseComponent } from 'core-app/features/enterprise/enterprise-base.component';
import { EnterpriseTrialModalComponent } from 'core-app/features/enterprise/enterprise-modal/enterprise-trial.modal';
import { EETrialFormComponent } from 'core-app/features/enterprise/enterprise-modal/enterprise-trial-form/ee-trial-form.component';
import { EETrialWaitingComponent } from 'core-app/features/enterprise/enterprise-trial-waiting/ee-trial-waiting.component';
import { EEActiveTrialComponent } from 'core-app/features/enterprise/enterprise-active-trial/ee-active-trial.component';
import { EEActiveSavedTrialComponent } from 'core-app/features/enterprise/enterprise-active-trial/ee-active-saved-trial.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { EnterpriseBannerFrameComponent } from 'core-app/features/enterprise/enterprise-banner-frame.component';
@NgModule({
imports: [
OpSharedModule,
OpenprojectModalModule,
FormsModule,
ReactiveFormsModule,
],
providers: [
EnterpriseTrialService,
],
exports: [
EnterpriseBannerFrameComponent,
],
declarations: [
EnterpriseBaseComponent,
EnterpriseTrialModalComponent,
EETrialFormComponent,
EETrialWaitingComponent,
EEActiveTrialComponent,
EEActiveSavedTrialComponent,
EnterpriseBannerFrameComponent,
],
})
-7
View File
@@ -57,7 +57,6 @@ import {
import { OPContextMenuComponent } from 'core-app/shared/components/op-context-menu/op-context-menu.component';
import { OpenprojectPrincipalRenderingModule } from 'core-app/shared/components/principal/principal-rendering.module';
import { FocusModule } from 'core-app/shared/directives/focus/focus.module';
import { FreeTrialButtonComponent } from 'core-app/features/enterprise/free-trial-button/free-trial-button.component';
import { HomescreenNewFeaturesBlockComponent } from 'core-app/features/homescreen/blocks/new-features.component';
import { TablePaginationComponent } from 'core-app/shared/components/table-pagination/table-pagination.component';
import { StaticQueriesService } from 'core-app/shared/components/op-view-select/op-static-queries.service';
@@ -170,9 +169,6 @@ export function bootstrapModule(injector:Injector):void {
EditableToolbarTitleComponent,
// Enterprise Edition
FreeTrialButtonComponent,
DynamicModule,
OpOptionListComponent,
@@ -211,9 +207,6 @@ export function bootstrapModule(injector:Injector):void {
PersistentToggleComponent,
RemoteFieldUpdaterComponent,
// Enterprise Edition
FreeTrialButtonComponent,
HomescreenNewFeaturesBlockComponent,
OpOptionListComponent,