Merge pull request #7995 from opf/feature/31935/ee-activation
[31935] WIP: Enterprise activation frontend [ci skip]pull/8264/head
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 143 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 24 KiB |
@ -0,0 +1,73 @@ |
|||||||
|
# Info boxes |
||||||
|
|
||||||
|
## Simple info boxes |
||||||
|
``` |
||||||
|
<div class="info-boxes"> |
||||||
|
<h2 class="info-boxes--title">Heading</h2> |
||||||
|
<div class="info-boxes--container"> |
||||||
|
<div class="info-boxes--item"> |
||||||
|
<h3 class="info-boxes--item-title">Box 1</h3> |
||||||
|
<div class="info-boxes--item-content"> |
||||||
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="info-boxes--item"> |
||||||
|
<h3 class="info-boxes--item-title">Box 2</h3> |
||||||
|
<div class="info-boxes--item-content"> |
||||||
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="info-boxes--item"> |
||||||
|
<h3 class="info-boxes--item-title">Box 3</h3> |
||||||
|
<div class="info-boxes--item-content"> |
||||||
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
``` |
||||||
|
|
||||||
|
## Centered with image and links |
||||||
|
``` |
||||||
|
<div class="info-boxes -centered"> |
||||||
|
<h2 class="info-boxes--title">Heading</h2> |
||||||
|
<div class="info-boxes--container"> |
||||||
|
<div class="info-boxes--item"> |
||||||
|
<img src="https://via.placeholder.com/250x200" class="info-boxes--teaser-image"> |
||||||
|
<h3 class="info-boxes--item-title">Box 1</h3> |
||||||
|
<div class="info-boxes--item-content"> |
||||||
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p> |
||||||
|
<ul class="widget-box--arrow-links"> |
||||||
|
<li> |
||||||
|
<a href="" target="_blank">Learn more</a> |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="info-boxes--item"> |
||||||
|
<img src="https://via.placeholder.com/250x200" class="info-boxes--teaser-image"> |
||||||
|
<h3 class="info-boxes--item-title">Box 2</h3> |
||||||
|
<div class="info-boxes--item-content"> |
||||||
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p> |
||||||
|
<ul class="widget-box--arrow-links"> |
||||||
|
<li> |
||||||
|
<a href="" target="_blank">Learn more</a> |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="info-boxes--item"> |
||||||
|
<img src="https://via.placeholder.com/250x200" class="info-boxes--teaser-image"> |
||||||
|
<h3 class="info-boxes--item-title">Box 3</h3> |
||||||
|
<div class="info-boxes--item-content"> |
||||||
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p> |
||||||
|
<ul class="widget-box--arrow-links"> |
||||||
|
<li> |
||||||
|
<a href="" target="_blank">Learn more</a> |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
``` |
@ -0,0 +1,62 @@ |
|||||||
|
//-- copyright |
||||||
|
// OpenProject is an open source project management software. |
||||||
|
// Copyright (C) 2012-2020 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-2017 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 docs/COPYRIGHT.rdoc for more details. |
||||||
|
//++ |
||||||
|
|
||||||
|
.info-boxes |
||||||
|
width: 100% |
||||||
|
max-width: 1140px |
||||||
|
|
||||||
|
.info-boxes--title, |
||||||
|
.info-boxes--item-title |
||||||
|
margin: 20px auto |
||||||
|
font-weight: bold |
||||||
|
|
||||||
|
&.-centered |
||||||
|
margin: auto |
||||||
|
padding: 0 15px |
||||||
|
.info-boxes--title |
||||||
|
text-align: center |
||||||
|
|
||||||
|
.info-boxes--container |
||||||
|
display: grid |
||||||
|
grid-gap: 20px |
||||||
|
grid-template-columns: repeat(auto-fit,minmax(200px,1fr)) |
||||||
|
|
||||||
|
.info-boxes--item |
||||||
|
.info-boxes--teaser-image |
||||||
|
display: block |
||||||
|
margin: auto |
||||||
|
max-width: 150px |
||||||
|
|
||||||
|
.info-boxes--item-title |
||||||
|
white-space: nowrap |
||||||
|
border-bottom: none |
||||||
|
|
||||||
|
.info-boxes--item-content |
||||||
|
.widget-box--arrow-links |
||||||
|
text-transform: uppercase |
||||||
|
font-size: .875rem |
@ -0,0 +1,21 @@ |
|||||||
|
module EnterpriseTrialHelper |
||||||
|
def augur_content_security_policy |
||||||
|
append_content_security_policy_directives( |
||||||
|
connect_src: [OpenProject::Configuration.enterprise_trial_creation_host] |
||||||
|
) |
||||||
|
end |
||||||
|
|
||||||
|
def chargebee_content_security_policy |
||||||
|
append_content_security_policy_directives( |
||||||
|
script_src: %w(js.chargebee.com), |
||||||
|
style_src: %w(js.chargebee.com openproject-enterprise-test.chargebee.com), |
||||||
|
frame_src: %w(js.chargebee.com openproject-enterprise-test.chargebee.com) |
||||||
|
) |
||||||
|
end |
||||||
|
|
||||||
|
def youtube_content_security_policy |
||||||
|
append_content_security_policy_directives( |
||||||
|
frame_src: %w(https://www.youtube.com) |
||||||
|
) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,41 @@ |
|||||||
|
#-- encoding: UTF-8 |
||||||
|
|
||||||
|
#-- copyright |
||||||
|
# OpenProject is an open source project management software. |
||||||
|
# Copyright (C) 2012-2020 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-2017 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 docs/COPYRIGHT.rdoc for more details. |
||||||
|
#++ |
||||||
|
|
||||||
|
require_dependency 'token/base' |
||||||
|
|
||||||
|
module Token |
||||||
|
class EnterpriseTrialKey < Base |
||||||
|
include ExpirableToken |
||||||
|
|
||||||
|
def self.validity_time |
||||||
|
1.days |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -1,28 +1,122 @@ |
|||||||
<div class="notification-box upsale-notification"> |
<%#-- copyright |
||||||
<div class="notification-box--content"> |
OpenProject is an open source project management software. |
||||||
<h3><%= t('admin.enterprise.upgrade_to_ee') %></h3> |
Copyright (C) 2012-2020 the OpenProject GmbH |
||||||
<%= image_tag "enterprise_edition.png", class: "widget-box--teaser-image" %> |
|
||||||
|
|
||||||
<p><%= t('homescreen.blocks.upsale.description') %></p> |
This program is free software; you can redistribute it and/or |
||||||
|
modify it under the terms of the GNU General Public License version 3. |
||||||
|
|
||||||
<ul class=""> |
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||||
|
Copyright (C) 2006-2017 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 docs/COPYRIGHT.rdoc for more details. |
||||||
|
|
||||||
|
++#%> |
||||||
|
<% content_for :header_tags do %> |
||||||
|
<script src="<%= OpenProject::Static::Links.links[:chargebee][:href] %>" |
||||||
|
data-cb-site="openproject-enterprise-test"> |
||||||
|
</script> |
||||||
|
<% end %> |
||||||
|
|
||||||
|
<enterprise-base></enterprise-base> |
||||||
|
|
||||||
|
<div class='upsale--actions'> |
||||||
|
<a href="#" |
||||||
|
class="button -highlight" |
||||||
|
data-cb-type="checkout" |
||||||
|
data-cb-plan-id="enterprise-edition---annual-user-license"> |
||||||
|
<%= t('admin.enterprise.book_now') %> |
||||||
|
</a> |
||||||
|
|
||||||
|
<% quote_link = OpenProject::Static::Links.links.fetch :upsale_get_quote %> |
||||||
|
<%= link_to t(quote_link[:label]), |
||||||
|
quote_link[:href], |
||||||
|
target: '_blank', |
||||||
|
class: 'button -highlight'%> |
||||||
|
|
||||||
|
<%= static_link_to :contact %> |
||||||
|
</div> |
||||||
|
|
||||||
|
<p> |
||||||
|
<b><%= t('js.admin.enterprise.upsale.confidence') %></b> |
||||||
|
</p> |
||||||
|
|
||||||
|
<div class="info-boxes upsale-benefits"> |
||||||
|
<h3 class="info-boxes--title -no-border"><%= t('js.admin.enterprise.upsale.benefits.description') %></h3> |
||||||
|
<div class="info-boxes--container"> |
||||||
|
<div class="info-boxes--item"> |
||||||
|
<%= image_tag "installation_alerts.svg", |
||||||
|
class: "info-boxes--teaser-image", |
||||||
|
title: t('js.admin.enterprise.upsale.benefits.installation'), |
||||||
|
alt: t('js.admin.enterprise.upsale.benefits.installation') %> |
||||||
|
<h4 class="info-boxes--item-title"><%= t('js.admin.enterprise.upsale.benefits.installation') %></h4> |
||||||
|
<div class="info-boxes--item-content"> |
||||||
|
<p><%= t('js.admin.enterprise.upsale.benefits.installation_text') %></p> |
||||||
|
<ul class="widget-box--arrow-links"> |
||||||
<li> |
<li> |
||||||
<%= t('homescreen.blocks.upsale.additional_features') %> |
<%= static_link_to :upsale_benefits_installation %> |
||||||
</li> |
</li> |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="info-boxes--item"> |
||||||
|
<%= image_tag "system_maintenance.jpg", |
||||||
|
class: "info-boxes--teaser-image", |
||||||
|
title: t('js.admin.enterprise.upsale.benefits.professional_support'), |
||||||
|
alt: t('js.admin.enterprise.upsale.benefits.professional_support') %> |
||||||
|
<h4 class="info-boxes--item-title"><%= t('js.admin.enterprise.upsale.benefits.professional_support') %></h4> |
||||||
|
<div class="info-boxes--item-content"> |
||||||
|
<p><%= t('js.admin.enterprise.upsale.benefits.professional_support_text') %></p> |
||||||
|
<ul class="widget-box--arrow-links"> |
||||||
<li> |
<li> |
||||||
<%= t('homescreen.blocks.upsale.professional_support') %> |
<%= static_link_to :upsale_benefits_support %> |
||||||
</li> |
</li> |
||||||
</ul> |
</ul> |
||||||
<p> |
</div> |
||||||
<b><%= t('homescreen.blocks.upsale.become_hero') %></b> <%= t('homescreen.blocks.upsale.you_contribute') %> |
</div> |
||||||
</p> |
<div class="info-boxes--item"> |
||||||
<%= link_to( "#{OpenProject::Static::Links.links[:upsale][:href]}/?utm_source=unknown&utm_medium=community-edition&utm_campaign=enterprise-admin", |
<%= image_tag "premium_features.svg", |
||||||
{ class: 'button -alt-highlight', |
class: "info-boxes--teaser-image", |
||||||
target: '_blank', |
title: t('js.admin.enterprise.upsale.benefits.premium_features'), |
||||||
aria: {label: t('admin.enterprise.order')}, |
alt: t('js.admin.enterprise.upsale.benefits.premium_features') %> |
||||||
title: t('admin.enterprise.order')}) do %> |
<h4 class="info-boxes--item-title"><%= t('js.admin.enterprise.upsale.benefits.premium_features') %></h4> |
||||||
<%= op_icon('button--icon icon-add') %> |
<div class="info-boxes--item-content"> |
||||||
<span class="button--text"><%= t('admin.enterprise.order') %></span> |
<p><%= t('js.admin.enterprise.upsale.benefits.premium_features_text') %></p> |
||||||
<% end %> |
<ul class="widget-box--arrow-links"> |
||||||
|
<li> |
||||||
|
<%= static_link_to :upsale_benefits_features %> |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="info-boxes--item"> |
||||||
|
<%= image_tag "security_alerts.jpg", |
||||||
|
class: "info-boxes--teaser-image", |
||||||
|
title: t('js.admin.enterprise.upsale.benefits.high_security'), |
||||||
|
alt: t('js.admin.enterprise.upsale.benefits.high_security') %> |
||||||
|
<h4 class="info-boxes--item-title"><%= t('js.admin.enterprise.upsale.benefits.high_security') %></h4> |
||||||
|
<div class="info-boxes--item-content"> |
||||||
|
<p><%= t('js.admin.enterprise.upsale.benefits.high_security_text') %></p> |
||||||
|
<ul class="widget-box--arrow-links"> |
||||||
|
<li> |
||||||
|
<%= static_link_to :upsale_benefits_security %> |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
</div> |
||||||
</div> |
</div> |
||||||
</div> |
</div> |
||||||
|
@ -1,28 +1,31 @@ |
|||||||
<%= render 'homescreen/blocks/header', title: t('homescreen.blocks.upsale.title') %> |
<%= render 'homescreen/blocks/header', title: t('homescreen.blocks.upsale.title') %> |
||||||
|
|
||||||
|
<div class="widget-box--description"> |
||||||
<%= image_tag "enterprise_edition.png", class: "widget-box--teaser-image" %> |
<%= image_tag "enterprise_edition.png", class: "widget-box--teaser-image" %> |
||||||
|
|
||||||
<p><%= t('homescreen.blocks.upsale.description') %></p> |
|
||||||
|
|
||||||
<ul class="widget-box--feature-list"> |
|
||||||
<li> |
|
||||||
<%= t('homescreen.blocks.upsale.additional_features') %> |
|
||||||
</li> |
|
||||||
<li> |
|
||||||
<%= t('homescreen.blocks.upsale.professional_support') %> |
|
||||||
</li> |
|
||||||
</ul> |
|
||||||
<p> |
<p> |
||||||
<b><%= t('homescreen.blocks.upsale.become_hero') %></b> <%= t('homescreen.blocks.upsale.you_contribute') %> |
<%= t('js.admin.enterprise.upsale.text') %> |
||||||
</p> |
</p> |
||||||
|
</div> |
||||||
|
|
||||||
<div class="widget-box--blocks--buttons"> |
<div class="widget-box--blocks--buttons"> |
||||||
|
<% if current_user.admin? %> |
||||||
|
<%= link_to t('js.admin.enterprise.upsale.button_start_trial'), enterprise_path, class: 'button -alt-highlight' %> |
||||||
|
<% end %> |
||||||
|
|
||||||
<%= link_to "#{OpenProject::Static::Links.links[:upsale][:href]}/?utm_source=unknown&utm_medium=community-edition&utm_campaign=home-screen", |
<%= link_to "#{OpenProject::Static::Links.links[:upsale][:href]}/?utm_source=unknown&utm_medium=community-edition&utm_campaign=home-screen", |
||||||
{ class: 'button -alt-highlight', |
{ class: 'button -highlight', |
||||||
aria: {label: t('homescreen.blocks.upsale.more_info')}, |
aria: {label: t('homescreen.blocks.upsale.more_info')}, |
||||||
target: '_blank', |
target: '_blank', |
||||||
title: t('homescreen.blocks.upsale.more_info')} do %> |
title: t('homescreen.blocks.upsale.more_info')} do %> |
||||||
<%= op_icon('button--icon icon-info2') %> |
|
||||||
<span class="button--text"><%= t('homescreen.blocks.upsale.more_info') %></span> |
<span class="button--text"><%= t('homescreen.blocks.upsale.more_info') %></span> |
||||||
|
<%= op_icon('button--icon icon-external-link') %> |
||||||
<% end %> |
<% end %> |
||||||
</div> |
</div> |
||||||
|
|
||||||
|
<span><b><%= t('js.admin.enterprise.upsale.become_hero') %></b></span> |
||||||
|
|
||||||
|
<p><%= t('js.admin.enterprise.upsale.you_contribute') %></p> |
||||||
|
|
||||||
|
<p> |
||||||
|
<b><%= t('js.admin.enterprise.upsale.confidence') %></b> |
||||||
|
</p> |
||||||
|
@ -0,0 +1,53 @@ |
|||||||
|
// -- copyright
|
||||||
|
// OpenProject is an open source project management software.
|
||||||
|
// Copyright (C) 2012-2020 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 docs/COPYRIGHT.rdoc for more details.
|
||||||
|
// ++
|
||||||
|
|
||||||
|
import {Component, ElementRef} from "@angular/core"; |
||||||
|
import {I18nService} from "app/modules/common/i18n/i18n.service"; |
||||||
|
import {EEActiveTrialBase} from "core-components/enterprise/enterprise-active-trial/ee-active-trial.base"; |
||||||
|
|
||||||
|
export const enterpriseActiveSavedTrialSelector = 'enterprise-active-saved-trial'; |
||||||
|
|
||||||
|
@Component({ |
||||||
|
selector: enterpriseActiveSavedTrialSelector, |
||||||
|
templateUrl: './ee-active-trial.component.html', |
||||||
|
styleUrls: ['./ee-active-trial.component.sass'] |
||||||
|
}) |
||||||
|
export class EEActiveSavedTrialComponent extends EEActiveTrialBase { |
||||||
|
public subscriber = this.elementRef.nativeElement.dataset['subscriber']; |
||||||
|
public email = this.elementRef.nativeElement.dataset['email']; |
||||||
|
public userCount = this.elementRef.nativeElement.dataset['userCount']; |
||||||
|
public startsAt = this.elementRef.nativeElement.dataset['startsAt']; |
||||||
|
public expiresAt = this.elementRef.nativeElement.dataset['expiresAt']; |
||||||
|
public company:string; |
||||||
|
public domain:string; |
||||||
|
|
||||||
|
constructor(readonly elementRef:ElementRef, |
||||||
|
readonly I18n:I18nService) { |
||||||
|
super(I18n); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,46 @@ |
|||||||
|
// -- copyright
|
||||||
|
// OpenProject is an open source project management software.
|
||||||
|
// Copyright (C) 2012-2020 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 docs/COPYRIGHT.rdoc for more details.
|
||||||
|
// ++
|
||||||
|
|
||||||
|
import {UntilDestroyedMixin} from "core-app/helpers/angular/until-destroyed.mixin"; |
||||||
|
import {I18nService} from "app/modules/common/i18n/i18n.service"; |
||||||
|
|
||||||
|
export class EEActiveTrialBase extends UntilDestroyedMixin { |
||||||
|
public text = { |
||||||
|
label_email: this.I18n.t('js.admin.enterprise.trial.form.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') |
||||||
|
}; |
||||||
|
|
||||||
|
constructor(readonly I18n:I18nService) { |
||||||
|
super(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
<div class="enterprise--active-token"> |
||||||
|
<div class="attributes-group"> |
||||||
|
<div class="attributes-key-value"> |
||||||
|
<div class="attributes-key-value--key">{{ text.label_subscriber }}</div> |
||||||
|
<div class="attributes-key-value--value-container"> |
||||||
|
<div class="attributes-key-value--value -text"> |
||||||
|
<span>{{ subscriber }}</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="attributes-key-value--key">{{ text.label_email }}</div> |
||||||
|
<div class="attributes-key-value--value-container"> |
||||||
|
<div class="attributes-key-value--value -text"> |
||||||
|
<span>{{ email }}</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div *ngIf="company" class="attributes-key-value--key">{{ text.label_company }}</div> |
||||||
|
<div *ngIf="company" class="attributes-key-value--value-container"> |
||||||
|
<div class="attributes-key-value--value -text"> |
||||||
|
<span>{{ company }}</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div *ngIf="domain" class="attributes-key-value--key">{{ text.label_domain }}</div> |
||||||
|
<div *ngIf="domain" class="attributes-key-value--value-container"> |
||||||
|
<div class="attributes-key-value--value -text"> |
||||||
|
<span>{{ domain }}</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div *ngIf="userCount" class="attributes-key-value--key">{{ text.label_maximum_users }}</div> |
||||||
|
<div *ngIf="userCount" class="attributes-key-value--value-container"> |
||||||
|
<div class="attributes-key-value--value -text"> |
||||||
|
<span>{{ userCount }}</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div *ngIf="startsAt" class="attributes-key-value--key">{{ text.label_starts_at }}</div> |
||||||
|
<div *ngIf="startsAt" class="attributes-key-value--value-container"> |
||||||
|
<div class="attributes-key-value--value -text"> |
||||||
|
<span>{{ startsAt }}</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<!-- only show if token expires --> |
||||||
|
<div *ngIf="expiresAt" class="attributes-key-value--key">{{ text.label_expires_at }}</div> |
||||||
|
<div *ngIf="expiresAt" class="attributes-key-value--value-container"> |
||||||
|
<div class="attributes-key-value--value -text"> |
||||||
|
<span>{{ expiresAt }}</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
@ -0,0 +1,2 @@ |
|||||||
|
.attributes-group |
||||||
|
margin-bottom: 1.25rem |
@ -0,0 +1,109 @@ |
|||||||
|
// -- copyright
|
||||||
|
// OpenProject is an open source project management software.
|
||||||
|
// Copyright (C) 2012-2020 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 docs/COPYRIGHT.rdoc for more details.
|
||||||
|
// ++
|
||||||
|
|
||||||
|
import {ChangeDetectorRef, Component, ElementRef, OnInit} from "@angular/core"; |
||||||
|
import {distinctUntilChanged} from "rxjs/operators"; |
||||||
|
import {I18nService} from "app/modules/common/i18n/i18n.service"; |
||||||
|
import {EnterpriseTrialService} from "app/components/enterprise/enterprise-trial.service"; |
||||||
|
import {HttpClient, HttpErrorResponse} from "@angular/common/http"; |
||||||
|
import {EEActiveTrialBase} from "core-components/enterprise/enterprise-active-trial/ee-active-trial.base"; |
||||||
|
import {GonService} from "core-app/modules/common/gon/gon.service"; |
||||||
|
|
||||||
|
@Component({ |
||||||
|
selector: 'enterprise-active-trial', |
||||||
|
templateUrl: './ee-active-trial.component.html', |
||||||
|
styleUrls: ['./ee-active-trial.component.sass'] |
||||||
|
}) |
||||||
|
export class EEActiveTrialComponent extends EEActiveTrialBase implements OnInit { |
||||||
|
public subscriber:string; |
||||||
|
public email:string; |
||||||
|
public userCount:string; |
||||||
|
public startsAt:string; |
||||||
|
public expiresAt:string; |
||||||
|
public company:string; |
||||||
|
public domain:string; |
||||||
|
|
||||||
|
constructor(readonly elementRef:ElementRef, |
||||||
|
readonly cdRef:ChangeDetectorRef, |
||||||
|
readonly I18n:I18nService, |
||||||
|
readonly http:HttpClient, |
||||||
|
readonly Gon:GonService, |
||||||
|
public eeTrialService:EnterpriseTrialService) { |
||||||
|
super(I18n); |
||||||
|
} |
||||||
|
|
||||||
|
ngOnInit() { |
||||||
|
if (!this.subscriber) { |
||||||
|
this.eeTrialService.userData$ |
||||||
|
.pipe( |
||||||
|
distinctUntilChanged(), |
||||||
|
this.untilDestroyed() |
||||||
|
) |
||||||
|
.subscribe(userForm => { |
||||||
|
this.formatUserData(userForm); |
||||||
|
this.cdRef.detectChanges(); |
||||||
|
}); |
||||||
|
|
||||||
|
this.initialize(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private initialize():void { |
||||||
|
let eeTrialKey = this.Gon.get('ee_trial_key') as any; |
||||||
|
|
||||||
|
if (eeTrialKey && !this.eeTrialService.userData) { |
||||||
|
// after reload: get data from Augur using the trial key saved in gon
|
||||||
|
this.eeTrialService.trialLink = this.eeTrialService.baseUrlAugur + '/public/v1/trials/' + eeTrialKey.value; |
||||||
|
this.getUserDataFromAugur(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// use the trial key saved in the db
|
||||||
|
// to get the user data from Augur
|
||||||
|
private getUserDataFromAugur() { |
||||||
|
this.http |
||||||
|
.get<any>(this.eeTrialService.trialLink + '/details') |
||||||
|
.toPromise() |
||||||
|
.then((userForm:any) => { |
||||||
|
this.formatUserData(userForm); |
||||||
|
this.eeTrialService.retryConfirmation(); |
||||||
|
}) |
||||||
|
.catch((error:HttpErrorResponse) => { |
||||||
|
// Check whether the mail has been confirmed by now
|
||||||
|
this.eeTrialService.getToken(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private formatUserData(userForm:any) { |
||||||
|
this.subscriber = userForm.first_name + ' ' + userForm.last_name; |
||||||
|
this.email = userForm.email; |
||||||
|
this.company = userForm.company; |
||||||
|
this.domain = userForm.domain; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
<div *ngIf="noTrialRequested; else alreadyRequested"> |
||||||
|
<p> |
||||||
|
<b>{{ text.text }}</b> |
||||||
|
</p> |
||||||
|
<p> |
||||||
|
<b>{{ text.become_hero }}</b><br> |
||||||
|
{{ text.you_contribute }} |
||||||
|
</p> |
||||||
|
<button class="button -alt-highlight" (click)="openTrialModal()"> |
||||||
|
{{ text.button_trial }} |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
|
||||||
|
<ng-template #alreadyRequested> |
||||||
|
<enterprise-trial-waiting></enterprise-trial-waiting> |
||||||
|
<p class="confirmation-hint">{{ text.email_not_received }} |
||||||
|
<a (click)="openTrialModal()">{{ text.try_another_email }}</a><br> |
||||||
|
{{ text.try_another_email_hint }} |
||||||
|
</p> |
||||||
|
</ng-template> |
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,7 @@ |
|||||||
|
.button |
||||||
|
float: left |
||||||
|
|
||||||
|
.confirmation-hint |
||||||
|
font-size: 0.9em |
||||||
|
font-style: italic |
||||||
|
margin-bottom: 2rem |
@ -0,0 +1,72 @@ |
|||||||
|
// -- copyright
|
||||||
|
// OpenProject is a project management system.
|
||||||
|
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||||
|
//
|
||||||
|
// 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 doc/COPYRIGHT.rdoc for more details.
|
||||||
|
// ++
|
||||||
|
|
||||||
|
import {Component, Injector} from "@angular/core"; |
||||||
|
import {I18nService} from "core-app/modules/common/i18n/i18n.service"; |
||||||
|
import {DynamicBootstrapper} from "core-app/globals/dynamic-bootstrapper"; |
||||||
|
import {EnterpriseTrialModal} from "core-components/enterprise/enterprise-modal/enterprise-trial.modal"; |
||||||
|
import {OpModalService} from "core-components/op-modals/op-modal.service"; |
||||||
|
import {EnterpriseTrialService} from "core-components/enterprise/enterprise-trial.service"; |
||||||
|
|
||||||
|
export const enterpriseBaseSelector = 'enterprise-base'; |
||||||
|
|
||||||
|
@Component({ |
||||||
|
selector: enterpriseBaseSelector, |
||||||
|
templateUrl: './enterprise-base.component.html', |
||||||
|
styleUrls: ['./enterprise-base.component.sass'] |
||||||
|
}) |
||||||
|
export class EnterpriseBaseComponent { |
||||||
|
public text = { |
||||||
|
button_trial: this.I18n.t('js.admin.enterprise.upsale.button_start_trial'), |
||||||
|
button_book: this.I18n.t('js.admin.enterprise.upsale.button_book_now'), |
||||||
|
link_quote: this.I18n.t('js.admin.enterprise.upsale.link_quote'), |
||||||
|
become_hero: this.I18n.t('js.admin.enterprise.upsale.become_hero'), |
||||||
|
you_contribute: this.I18n.t('js.admin.enterprise.upsale.you_contribute'), |
||||||
|
email_not_received: this.I18n.t('js.admin.enterprise.trial.email_not_received'), |
||||||
|
text: this.I18n.t('js.admin.enterprise.upsale.text'), |
||||||
|
try_another_email: this.I18n.t('js.admin.enterprise.trial.try_another_email'), |
||||||
|
try_another_email_hint: this.I18n.t('js.admin.enterprise.trial.try_another_email_hint') |
||||||
|
}; |
||||||
|
|
||||||
|
constructor(protected I18n:I18nService, |
||||||
|
protected opModalService:OpModalService, |
||||||
|
readonly injector:Injector, |
||||||
|
public eeTrialService:EnterpriseTrialService) { |
||||||
|
} |
||||||
|
|
||||||
|
public openTrialModal() { |
||||||
|
// cancel request and open first modal window
|
||||||
|
this.eeTrialService.cancelled = true; |
||||||
|
this.eeTrialService.modalOpen = true; |
||||||
|
this.opModalService.show(EnterpriseTrialModal, this.injector); |
||||||
|
} |
||||||
|
|
||||||
|
public get noTrialRequested() { |
||||||
|
return this.eeTrialService.status === undefined; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,86 @@ |
|||||||
|
<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 class="form--field -wide-label -required" [ngClass]="{ '-error': eeTrialService.errorMsg }"> |
||||||
|
<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' : eeTrialService.errorMsg }"> |
||||||
|
<input type="email" |
||||||
|
class="form--text-field" |
||||||
|
id="trial-email" |
||||||
|
formControlName="email" (blur)="checkMailField()"> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div *ngIf="eeTrialService.errorMsg" class="form--field-instructions">{{ eeTrialService.errorMsg }}</div> |
||||||
|
</div> |
||||||
|
<div 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"> |
||||||
|
<input type="text" |
||||||
|
id="trial-domain-name" |
||||||
|
class="form--text-field" |
||||||
|
formControlName="domain"> |
||||||
|
</div> |
||||||
|
</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> |
@ -0,0 +1,88 @@ |
|||||||
|
// -- copyright
|
||||||
|
// OpenProject is an open source project management software.
|
||||||
|
// Copyright (C) 2012-2020 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 docs/COPYRIGHT.rdoc for more details.
|
||||||
|
// ++
|
||||||
|
|
||||||
|
import {Component, ElementRef} from "@angular/core"; |
||||||
|
import {FormBuilder, Validators} from "@angular/forms"; |
||||||
|
import {I18nService} from "app/modules/common/i18n/i18n.service"; |
||||||
|
import {EnterpriseTrialService} from "core-components/enterprise/enterprise-trial.service"; |
||||||
|
|
||||||
|
const termsOfServiceURL = 'https://www.openproject.com/terms-of-service/'; |
||||||
|
const legalNoticeURL = 'https://www.openproject.com/legal-notice/'; |
||||||
|
const newsletterURL = 'https://www.openproject.com/newsletter/'; |
||||||
|
|
||||||
|
@Component({ |
||||||
|
selector: 'enterprise-trial-form', |
||||||
|
templateUrl: './ee-trial-form.component.html' |
||||||
|
}) |
||||||
|
export class EETrialFormComponent { |
||||||
|
// enterprise trial form
|
||||||
|
trialForm = this.formBuilder.group({ |
||||||
|
company: ['', Validators.required], |
||||||
|
first_name: ['', Validators.required], |
||||||
|
last_name: ['', Validators.required], |
||||||
|
email: ['', [Validators.required, Validators.email]], |
||||||
|
domain: ['', Validators.required], |
||||||
|
general_consent: [null, Validators.required], |
||||||
|
newsletter_consent: null, |
||||||
|
}); |
||||||
|
|
||||||
|
public text = { |
||||||
|
general_consent: this.I18n.t('js.admin.enterprise.trial.form.general_consent', { |
||||||
|
link_terms: termsOfServiceURL, |
||||||
|
link_privacy: legalNoticeURL |
||||||
|
}), |
||||||
|
invalid_email: this.I18n.t('js.admin.enterprise.trial.form.invalid_email'), |
||||||
|
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.admin.enterprise.trial.form.label_email'), |
||||||
|
label_domain: this.I18n.t('js.admin.enterprise.trial.form.label_domain'), |
||||||
|
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, |
||||||
|
private formBuilder:FormBuilder, |
||||||
|
public eeTrialService:EnterpriseTrialService) { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
// checks if mail is valid after input field was edited by the user
|
||||||
|
// displays message for user
|
||||||
|
public checkMailField() { |
||||||
|
if (this.trialForm.value.email !== '' && this.trialForm.controls.email.errors) { |
||||||
|
this.eeTrialService.errorMsg = this.text.invalid_email; |
||||||
|
} else { |
||||||
|
this.eeTrialService.errorMsg = undefined; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,73 @@ |
|||||||
|
<div class="op-modal--portal"> |
||||||
|
<div class="op-modal--modal-container" |
||||||
|
data-indicator-name="modal" |
||||||
|
tabindex="0"> |
||||||
|
<div class="op-modal--modal-header"> |
||||||
|
<h3 [textContent]="headerText()"></h3> |
||||||
|
<a *ngIf="showClose" class="op-modal--modal-close-button"> |
||||||
|
<i class="icon-close" (click)="closeModal($event)" [attr.title]="text.close_popup"> |
||||||
|
</i> |
||||||
|
</a> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div [ngSwitch]="openWindow()" class="op-modal--modal-body"> |
||||||
|
<!-- first modal window --> |
||||||
|
<div *ngSwitchCase="1"> |
||||||
|
<enterprise-trial-form></enterprise-trial-form> |
||||||
|
</div> |
||||||
|
<!-- second modal window --> |
||||||
|
<div *ngSwitchCase="2"> |
||||||
|
<enterprise-trial-waiting></enterprise-trial-waiting> |
||||||
|
</div> |
||||||
|
<!-- third modal window --> |
||||||
|
<div *ngSwitchCase="3"> |
||||||
|
<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="op-modal--modal-footer"> |
||||||
|
<div *ngIf="!eeTrialService.status || eeTrialService.cancelled; else mailSubmitted"> |
||||||
|
<button class="confirm-form-submit--continue button -highlight" |
||||||
|
(click)="onSubmit()" |
||||||
|
[disabled]="!trialForm || trialForm.invalid" |
||||||
|
[textContent]="text.button_submit" |
||||||
|
[attr.title]="text.button_submit" |
||||||
|
[hidden]="eeTrialService.mailSubmitted && !eeTrialService.cancelled"> |
||||||
|
</button> |
||||||
|
<button class="confirm-form-submit--cancel button" |
||||||
|
(click)="closeModal($event)" |
||||||
|
[textContent]="text.button_cancel" |
||||||
|
[attr.title]="text.button_cancel"> |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
<ng-template #mailSubmitted> |
||||||
|
<button class="confirm-form-submit--continue button -highlight" |
||||||
|
(click)="startEnterpriseTrial()" |
||||||
|
[textContent]="text.button_continue" |
||||||
|
[attr.title]="text.button_continue" |
||||||
|
[disabled]="!eeTrialService.confirmed" |
||||||
|
[hidden]="eeTrialService.trialStarted"> |
||||||
|
</button> |
||||||
|
<button *ngIf="eeTrialService.trialStarted" |
||||||
|
class="confirm-form-submit--continue button -highlight" |
||||||
|
(click)="closeModal($event)" |
||||||
|
[textContent]="text.button_continue" |
||||||
|
[attr.title]="text.button_continue"> |
||||||
|
</button> |
||||||
|
</ng-template> |
||||||
|
</div> |
||||||
|
|
||||||
|
</div> |
||||||
|
</div> |
@ -0,0 +1,9 @@ |
|||||||
|
.op-modal--modal-body |
||||||
|
padding: 0 |
||||||
|
|
||||||
|
.onboarding--video-text |
||||||
|
margin-bottom: 1.25rem |
||||||
|
|
||||||
|
.op-modal--modal-footer |
||||||
|
margin: 0 |
||||||
|
padding: 0 |
@ -0,0 +1,130 @@ |
|||||||
|
// -- copyright
|
||||||
|
// OpenProject is an open source project management software.
|
||||||
|
// Copyright (C) 2012-2020 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 docs/COPYRIGHT.rdoc for more details.
|
||||||
|
// ++
|
||||||
|
|
||||||
|
import {AfterViewInit, ChangeDetectorRef, Component, ElementRef, Inject, Input, ViewChild} from "@angular/core"; |
||||||
|
import {DomSanitizer, SafeResourceUrl} from "@angular/platform-browser"; |
||||||
|
import {FormControl, FormGroup} from "@angular/forms"; |
||||||
|
import {OpModalComponent} from "app/components/op-modals/op-modal.component"; |
||||||
|
import {OpModalLocalsToken} from "app/components/op-modals/op-modal.service"; |
||||||
|
import {OpModalLocalsMap} from "app/components/op-modals/op-modal.types"; |
||||||
|
import {I18nService} from "app/modules/common/i18n/i18n.service"; |
||||||
|
import {EETrialFormComponent} from "core-components/enterprise/enterprise-modal/enterprise-trial-form/ee-trial-form.component"; |
||||||
|
import {EnterpriseTrialService} from "core-components/enterprise/enterprise-trial.service"; |
||||||
|
|
||||||
|
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'] |
||||||
|
}) |
||||||
|
export class EnterpriseTrialModal extends OpModalComponent implements AfterViewInit { |
||||||
|
@ViewChild(EETrialFormComponent, { static: false }) formComponent:EETrialFormComponent; |
||||||
|
@Input() public opReferrer:string; |
||||||
|
|
||||||
|
public trialForm:FormGroup; |
||||||
|
public errorMsg:string|undefined; |
||||||
|
|
||||||
|
// modal configuration
|
||||||
|
public showClose = true; |
||||||
|
public closeOnEscape = false; |
||||||
|
public closeOnOutsideClick = false; |
||||||
|
|
||||||
|
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') |
||||||
|
}; |
||||||
|
|
||||||
|
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() { |
||||||
|
this.trialForm = this.formComponent.trialForm; |
||||||
|
} |
||||||
|
|
||||||
|
// checks if form is valid and submits it
|
||||||
|
public onSubmit() { |
||||||
|
if (this.trialForm.valid) { |
||||||
|
this.trialForm.addControl('_type', new FormControl('enterprise-trial')); |
||||||
|
this.eeTrialService.sendForm(this.trialForm); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public startEnterpriseTrial() { |
||||||
|
// open onboarding modal screen
|
||||||
|
this.eeTrialService.setStartTrialStatus(); |
||||||
|
} |
||||||
|
|
||||||
|
public headerText() { |
||||||
|
if (this.eeTrialService.mailSubmitted) { |
||||||
|
return this.text.heading_confirmation; |
||||||
|
} else if (this.eeTrialService.trialStarted) { |
||||||
|
return this.text.heading_next_steps; |
||||||
|
} else { |
||||||
|
return this.text.heading_test_ee; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public closeModal(event:any) { |
||||||
|
this.closeMe(event); |
||||||
|
// refresh page to show enterprise trial
|
||||||
|
if (this.eeTrialService.trialStarted || this.eeTrialService.confirmed) { |
||||||
|
window.location.reload(); |
||||||
|
} |
||||||
|
this.eeTrialService.modalOpen = false; |
||||||
|
} |
||||||
|
|
||||||
|
public trustedURL(url:string) { |
||||||
|
return this.domSanitizer.bypassSecurityTrustResourceUrl(url); |
||||||
|
} |
||||||
|
|
||||||
|
public openWindow():number { |
||||||
|
if (!this.eeTrialService.status || this.eeTrialService.cancelled) { |
||||||
|
return 1; |
||||||
|
} else if (this.eeTrialService.mailSubmitted && !this.eeTrialService.cancelled) { |
||||||
|
return 2; |
||||||
|
} else { |
||||||
|
return 3; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,16 @@ |
|||||||
|
<enterprise-active-trial></enterprise-active-trial> |
||||||
|
|
||||||
|
<p>{{ text.confirmation_info }}</p> |
||||||
|
<p> |
||||||
|
<span>{{ text.status_label }} </span> |
||||||
|
<span *ngIf="!eeTrialService.confirmed; else confirmedStatus" class="status--waiting"> |
||||||
|
{{ text.status_waiting }} |
||||||
|
|
||||||
|
<a id="resend-link" (click)="resendMail()">{{ text.resend }}</a> |
||||||
|
<p *ngIf="eeTrialService.cancelled">{{ text.session_timeout }}</p> |
||||||
|
</span> |
||||||
|
|
||||||
|
<ng-template #confirmedStatus> |
||||||
|
<span class="status--confirmed icon-yes">{{ text.status_confirmed }}</span> |
||||||
|
</ng-template> |
||||||
|
</p> |
@ -0,0 +1,7 @@ |
|||||||
|
#resend-link |
||||||
|
float: right |
||||||
|
|
||||||
|
.status--confirmed |
||||||
|
color: var(--button--alt-highlight-background-color) |
||||||
|
.status--waiting |
||||||
|
color: orange |
@ -0,0 +1,79 @@ |
|||||||
|
// -- copyright
|
||||||
|
// OpenProject is an open source project management software.
|
||||||
|
// Copyright (C) 2012-2020 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 docs/COPYRIGHT.rdoc for more details.
|
||||||
|
// ++
|
||||||
|
|
||||||
|
import {Component, ElementRef} from "@angular/core"; |
||||||
|
import {I18nService} from "app/modules/common/i18n/i18n.service"; |
||||||
|
import {EnterpriseTrialService} from "app/components/enterprise/enterprise-trial.service"; |
||||||
|
import {HttpClient, HttpErrorResponse} from "@angular/common/http"; |
||||||
|
import {NotificationsService} from "core-app/modules/common/notifications/notifications.service"; |
||||||
|
|
||||||
|
|
||||||
|
@Component({ |
||||||
|
selector: 'enterprise-trial-waiting', |
||||||
|
templateUrl: './ee-trial-waiting.component.html', |
||||||
|
styleUrls: ['./ee-trial-waiting.component.sass'] |
||||||
|
}) |
||||||
|
export class EETrialWaitingComponent { |
||||||
|
public text = { |
||||||
|
confirmation_info: this.I18n.t('js.admin.enterprise.trial.confirmation_info'), |
||||||
|
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 notificationsService:NotificationsService, |
||||||
|
public eeTrialService:EnterpriseTrialService) { |
||||||
|
} |
||||||
|
|
||||||
|
// resend mail if resend link has been clicked
|
||||||
|
public resendMail() { |
||||||
|
this.eeTrialService.cancelled = false; |
||||||
|
this.http.post(this.eeTrialService.resendLink, {}) |
||||||
|
.toPromise() |
||||||
|
.then(() => { |
||||||
|
this.notificationsService.addSuccess(this.text.resend_success); |
||||||
|
this.eeTrialService.retryConfirmation(); |
||||||
|
}) |
||||||
|
.catch(() => { |
||||||
|
if (this.eeTrialService.trialLink) { |
||||||
|
// Check whether the mail has been confirmed by now
|
||||||
|
this.eeTrialService.getToken(); |
||||||
|
} else { |
||||||
|
this.notificationsService.addError(this.text.resend_warning); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,166 @@ |
|||||||
|
import {Injectable} from "@angular/core"; |
||||||
|
import {I18nService} from "core-app/modules/common/i18n/i18n.service"; |
||||||
|
import {HttpClient, HttpErrorResponse} from "@angular/common/http"; |
||||||
|
import {PathHelperService} from "core-app/modules/common/path-helper/path-helper.service"; |
||||||
|
import {NotificationsService} from "core-app/modules/common/notifications/notifications.service"; |
||||||
|
import {FormGroup} from "@angular/forms"; |
||||||
|
import {BehaviorSubject} from 'rxjs'; |
||||||
|
|
||||||
|
@Injectable() |
||||||
|
export class EnterpriseTrialService { |
||||||
|
// user data needs to be sync in ee-active-trial.component.ts
|
||||||
|
private userDataSubject = new BehaviorSubject<any>({}); |
||||||
|
public userData$ = this.userDataSubject.asObservable(); |
||||||
|
public userData:any; |
||||||
|
public baseUrlAugur:string; |
||||||
|
|
||||||
|
|
||||||
|
public trialLink:string; |
||||||
|
public resendLink:string; |
||||||
|
|
||||||
|
public modalOpen = false; |
||||||
|
public confirmed:boolean; |
||||||
|
public cancelled = false; |
||||||
|
public status:'mailSubmitted'|'startTrial'|undefined; |
||||||
|
public errorMsg:string|undefined; |
||||||
|
|
||||||
|
constructor(readonly I18n:I18nService, |
||||||
|
protected http:HttpClient, |
||||||
|
readonly pathHelper:PathHelperService, |
||||||
|
protected notificationsService:NotificationsService) { |
||||||
|
let gon = (window as any).gon; |
||||||
|
this.baseUrlAugur = gon.augur_url; |
||||||
|
|
||||||
|
if ((window as any).gon.ee_trial_key) { |
||||||
|
this.setMailSubmittedStatus(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// send POST request with form object
|
||||||
|
// receive an enterprise trial link to access a token
|
||||||
|
public sendForm(form:FormGroup) { |
||||||
|
this.userData = form.value; |
||||||
|
this.userDataSubject.next(this.userData); |
||||||
|
|
||||||
|
this.cancelled = false; |
||||||
|
this.http.post(this.baseUrlAugur + '/public/v1/trials', form.value) |
||||||
|
.toPromise() |
||||||
|
.then((enterpriseTrial:any) => { |
||||||
|
this.trialLink = enterpriseTrial._links.self.href; |
||||||
|
this.saveTrialKey(this.trialLink); |
||||||
|
|
||||||
|
this.retryConfirmation(); |
||||||
|
}) |
||||||
|
.catch((error:HttpErrorResponse) => { |
||||||
|
// mail is invalid or user already created a trial
|
||||||
|
if (error.status === 422 || error.status === 400) { |
||||||
|
this.errorMsg = error.error.description; |
||||||
|
} else { |
||||||
|
this.notificationsService.addWarning(error.error.description || I18n.t('js.error.internal')); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
// get a token from the trial link if user confirmed mail
|
||||||
|
public getToken() { |
||||||
|
// 2) GET /public/v1/trials/:id
|
||||||
|
this.http |
||||||
|
.get<any>(this.trialLink) |
||||||
|
.toPromise() |
||||||
|
.then((res:any) => { |
||||||
|
// show confirmed status and enable continue btn
|
||||||
|
this.confirmed = true; |
||||||
|
|
||||||
|
// returns token if mail was confirmed
|
||||||
|
// -> if token is new (token_retrieved: false) save token in backend
|
||||||
|
if (!res.token_retrieved) { |
||||||
|
this.saveToken(res.token); |
||||||
|
} |
||||||
|
|
||||||
|
// load page if mail was confirmed and modal window is not open
|
||||||
|
if (!this.modalOpen) { |
||||||
|
setTimeout(() => { // display confirmed status before reloading
|
||||||
|
window.location.reload(); |
||||||
|
}, 500); |
||||||
|
} |
||||||
|
}) |
||||||
|
.catch((error:HttpErrorResponse) => { |
||||||
|
// returns error 422 while waiting of confirmation
|
||||||
|
if (error.status === 422 && error.error.identifier === 'waiting_for_email_verification') { |
||||||
|
// get resend button link
|
||||||
|
this.resendLink = error.error._links.resend.href; |
||||||
|
// save a key for the requested trial
|
||||||
|
if (!this.status || this.cancelled) { // only do it once
|
||||||
|
this.saveTrialKey(this.resendLink); |
||||||
|
} |
||||||
|
// open next modal window -> status waiting
|
||||||
|
this.setMailSubmittedStatus(); |
||||||
|
this.confirmed = false; |
||||||
|
} else if (_.get(error, 'error._type') === 'Error') { |
||||||
|
this.notificationsService.addError(error.error.message); |
||||||
|
} else { |
||||||
|
this.notificationsService.addError(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) { |
||||||
|
// extract token from resend link
|
||||||
|
let trialKey = resendlink.split('/')[6]; |
||||||
|
return this.http.post( |
||||||
|
this.pathHelper.api.v3.appBasePath + '/admin/enterprise/save_trial_key', |
||||||
|
{ trial_key: trialKey }, |
||||||
|
{ withCredentials: true } |
||||||
|
) |
||||||
|
.toPromise() |
||||||
|
.catch((e:any) => { |
||||||
|
this.notificationsService.addError(e.error.message || e.message || e); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
// save received token in controller
|
||||||
|
private saveToken(token:string) { |
||||||
|
this.http.post( |
||||||
|
this.pathHelper.api.v3.appBasePath + '/admin/enterprise', |
||||||
|
{ enterprise_token: { encoded_token: token } }, |
||||||
|
{ withCredentials: true } |
||||||
|
) |
||||||
|
.toPromise() |
||||||
|
.catch((error:HttpErrorResponse) => { |
||||||
|
this.notificationsService.addError(error.error.description || I18n.t('js.error.internal')); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
// retry request while waiting for mail confirmation
|
||||||
|
public retryConfirmation(delay:number = 5000, retries:number = 60) { |
||||||
|
if (this.cancelled || this.confirmed) { |
||||||
|
return; |
||||||
|
} else if (retries === 0) { |
||||||
|
this.cancelled = true; |
||||||
|
} else { |
||||||
|
this.getToken(); |
||||||
|
setTimeout( () => { |
||||||
|
this.retryConfirmation(delay, retries - 1); |
||||||
|
}, delay); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public setStartTrialStatus() { |
||||||
|
this.status = 'startTrial'; |
||||||
|
} |
||||||
|
|
||||||
|
public setMailSubmittedStatus() { |
||||||
|
this.status = 'mailSubmitted'; |
||||||
|
} |
||||||
|
|
||||||
|
public get trialStarted():boolean { |
||||||
|
return this.status === 'startTrial'; |
||||||
|
} |
||||||
|
|
||||||
|
public get mailSubmitted():boolean { |
||||||
|
return this.status === 'mailSubmitted'; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,59 @@ |
|||||||
|
// -- copyright
|
||||||
|
// OpenProject is an open source project management software.
|
||||||
|
// Copyright (C) 2012-2020 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 docs/COPYRIGHT.rdoc for more details.
|
||||||
|
// ++
|
||||||
|
|
||||||
|
import {NgModule} from '@angular/core'; |
||||||
|
import {OpenprojectCommonModule} from "core-app/modules/common/openproject-common.module"; |
||||||
|
import {EnterpriseTrialService} from "core-components/enterprise/enterprise-trial.service"; |
||||||
|
import {EnterpriseBaseComponent} from "core-components/enterprise/enterprise-base.component"; |
||||||
|
import {EnterpriseTrialModal} from "core-components/enterprise/enterprise-modal/enterprise-trial.modal"; |
||||||
|
import {EETrialFormComponent} from "core-components/enterprise/enterprise-modal/enterprise-trial-form/ee-trial-form.component"; |
||||||
|
import {EETrialWaitingComponent} from "core-components/enterprise/enterprise-trial-waiting/ee-trial-waiting.component"; |
||||||
|
import {EEActiveTrialComponent} from "core-components/enterprise/enterprise-active-trial/ee-active-trial.component"; |
||||||
|
import {EEActiveSavedTrialComponent} from "core-components/enterprise/enterprise-active-trial/ee-active-saved-trial.component"; |
||||||
|
import {FormsModule, ReactiveFormsModule} from "@angular/forms"; |
||||||
|
|
||||||
|
@NgModule({ |
||||||
|
imports: [ |
||||||
|
OpenprojectCommonModule, |
||||||
|
FormsModule, |
||||||
|
ReactiveFormsModule |
||||||
|
], |
||||||
|
providers: [ |
||||||
|
EnterpriseTrialService |
||||||
|
], |
||||||
|
declarations: [ |
||||||
|
EnterpriseBaseComponent, |
||||||
|
EnterpriseTrialModal, |
||||||
|
EETrialFormComponent, |
||||||
|
EETrialWaitingComponent, |
||||||
|
EEActiveTrialComponent, |
||||||
|
EEActiveSavedTrialComponent, |
||||||
|
] |
||||||
|
}) |
||||||
|
export class OpenprojectEnterpriseModule { |
||||||
|
} |
@ -0,0 +1,246 @@ |
|||||||
|
#-- copyright |
||||||
|
# OpenProject is an open source project management software. |
||||||
|
# Copyright (C) 2012-2020 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-2017 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 docs/COPYRIGHT.rdoc for more details. |
||||||
|
#++ |
||||||
|
|
||||||
|
require 'spec_helper' |
||||||
|
|
||||||
|
describe 'Enterprise trial management', |
||||||
|
type: :feature, |
||||||
|
skip: true, |
||||||
|
driver: :headless_firefox_billy do |
||||||
|
|
||||||
|
let(:admin) { FactoryBot.create(:admin) } |
||||||
|
|
||||||
|
let(:trial_id) { '1b6486b4-5a30-4042-8714-99d7c8e6b637' } |
||||||
|
let(:created_body) do |
||||||
|
{ |
||||||
|
_type: "enterprise-trial", |
||||||
|
id: trial_id, |
||||||
|
_links: |
||||||
|
{ |
||||||
|
self: |
||||||
|
{ |
||||||
|
href: "https://augur.openproject-edge.com/public/v1/trials/#{trial_id}" |
||||||
|
}, |
||||||
|
details: |
||||||
|
{ |
||||||
|
href: "https://augur.openproject-edge.com/public/v1/trials/#{trial_id}/details" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
end |
||||||
|
|
||||||
|
let(:waiting_body) do |
||||||
|
{ |
||||||
|
_type: "error", |
||||||
|
code: 422, |
||||||
|
identifier: "waiting_for_email_verification", |
||||||
|
description: "User has to confirm their email address", |
||||||
|
_links: { |
||||||
|
resend: { |
||||||
|
href: "https://augur.openproject-edge.com/public/v1/trials/#{trial_id}/resend", |
||||||
|
method: "POST" |
||||||
|
}, |
||||||
|
details: { |
||||||
|
href: "https://augur.openproject-edge.com/public/v1/trials/#{trial_id}/details" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
end |
||||||
|
|
||||||
|
let(:expired_token) do |
||||||
|
<<~EOS |
||||||
|
-----BEGIN OPENPROJECT-EE TOKEN----- |
||||||
|
eyJkYXRhIjoiTE02OG5UWjJ1cTY4dnlKNWo4NEk0ZnZGdHlFcUtEU1ZxVGd5 |
||||||
|
WnBicTUzTlA5VFFOa3NSc3haOGl1KzZpXG5VTEhuQmhnWjc5c3pYRzhTV2lt |
||||||
|
Tlg3QnpLdkh2MlFLeXFqOCtkQ2dzNHNhQUEvV21aRWZ3YmtPVExTSTBcblVY |
||||||
|
eTYxMmFnKzY0OXVOT2dOdTZmTm5mQndoTnNZdnFGRmxDZjJZd1VQU0ROZUhQ |
||||||
|
dWF2bDJEa3hlTTlLdlxuaWRNbC8wU3BxdWpzMVk4VjlLazhEejRJNUViQU1E |
||||||
|
K1NOMzE1eHplOWc2MDduN2p4c3FKS3k3RVVrUTI5XG5RRG5DSTVZSTJ6bTJv |
||||||
|
dkpaXG4iLCJrZXkiOiJSaXlnRTE0RWswdi9qVFZkOW9HRWJOcldudStQQlN2 |
||||||
|
K0xDTEVpUWZadEczY2g2djN1TERWdWVZeG8xV2NcbjRXdUFGUkdKOFEvejhn |
||||||
|
OG01NWpyMkRKdGh6UUdoVjRYa2t4ZlN2ZUdaaUVzRWJFMmh5NzQ2cDRHNjl1 |
||||||
|
b1xuaFFOQmtqZ1FqWUZwTW9yUVBSRmhXRTNjbkp1dGFKOGU1dUVTbkZPYUFD |
||||||
|
RDdsdkNvMUhMY2J4NWduMm96XG5NcXllbC96NytBdSt5QUNtT2poSlRaUW9L |
||||||
|
M25ZenVuZ1FXbXJiZm93ZGUzVVN6c1lraEdyRHlBNXJSWmlcbk9TaXpqSnNE |
||||||
|
MXBIRmZ4aVhEMnYzVlNuMWJMNXpJWFZNMDBUSFJGUHZLODVYY3IzTEVFNTZy |
||||||
|
TVBCMytnRlxuYUptcVFUYVJOWFowamJKZ3cwNFdqUEtTbGxxaDVIVWZ5Umk2 |
||||||
|
ZjErRGxxYWlsMmcvOUZpTUpPc29QOFhhXG5IYm9oUURBY1drOHBGVE9Fci8z |
||||||
|
NTNSdU4rejE3SklJdVdsM0Z2ZmhiQVFGZVdHQ0paR0JzTnJ4WUV3QzBcbmN1 |
||||||
|
WC80NFZKem9kcVkrRHlSUWFYNFlkcytCOUJVRnB2MHRaSnUrZmNza1MyVnc5 |
||||||
|
MGtCM3hQZTBmYTFjV1xuZTVSdHFucklFNXRQMzlEQ25YZWxwSmxGRXh5YzhX |
||||||
|
ekZiL3BlVEVqUWtCWnM0ZUNKMzhQT1djQXh1R2s2XG5iRFppYWlEQitJSFV4 |
||||||
|
QTBuYXhSa2R5OWJvbHBNa1RBNjk1a3Q3TnhSTDJ2WU1PZFFEY3pIekFNQmpV |
||||||
|
TE5cbnFQd3FuQWlreDgrazJqTStHWDFmTXgrUzcyb3FQTjd6M0ZqaU85S1BV |
||||||
|
VVAveTlNMG1RV1hCZEY0bVJUR1xuM2RuUDNoOXErSHNnTkFHTUNxSHBTRzNv |
||||||
|
L05ONTRQSCs1NCsyWk5MVDFzZ1ZubjBsQ1hVdlh0Vkt6b1hhXG5TdXlZNzBV |
||||||
|
R3NJTUdDYmZhdnlYREVpQzU2SWtJVzNTSU1CVHVQdisxQ1J4TCtIcEEzRE5x |
||||||
|
R09BVjVMbFFcbkUrdGNqZlNpUlVzRXcxeWkxWUZPODVEM2ZVTXdLZzFKclZB |
||||||
|
WEV1YVdvbjUxMzNVRnZNZjBNbFhkSUQ1L1xuNEtXczNGeEdmWVJJRUQ5VlhR |
||||||
|
eFNYdEQ3cWYzSlFFbHdHSGVMdUtVYkRmMWEzTEVKMUFKb2FOV0phcG9xXG4v |
||||||
|
QlU3ZHJoM28zSDFXYVBpeUhpUlQrVExTa2cxUXhxY2p0eUVuK1JiNnBKVmwr |
||||||
|
eVJXWHMrR1pkcGFDY09cbnh2ZWRIam12NzNsUjc5WFpVNVh6UlhwY3E1d1pm |
||||||
|
T2FVaHZ1NHllQysyR0FpYlIrcGowbTA0UzRXbjI0elxucjUvZGtnSG5Xek9B |
||||||
|
V2lZb0MxOEZpckhTSnVGM1FHWHJUK1JyT2c4QVdwTDlHMGZQQlpveTJvNFZj |
||||||
|
V004XG51UXJvSDUwT3Rtcm00cW53QUU3TEFyc3g3bWxOblBGMmpyejZMeWkz |
||||||
|
UlhDN1ZrSE9FVXhiUHNjZHJiRlhcbkd3cTlvNU5LNi9sb2RVTTAzeklyaTBs |
||||||
|
TVdKSlpUU3BNMnVzU0VxWUpoS05uSGI1a3lYcy9MRkhOWW05c1xuN2hBOVdS |
||||||
|
RUxWQi9Tc2x5RjJQczNzSHJQaGtZM1BGZElSeU9Kb2JxdnZoaUpPTVA5dDVu |
||||||
|
MUxUeTFjbkhGXG5DbTBDM0U1bWFjTi9hOE5OSXk2dGhia3JJVE5XK2I4K2Jw |
||||||
|
VDN3OGkxSDVYNCtodlJ5T1g0Y0JEVWhNN2pcbnN3Wkw0citmWlRmaGlNQkZi |
||||||
|
K2NmSUZ0U2lyMVBpdz09XG4iLCJpdiI6IjFxbEZqRWM4QzcrMjg4QWR6cXdL |
||||||
|
OEE9PVxuIn0= |
||||||
|
-----END OPENPROJECT-EE TOKEN----- |
||||||
|
EOS |
||||||
|
end |
||||||
|
let (:confirmed_body) do |
||||||
|
{ |
||||||
|
_type: "enterprise-trial", |
||||||
|
id: trial_id, |
||||||
|
token: expired_token, |
||||||
|
token_retrieved: false, |
||||||
|
_links: { |
||||||
|
self: { |
||||||
|
href: "https://augur.openproject-edge.com/public/v1/trials/#{trial_id}" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
end |
||||||
|
|
||||||
|
let(:mail_in_use_body) do |
||||||
|
{ |
||||||
|
_type: "error", |
||||||
|
code: 422, |
||||||
|
identifier: "user_already_created_trial", |
||||||
|
description: "Each user can only create one trial." |
||||||
|
} |
||||||
|
end |
||||||
|
|
||||||
|
before do |
||||||
|
login_as(admin) |
||||||
|
visit enterprise_path |
||||||
|
end |
||||||
|
|
||||||
|
def fill_out_modal(mail: 'foo@foocorp.example') |
||||||
|
fill_in 'Company', with: 'Foo Corp.' |
||||||
|
fill_in 'First name', with: 'Foo' |
||||||
|
fill_in 'Last name', with: 'Bar' |
||||||
|
fill_in 'Email', with: mail |
||||||
|
fill_in 'Domain', with: 'foo.example.com' |
||||||
|
|
||||||
|
find('#trial-general-consent').check |
||||||
|
end |
||||||
|
|
||||||
|
it 'blocks the request assuming the mail was used' do |
||||||
|
proxy.stub('https://augur.openproject-edge.com:443/public/v1/trials', method: 'post') |
||||||
|
.and_return(headers: {'Access-Control-Allow-Origin' => '*'}, code: 422, body: mail_in_use_body.to_json) |
||||||
|
|
||||||
|
find('.button', text: 'Start free trial').click |
||||||
|
fill_out_modal |
||||||
|
find('.button:not(:disabled)', text: 'Submit').click |
||||||
|
|
||||||
|
expect(page).to have_selector('.form--field.-error #trial-email') |
||||||
|
expect(page).to have_text 'Each user can only create one trial.' |
||||||
|
expect(page).to have_no_text 'email sent - waiting for confirmation' |
||||||
|
end |
||||||
|
|
||||||
|
context 'with a waiting request pending' do |
||||||
|
before do |
||||||
|
proxy.stub('https://augur.openproject-edge.com:443/public/v1/trials', method: 'post') |
||||||
|
.and_return(headers: {'Access-Control-Allow-Origin' => '*'}, code: 200, body: created_body.to_json) |
||||||
|
|
||||||
|
proxy.stub("https://augur.openproject-edge.com:443/public/v1/trials/#{trial_id}") |
||||||
|
.and_return(headers: {'Access-Control-Allow-Origin' => '*'}, code: 422, body: waiting_body.to_json) |
||||||
|
|
||||||
|
find('.button', text: 'Start free trial').click |
||||||
|
fill_out_modal |
||||||
|
find('.button:not(:disabled)', text: 'Submit').click |
||||||
|
|
||||||
|
expect(page).to have_text 'foo@foocorp.example' |
||||||
|
expect(page).to have_text 'email sent - waiting for confirmation' |
||||||
|
end |
||||||
|
|
||||||
|
it 'can get the trial if reloading the page' do |
||||||
|
# We need to go to another page to stop the request cycle |
||||||
|
visit info_admin_index_path |
||||||
|
|
||||||
|
# Stub with successful body |
||||||
|
# Stub the proxy to a successful return |
||||||
|
# which marks the user has confirmed the mail link |
||||||
|
proxy.stub("https://augur.openproject-edge.com:443/public/v1/trials/#{trial_id}") |
||||||
|
.and_return(headers: {'Access-Control-Allow-Origin' => '*'}, code: 200, body: confirmed_body.to_json) |
||||||
|
|
||||||
|
# Stub the details URL to still return 403 |
||||||
|
proxy.stub("https://augur.openproject-edge.com:443/public/v1/trials/#{trial_id}/details") |
||||||
|
.and_return(headers: {'Access-Control-Allow-Origin' => '*'}, code: 403) |
||||||
|
|
||||||
|
visit enterprise_path |
||||||
|
|
||||||
|
expect(page).to have_selector('.attributes-key-value--value-container', text: 'OpenProject Test', wait: 20) |
||||||
|
expect(page).to have_selector('.attributes-key-value--value-container', text: '01/01/2020') |
||||||
|
expect(page).to have_selector('.attributes-key-value--value-container', text: '01/02/2020') |
||||||
|
expect(page).to have_selector('.attributes-key-value--value-container', text: '5') |
||||||
|
# Generated expired token has different mail |
||||||
|
expect(page).to have_selector('.attributes-key-value--value-container', text: 'info@openproject.com') |
||||||
|
end |
||||||
|
|
||||||
|
it 'can confirm that trial regularly' do |
||||||
|
# Stub resend method |
||||||
|
proxy.stub("https://augur.openproject-edge.com:443/public/v1/trials/#{trial_id}/resend") |
||||||
|
.and_return(headers: {'Access-Control-Allow-Origin' => '*'}, code: 200, body: waiting_body.to_json) |
||||||
|
|
||||||
|
find('.op-modal--modal-body #resend-link', text: 'Resend').click |
||||||
|
|
||||||
|
expect(page).to have_text 'Email has been resent.' |
||||||
|
|
||||||
|
expect(page).to have_text 'foo@foocorp.example' |
||||||
|
expect(page).to have_text 'email sent - waiting for confirmation' |
||||||
|
|
||||||
|
# Stub the proxy to a successful return |
||||||
|
# which marks the user has confirmed the mail link |
||||||
|
proxy.stub("https://augur.openproject-edge.com:443/public/v1/trials/#{trial_id}") |
||||||
|
.and_return(headers: {'Access-Control-Allow-Origin' => '*'}, code: 200, body: confirmed_body.to_json) |
||||||
|
|
||||||
|
# Wait until the next request |
||||||
|
expect(page).to have_selector '.status--confirmed', text: 'confirmed', wait: 20 |
||||||
|
|
||||||
|
# advance to video |
||||||
|
click_on 'Continue' |
||||||
|
|
||||||
|
# advance to close |
||||||
|
click_on 'Continue' |
||||||
|
|
||||||
|
expect(page).to have_selector('.flash.notice', text: 'Successful update.', wait: 10) |
||||||
|
expect(page).to have_selector('.attributes-key-value--value-container', text: 'OpenProject Test') |
||||||
|
expect(page).to have_selector('.attributes-key-value--value-container', text: '01/01/2020') |
||||||
|
expect(page).to have_selector('.attributes-key-value--value-container', text: '01/02/2020') |
||||||
|
expect(page).to have_selector('.attributes-key-value--value-container', text: '5') |
||||||
|
# Generated expired token has different mail |
||||||
|
expect(page).to have_selector('.attributes-key-value--value-container', text: 'info@openproject.com') |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,39 @@ |
|||||||
|
#-- encoding: UTF-8 |
||||||
|
#-- copyright |
||||||
|
# OpenProject is an open source project management software. |
||||||
|
# Copyright (C) 2012-2020 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-2017 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 docs/COPYRIGHT.rdoc for more details. |
||||||
|
#++ |
||||||
|
|
||||||
|
# puffing-billy is a gem that creates a middleman proxy between the browser controlled |
||||||
|
# by capybara/selenium and the spec execution. |
||||||
|
# |
||||||
|
# This allows us to stub requests to external APIs to guarantee responses regardless of |
||||||
|
# their availability. |
||||||
|
# |
||||||
|
# In order to use the proxied server, you need to use `driver: headless_firefox_billy` in your examples |
||||||
|
# |
||||||
|
# See https://github.com/oesmith/puffing-billy for more information |
||||||
|
require 'billy/capybara/rspec' |
After Width: | Height: | Size: 1.5 KiB |