mirror of https://github.com/zulip/zulip.git
plans: Show buttons as per current context.
Also show correct tab based on remote / cloud user.
This commit is contained in:
parent
49908ba166
commit
8d9a7679bc
|
@ -106,8 +106,7 @@ class RemoteBillingAuthenticationTest(BouncerTestCase):
|
||||||
# Go to the URL we're redirected to after authentication and assert
|
# Go to the URL we're redirected to after authentication and assert
|
||||||
# some basic expected content.
|
# some basic expected content.
|
||||||
result = self.client_get(result["Location"], subdomain="selfhosting")
|
result = self.client_get(result["Location"], subdomain="selfhosting")
|
||||||
self.assert_in_success_response(["Your remote user info:"], result)
|
self.assert_in_success_response(["showing-self-hosted", "Retain full control"], result)
|
||||||
self.assert_in_success_response([desdemona.delivery_email], result)
|
|
||||||
|
|
||||||
@responses.activate
|
@responses.activate
|
||||||
def test_remote_billing_authentication_flow_realm_not_registered(self) -> None:
|
def test_remote_billing_authentication_flow_realm_not_registered(self) -> None:
|
||||||
|
@ -143,8 +142,7 @@ class RemoteBillingAuthenticationTest(BouncerTestCase):
|
||||||
self.assertEqual(result["Location"], f"/realm/{realm.uuid!s}/plans/")
|
self.assertEqual(result["Location"], f"/realm/{realm.uuid!s}/plans/")
|
||||||
|
|
||||||
result = self.client_get(result["Location"], subdomain="selfhosting")
|
result = self.client_get(result["Location"], subdomain="selfhosting")
|
||||||
self.assert_in_success_response(["Your remote user info:"], result)
|
self.assert_in_success_response(["showing-self-hosted", "Retain full control"], result)
|
||||||
self.assert_in_success_response([desdemona.delivery_email], result)
|
|
||||||
|
|
||||||
@responses.activate
|
@responses.activate
|
||||||
def test_remote_billing_authentication_flow_tos_consent_failure(self) -> None:
|
def test_remote_billing_authentication_flow_tos_consent_failure(self) -> None:
|
||||||
|
@ -227,8 +225,7 @@ class RemoteBillingAuthenticationTest(BouncerTestCase):
|
||||||
tick=False,
|
tick=False,
|
||||||
):
|
):
|
||||||
result = self.client_get(final_url, subdomain="selfhosting")
|
result = self.client_get(final_url, subdomain="selfhosting")
|
||||||
self.assert_in_success_response(["Your remote user info:"], result)
|
self.assert_in_success_response(["showing-self-hosted", "Retain full control"], result)
|
||||||
self.assert_in_success_response([desdemona.delivery_email], result)
|
|
||||||
|
|
||||||
# Now go there again, simulating doing this after the session has expired.
|
# Now go there again, simulating doing this after the session has expired.
|
||||||
# We should be denied access and redirected to re-auth.
|
# We should be denied access and redirected to re-auth.
|
||||||
|
@ -257,8 +254,7 @@ class RemoteBillingAuthenticationTest(BouncerTestCase):
|
||||||
)
|
)
|
||||||
self.assertEqual(result["Location"], f"/realm/{realm.uuid!s}/plans/")
|
self.assertEqual(result["Location"], f"/realm/{realm.uuid!s}/plans/")
|
||||||
result = self.client_get(result["Location"], subdomain="selfhosting")
|
result = self.client_get(result["Location"], subdomain="selfhosting")
|
||||||
self.assert_in_success_response(["Your remote user info:"], result)
|
self.assert_in_success_response(["showing-self-hosted", "Retain full control"], result)
|
||||||
self.assert_in_success_response([desdemona.delivery_email], result)
|
|
||||||
|
|
||||||
@responses.activate
|
@responses.activate
|
||||||
def test_remote_billing_unauthed_access(self) -> None:
|
def test_remote_billing_unauthed_access(self) -> None:
|
||||||
|
@ -408,7 +404,7 @@ class LegacyServerLoginTest(BouncerTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(result.status_code, 302)
|
self.assertEqual(result.status_code, 302)
|
||||||
self.assertEqual(result["Location"], f"/server/{self.uuid}/upgrade/")
|
self.assertEqual(result["Location"], f"/server/{self.uuid}/plans/")
|
||||||
|
|
||||||
# Verify the authed data that should have been stored in the session.
|
# Verify the authed data that should have been stored in the session.
|
||||||
identity_dict = LegacyServerIdentityDict(
|
identity_dict = LegacyServerIdentityDict(
|
||||||
|
|
|
@ -27,13 +27,13 @@ from corporate.views.portico import (
|
||||||
hello_view,
|
hello_view,
|
||||||
landing_view,
|
landing_view,
|
||||||
plans_view,
|
plans_view,
|
||||||
|
remote_realm_plans_page,
|
||||||
|
remote_server_plans_page,
|
||||||
team_view,
|
team_view,
|
||||||
)
|
)
|
||||||
from corporate.views.remote_billing_page import (
|
from corporate.views.remote_billing_page import (
|
||||||
remote_billing_legacy_server_login,
|
remote_billing_legacy_server_login,
|
||||||
remote_realm_billing_finalize_login,
|
remote_realm_billing_finalize_login,
|
||||||
remote_realm_plans_page,
|
|
||||||
remote_server_plans_page,
|
|
||||||
)
|
)
|
||||||
from corporate.views.session import (
|
from corporate.views.session import (
|
||||||
start_card_update_stripe_session,
|
start_card_update_stripe_session,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from dataclasses import asdict, dataclass
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
|
@ -6,9 +7,14 @@ from django.conf import settings
|
||||||
from django.contrib.auth.views import redirect_to_login
|
from django.contrib.auth.views import redirect_to_login
|
||||||
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
|
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
from corporate.lib.stripe import is_realm_on_free_trial
|
from corporate.lib.decorator import (
|
||||||
from corporate.models import get_customer_by_realm
|
authenticated_remote_realm_management_endpoint,
|
||||||
|
authenticated_remote_server_management_endpoint,
|
||||||
|
)
|
||||||
|
from corporate.lib.stripe import RemoteRealmBillingSession, RemoteServerBillingSession
|
||||||
|
from corporate.models import CustomerPlan, get_current_plan_by_customer, get_customer_by_realm
|
||||||
from zerver.context_processors import get_realm_from_request, latest_info_context
|
from zerver.context_processors import get_realm_from_request, latest_info_context
|
||||||
from zerver.decorator import add_google_analytics
|
from zerver.decorator import add_google_analytics
|
||||||
from zerver.lib.github import (
|
from zerver.lib.github import (
|
||||||
|
@ -47,16 +53,43 @@ def app_download_link_redirect(request: HttpRequest, platform: str) -> HttpRespo
|
||||||
return TemplateResponse(request, "404.html", status=404)
|
return TemplateResponse(request, "404.html", status=404)
|
||||||
|
|
||||||
|
|
||||||
|
def is_customer_on_free_trial(customer_plan: CustomerPlan) -> bool:
|
||||||
|
return customer_plan.status in (
|
||||||
|
CustomerPlan.FREE_TRIAL,
|
||||||
|
CustomerPlan.DOWNGRADE_AT_END_OF_FREE_TRIAL,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PlansPageContext:
|
||||||
|
sponsorship_url: str
|
||||||
|
free_trial_days: Optional[int]
|
||||||
|
on_free_trial: bool = False
|
||||||
|
sponsorship_pending: bool = False
|
||||||
|
is_sponsored: bool = False
|
||||||
|
|
||||||
|
is_cloud_realm: bool = False
|
||||||
|
is_self_hosted_realm: bool = False
|
||||||
|
|
||||||
|
is_new_customer: bool = False
|
||||||
|
on_free_tier: bool = False
|
||||||
|
customer_plan: Optional[CustomerPlan] = None
|
||||||
|
|
||||||
|
billing_base_url: str = ""
|
||||||
|
|
||||||
|
|
||||||
@add_google_analytics
|
@add_google_analytics
|
||||||
def plans_view(request: HttpRequest) -> HttpResponse:
|
def plans_view(request: HttpRequest) -> HttpResponse:
|
||||||
realm = get_realm_from_request(request)
|
realm = get_realm_from_request(request)
|
||||||
free_trial_days = settings.FREE_TRIAL_DAYS
|
context = PlansPageContext(
|
||||||
sponsorship_pending = False
|
is_cloud_realm=True,
|
||||||
sponsorship_url = "/sponsorship/"
|
sponsorship_url=reverse("sponsorship_request"),
|
||||||
|
free_trial_days=settings.FREE_TRIAL_DAYS,
|
||||||
|
is_sponsored=realm is not None and realm.plan_type == Realm.PLAN_TYPE_STANDARD_FREE,
|
||||||
|
)
|
||||||
if is_subdomain_root_or_alias(request):
|
if is_subdomain_root_or_alias(request):
|
||||||
# If we're on the root domain, we make this link first ask you which organization.
|
# If we're on the root domain, we make this link first ask you which organization.
|
||||||
sponsorship_url = f"/accounts/go/?{urlencode({'next': sponsorship_url})}"
|
context.sponsorship_url = f"/accounts/go/?{urlencode({'next': context.sponsorship_url})}"
|
||||||
realm_on_free_trial = False
|
|
||||||
|
|
||||||
if realm is not None:
|
if realm is not None:
|
||||||
if realm.plan_type == Realm.PLAN_TYPE_SELF_HOSTED and settings.PRODUCTION:
|
if realm.plan_type == Realm.PLAN_TYPE_SELF_HOSTED and settings.PRODUCTION:
|
||||||
|
@ -65,21 +98,99 @@ def plans_view(request: HttpRequest) -> HttpResponse:
|
||||||
return redirect_to_login(next="/plans/")
|
return redirect_to_login(next="/plans/")
|
||||||
if request.user.is_guest:
|
if request.user.is_guest:
|
||||||
return TemplateResponse(request, "404.html", status=404)
|
return TemplateResponse(request, "404.html", status=404)
|
||||||
customer = get_customer_by_realm(realm)
|
|
||||||
if customer is not None:
|
|
||||||
sponsorship_pending = customer.sponsorship_pending
|
|
||||||
realm_on_free_trial = is_realm_on_free_trial(realm)
|
|
||||||
|
|
||||||
|
customer = get_customer_by_realm(realm)
|
||||||
|
context.on_free_tier = customer is None and not context.is_sponsored
|
||||||
|
if customer is not None:
|
||||||
|
context.sponsorship_pending = customer.sponsorship_pending
|
||||||
|
context.customer_plan = get_current_plan_by_customer(customer)
|
||||||
|
if context.customer_plan is None:
|
||||||
|
# Cloud realms on free tier don't have active customer plan unless they are sponsored.
|
||||||
|
context.on_free_tier = not context.is_sponsored
|
||||||
|
else:
|
||||||
|
context.on_free_trial = is_customer_on_free_trial(context.customer_plan)
|
||||||
|
|
||||||
|
context.is_new_customer = (
|
||||||
|
not context.on_free_tier and context.customer_plan is None and not context.is_sponsored
|
||||||
|
)
|
||||||
return TemplateResponse(
|
return TemplateResponse(
|
||||||
request,
|
request,
|
||||||
"corporate/plans.html",
|
"corporate/plans.html",
|
||||||
context={
|
context=asdict(context),
|
||||||
"realm": realm,
|
)
|
||||||
"free_trial_days": free_trial_days,
|
|
||||||
"realm_on_free_trial": realm_on_free_trial,
|
|
||||||
"sponsorship_pending": sponsorship_pending,
|
@add_google_analytics
|
||||||
"sponsorship_url": sponsorship_url,
|
@authenticated_remote_realm_management_endpoint
|
||||||
},
|
def remote_realm_plans_page(
|
||||||
|
request: HttpRequest, billing_session: RemoteRealmBillingSession
|
||||||
|
) -> HttpResponse: # nocoverage
|
||||||
|
customer = billing_session.get_customer()
|
||||||
|
context = PlansPageContext(
|
||||||
|
is_self_hosted_realm=True,
|
||||||
|
sponsorship_url=reverse(
|
||||||
|
"remote_realm_sponsorship_page", args=(billing_session.remote_realm.uuid,)
|
||||||
|
),
|
||||||
|
free_trial_days=settings.FREE_TRIAL_DAYS,
|
||||||
|
billing_base_url=billing_session.billing_base_url,
|
||||||
|
is_sponsored=billing_session.is_sponsored(),
|
||||||
|
)
|
||||||
|
|
||||||
|
context.on_free_tier = customer is None and not context.is_sponsored
|
||||||
|
if customer is not None:
|
||||||
|
context.sponsorship_pending = customer.sponsorship_pending
|
||||||
|
context.customer_plan = get_current_plan_by_customer(customer)
|
||||||
|
if context.customer_plan is None:
|
||||||
|
context.on_free_tier = not context.is_sponsored
|
||||||
|
else:
|
||||||
|
context.on_free_trial = is_customer_on_free_trial(context.customer_plan)
|
||||||
|
|
||||||
|
context.is_new_customer = (
|
||||||
|
not context.on_free_tier and context.customer_plan is None and not context.is_sponsored
|
||||||
|
)
|
||||||
|
return TemplateResponse(
|
||||||
|
request,
|
||||||
|
"corporate/plans.html",
|
||||||
|
context=asdict(context),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@add_google_analytics
|
||||||
|
@authenticated_remote_server_management_endpoint
|
||||||
|
def remote_server_plans_page(
|
||||||
|
request: HttpRequest, billing_session: RemoteServerBillingSession
|
||||||
|
) -> HttpResponse: # nocoverage
|
||||||
|
customer = billing_session.get_customer()
|
||||||
|
context = PlansPageContext(
|
||||||
|
is_self_hosted_realm=True,
|
||||||
|
sponsorship_url=reverse(
|
||||||
|
"remote_server_sponsorship_page", args=(billing_session.remote_server.uuid,)
|
||||||
|
),
|
||||||
|
free_trial_days=settings.FREE_TRIAL_DAYS,
|
||||||
|
billing_base_url=billing_session.billing_base_url,
|
||||||
|
is_sponsored=billing_session.is_sponsored(),
|
||||||
|
)
|
||||||
|
|
||||||
|
context.on_free_tier = customer is None and not context.is_sponsored
|
||||||
|
if customer is not None:
|
||||||
|
context.sponsorship_pending = customer.sponsorship_pending
|
||||||
|
context.customer_plan = get_current_plan_by_customer(customer)
|
||||||
|
if context.customer_plan is None:
|
||||||
|
context.on_free_tier = not context.is_sponsored
|
||||||
|
else:
|
||||||
|
context.on_free_tier = context.customer_plan.tier in (
|
||||||
|
CustomerPlan.TIER_SELF_HOSTED_LEGACY,
|
||||||
|
CustomerPlan.TIER_SELF_HOSTED_BASE,
|
||||||
|
)
|
||||||
|
context.on_free_trial = is_customer_on_free_trial(context.customer_plan)
|
||||||
|
|
||||||
|
context.is_new_customer = (
|
||||||
|
not context.on_free_tier and context.customer_plan is None and not context.is_sponsored
|
||||||
|
)
|
||||||
|
return TemplateResponse(
|
||||||
|
request,
|
||||||
|
"corporate/plans.html",
|
||||||
|
context=asdict(context),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -13,11 +13,7 @@ from django.utils.translation import gettext as _
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from pydantic import Json
|
from pydantic import Json
|
||||||
|
|
||||||
from corporate.lib.decorator import (
|
from corporate.lib.decorator import self_hosting_management_endpoint
|
||||||
authenticated_remote_realm_management_endpoint,
|
|
||||||
authenticated_remote_server_management_endpoint,
|
|
||||||
self_hosting_management_endpoint,
|
|
||||||
)
|
|
||||||
from corporate.lib.remote_billing_util import (
|
from corporate.lib.remote_billing_util import (
|
||||||
REMOTE_BILLING_SESSION_VALIDITY_SECONDS,
|
REMOTE_BILLING_SESSION_VALIDITY_SECONDS,
|
||||||
LegacyServerIdentityDict,
|
LegacyServerIdentityDict,
|
||||||
|
@ -25,7 +21,6 @@ from corporate.lib.remote_billing_util import (
|
||||||
RemoteBillingUserDict,
|
RemoteBillingUserDict,
|
||||||
get_identity_dict_from_session,
|
get_identity_dict_from_session,
|
||||||
)
|
)
|
||||||
from corporate.lib.stripe import RemoteRealmBillingSession, RemoteServerBillingSession
|
|
||||||
from zerver.lib.exceptions import JsonableError, MissingRemoteRealmError
|
from zerver.lib.exceptions import JsonableError, MissingRemoteRealmError
|
||||||
from zerver.lib.remote_server import RealmDataForAnalytics, UserDataForRemoteBilling
|
from zerver.lib.remote_server import RealmDataForAnalytics, UserDataForRemoteBilling
|
||||||
from zerver.lib.response import json_success
|
from zerver.lib.response import json_success
|
||||||
|
@ -293,22 +288,6 @@ def remote_billing_plans_common(
|
||||||
return render_tmp_remote_billing_page(request, realm_uuid=realm_uuid, server_uuid=server_uuid)
|
return render_tmp_remote_billing_page(request, realm_uuid=realm_uuid, server_uuid=server_uuid)
|
||||||
|
|
||||||
|
|
||||||
@authenticated_remote_realm_management_endpoint
|
|
||||||
def remote_realm_plans_page(
|
|
||||||
request: HttpRequest, billing_session: RemoteRealmBillingSession
|
|
||||||
) -> HttpResponse:
|
|
||||||
realm_uuid = str(billing_session.remote_realm.uuid)
|
|
||||||
return remote_billing_plans_common(request, realm_uuid=realm_uuid, server_uuid=None)
|
|
||||||
|
|
||||||
|
|
||||||
@authenticated_remote_server_management_endpoint
|
|
||||||
def remote_server_plans_page(
|
|
||||||
request: HttpRequest, billing_session: RemoteServerBillingSession
|
|
||||||
) -> HttpResponse:
|
|
||||||
server_uuid = str(billing_session.remote_server.uuid)
|
|
||||||
return remote_billing_plans_common(request, server_uuid=server_uuid, realm_uuid=None)
|
|
||||||
|
|
||||||
|
|
||||||
def remote_billing_page_common(
|
def remote_billing_page_common(
|
||||||
request: HttpRequest, realm_uuid: Optional[str], server_uuid: Optional[str]
|
request: HttpRequest, realm_uuid: Optional[str], server_uuid: Optional[str]
|
||||||
) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
|
@ -369,10 +348,7 @@ def remote_billing_legacy_server_login(
|
||||||
reverse(f"remote_server_{next_page}_page", args=(remote_server_uuid,))
|
reverse(f"remote_server_{next_page}_page", args=(remote_server_uuid,))
|
||||||
)
|
)
|
||||||
elif remote_server.plan_type == RemoteZulipServer.PLAN_TYPE_SELF_HOSTED:
|
elif remote_server.plan_type == RemoteZulipServer.PLAN_TYPE_SELF_HOSTED:
|
||||||
# TODO: Take user to plans page once that is available.
|
return HttpResponseRedirect(reverse("remote_server_plans_page", args=(remote_server_uuid,)))
|
||||||
return HttpResponseRedirect(
|
|
||||||
reverse("remote_server_upgrade_page", args=(remote_server_uuid,))
|
|
||||||
)
|
|
||||||
elif remote_server.plan_type == RemoteZulipServer.PLAN_TYPE_COMMUNITY:
|
elif remote_server.plan_type == RemoteZulipServer.PLAN_TYPE_COMMUNITY:
|
||||||
return HttpResponseRedirect(
|
return HttpResponseRedirect(
|
||||||
reverse("remote_server_sponsorship_page", args=(remote_server_uuid,))
|
reverse("remote_server_sponsorship_page", args=(remote_server_uuid,))
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
{% include 'zerver/landing_nav.html' %}
|
{% include 'zerver/landing_nav.html' %}
|
||||||
|
|
||||||
<div class="portico-pricing plans showing-cloud">
|
<div class="portico-pricing plans {% if is_self_hosted_realm %} showing-self-hosted {% else %} showing-cloud {% endif %}">
|
||||||
<div class="main">
|
<div class="main">
|
||||||
{% include "corporate/pricing_model.html" %}
|
{% include "corporate/pricing_model.html" %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -33,15 +33,15 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="bottom">
|
<div class="bottom">
|
||||||
<div class="text-content">
|
<div class="text-content">
|
||||||
{% if not realm or realm.plan_type == realm.PLAN_TYPE_SELF_HOSTED %}
|
{% if is_cloud_realm and on_free_tier %}
|
||||||
<a href="/new/" class="button get-started-button">
|
|
||||||
Create organization
|
|
||||||
</a>
|
|
||||||
{% elif realm.plan_type == realm.PLAN_TYPE_LIMITED or sponsorship_pending %}
|
|
||||||
<div class="pricing-details"></div>
|
<div class="pricing-details"></div>
|
||||||
<a href='/upgrade/' class="button current-plan-button" type="button">
|
<a href='/upgrade/' class="button current-plan-button" type="button">
|
||||||
Current plan
|
Current plan
|
||||||
</a>
|
</a>
|
||||||
|
{% elif not is_cloud_realm or is_new_customer %}
|
||||||
|
<a href="/new/" class="button get-started-button">
|
||||||
|
Create organization
|
||||||
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -69,7 +69,7 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if not realm %}
|
{% if is_cloud_realm and on_free_tier and not sponsorship_pending %}
|
||||||
<a href="/upgrade/" class="button upgrade-button">
|
<a href="/upgrade/" class="button upgrade-button">
|
||||||
{% if free_trial_days %}
|
{% if free_trial_days %}
|
||||||
Start {{ free_trial_days }}-day free trial
|
Start {{ free_trial_days }}-day free trial
|
||||||
|
@ -77,16 +77,17 @@
|
||||||
Upgrade to Standard
|
Upgrade to Standard
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
{% elif realm.plan_type in [realm.PLAN_TYPE_STANDARD, realm.PLAN_TYPE_STANDARD_FREE] %}
|
<!-- Sponsored realm may not have customer plan. -->
|
||||||
|
{% elif (is_cloud_realm and is_sponsored) or (customer_plan and customer_plan.tier == customer_plan.TIER_CLOUD_STANDARD) %}
|
||||||
<a href='/billing' class="button current-plan-button" type="button">
|
<a href='/billing' class="button current-plan-button" type="button">
|
||||||
<i class="icon current-plan-icon"></i>
|
<i class="icon current-plan-icon"></i>
|
||||||
{% if realm_on_free_trial %}
|
{% if on_free_trial %}
|
||||||
Current plan (free trial)
|
Current plan (free trial)
|
||||||
{% else %}
|
{% else %}
|
||||||
Current plan
|
Current plan
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
{% elif sponsorship_pending %}
|
{% elif is_cloud_realm and sponsorship_pending %}
|
||||||
<a href="/billing/" class="button current-plan-button" type="button">
|
<a href="/billing/" class="button current-plan-button" type="button">
|
||||||
Sponsorship pending
|
Sponsorship pending
|
||||||
</a>
|
</a>
|
||||||
|
@ -158,13 +159,80 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="bottom">
|
<div class="bottom">
|
||||||
<div class="text-content">
|
<div class="text-content">
|
||||||
|
{% if is_self_hosted_realm and on_free_tier %}
|
||||||
|
<a href='{{ billing_base_url }}/billing' class="button current-plan-button" type="button">
|
||||||
|
<i class="icon current-plan-icon"></i>
|
||||||
|
Current plan
|
||||||
|
</a>
|
||||||
|
{% elif not is_self_hosted_realm %}
|
||||||
<a href="/self-hosting/" class="button get-started-button">
|
<a href="/self-hosting/" class="button get-started-button">
|
||||||
Self-host Zulip
|
Self-host Zulip
|
||||||
</a>
|
</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if development_environment %}
|
||||||
|
<div class="price-box" tabindex="-1">
|
||||||
|
<div class="text-content">
|
||||||
|
<h2>Business</h2>
|
||||||
|
<ul class="feature-list">
|
||||||
|
<li><span>All Free features included</span></li>
|
||||||
|
<li><span>Professional support with SLAs</span></li>
|
||||||
|
<li><span>High availability</span></li>
|
||||||
|
<li><span>Incident collaboration</span></li>
|
||||||
|
<li><span>Advanced compliance</span></li>
|
||||||
|
<li><span>Funds the Zulip open source project</span></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="bottom">
|
||||||
|
<div class="text-content">
|
||||||
|
{% if is_self_hosted_realm and on_free_tier and not sponsorship_pending %}
|
||||||
|
<a href="{{ billing_base_url }}/sponsorship/" class="button current-plan-button request-sponsorship">
|
||||||
|
Request sponsorship
|
||||||
|
</a>
|
||||||
|
<a href="{{ billing_base_url }}/upgrade/" class="button upgrade-button">
|
||||||
|
{% if free_trial_days %}
|
||||||
|
Start {{ free_trial_days }}-day free trial
|
||||||
|
{% else %}
|
||||||
|
Upgrade to Business
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
{% elif is_self_hosted_realm and (is_sponsored or (customer_plan and customer_plan.tier == customer_plan.TIER_SELF_HOSTED_BUSINESS)) %}
|
||||||
|
<a href='{{ billing_base_url }}/billing' class="button current-plan-button" type="button">
|
||||||
|
<i class="icon current-plan-icon"></i>
|
||||||
|
{% if on_free_trial %}
|
||||||
|
Current plan (free trial)
|
||||||
|
{% else %}
|
||||||
|
Current plan
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
{% elif is_self_hosted_realm and sponsorship_pending %}
|
||||||
|
<a href="{{ billing_base_url }}/billing/" class="button current-plan-button" type="button">
|
||||||
|
Sponsorship pending
|
||||||
|
</a>
|
||||||
|
{% elif is_self_hosted_realm %}
|
||||||
|
<a href="{{ billing_base_url }}/sponsorship/" class="button upgrade-button request-sponsorship">
|
||||||
|
Request sponsorship
|
||||||
|
</a>
|
||||||
|
<a href="{{ billing_base_url }}/upgrade/" class="button upgrade-button">
|
||||||
|
{% if free_trial_days %}
|
||||||
|
Start {{ free_trial_days }}-day free trial
|
||||||
|
{% else %}
|
||||||
|
Upgrade to Business
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="mailto:sales@zulip.com" target="_blank" rel="noopener noreferrer" class="button upgrade-button">
|
||||||
|
Contact sales
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="price-box" tabindex="-1">
|
<div class="price-box" tabindex="-1">
|
||||||
<div class="text-content">
|
<div class="text-content">
|
||||||
<h2>Enterprise</h2>
|
<h2>Enterprise</h2>
|
||||||
|
@ -179,9 +247,16 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="bottom">
|
<div class="bottom">
|
||||||
<div class="text-content">
|
<div class="text-content">
|
||||||
|
{% if is_self_hosted_realm and customer_plan and customer_plan.tier == customer_plan.TIER_SELF_HOSTED_ENTERPRISE %}
|
||||||
|
<a href='{{ billing_base_url }}/billing' class="button current-plan-button" type="button">
|
||||||
|
<i class="icon current-plan-icon"></i>
|
||||||
|
Current plan
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
<a href="mailto:sales@zulip.com" target="_blank" rel="noopener noreferrer" class="button upgrade-button">
|
<a href="mailto:sales@zulip.com" target="_blank" rel="noopener noreferrer" class="button upgrade-button">
|
||||||
Contact sales
|
Contact sales
|
||||||
</a>
|
</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -136,12 +136,18 @@ $(() => {
|
||||||
render_tabs(contributors);
|
render_tabs(contributors);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.location.pathname === "/plans/" && window.location.hash === "#self-hosted") {
|
if (window.location.pathname.endsWith("/plans/")) {
|
||||||
|
const tabs = ["#cloud", "#self-hosted"];
|
||||||
|
if (!tabs.includes(window.location.hash)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tab_to_show = window.location.hash;
|
||||||
|
|
||||||
// Don't scroll to the target element
|
// Don't scroll to the target element
|
||||||
window.scroll({top: 0});
|
window.scroll({top: 0});
|
||||||
const $pricing_wrapper = $(".portico-pricing");
|
const $pricing_wrapper = $(".portico-pricing");
|
||||||
$pricing_wrapper.removeClass("showing-cloud");
|
$pricing_wrapper.removeClass("showing-cloud showing-self-hosted");
|
||||||
$pricing_wrapper.addClass("showing-self-hosted");
|
$pricing_wrapper.addClass(`showing-${tab_to_show.slice(1)}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -145,6 +145,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.request-sponsorship {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.pricing-pane-scroll-container {
|
.pricing-pane-scroll-container {
|
||||||
grid-area: pricing;
|
grid-area: pricing;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
|
|
@ -562,11 +562,6 @@ class PlansPageTest(ZulipTestCase):
|
||||||
self.assertEqual(result.status_code, 302)
|
self.assertEqual(result.status_code, 302)
|
||||||
self.assertEqual(result["Location"], "https://zulip.com/plans/")
|
self.assertEqual(result["Location"], "https://zulip.com/plans/")
|
||||||
|
|
||||||
# But in the development environment, it renders a page
|
|
||||||
result = self.client_get("/plans/", subdomain="zulip")
|
|
||||||
self.assert_in_success_response([sign_up_now, upgrade_to_standard], result)
|
|
||||||
self.assert_not_in_success_response([current_plan, sponsorship_pending], result)
|
|
||||||
|
|
||||||
realm.plan_type = Realm.PLAN_TYPE_LIMITED
|
realm.plan_type = Realm.PLAN_TYPE_LIMITED
|
||||||
realm.save(update_fields=["plan_type"])
|
realm.save(update_fields=["plan_type"])
|
||||||
result = self.client_get("/plans/", subdomain="zulip")
|
result = self.client_get("/plans/", subdomain="zulip")
|
||||||
|
@ -580,6 +575,8 @@ class PlansPageTest(ZulipTestCase):
|
||||||
[sign_up_now, sponsorship_pending, upgrade_to_standard], result
|
[sign_up_now, sponsorship_pending, upgrade_to_standard], result
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Sponsored realms always have Customer entry.
|
||||||
|
customer = Customer.objects.create(realm=get_realm("zulip"), stripe_customer_id="cus_id")
|
||||||
realm.plan_type = Realm.PLAN_TYPE_STANDARD_FREE
|
realm.plan_type = Realm.PLAN_TYPE_STANDARD_FREE
|
||||||
realm.save(update_fields=["plan_type"])
|
realm.save(update_fields=["plan_type"])
|
||||||
result = self.client_get("/plans/", subdomain="zulip")
|
result = self.client_get("/plans/", subdomain="zulip")
|
||||||
|
@ -588,6 +585,14 @@ class PlansPageTest(ZulipTestCase):
|
||||||
[sign_up_now, upgrade_to_standard, sponsorship_pending], result
|
[sign_up_now, upgrade_to_standard, sponsorship_pending], result
|
||||||
)
|
)
|
||||||
|
|
||||||
|
plan = CustomerPlan.objects.create(
|
||||||
|
customer=customer,
|
||||||
|
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
|
status=CustomerPlan.ACTIVE,
|
||||||
|
billing_cycle_anchor=timezone_now(),
|
||||||
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||||
|
)
|
||||||
|
|
||||||
realm.plan_type = Realm.PLAN_TYPE_STANDARD
|
realm.plan_type = Realm.PLAN_TYPE_STANDARD
|
||||||
realm.save(update_fields=["plan_type"])
|
realm.save(update_fields=["plan_type"])
|
||||||
result = self.client_get("/plans/", subdomain="zulip")
|
result = self.client_get("/plans/", subdomain="zulip")
|
||||||
|
@ -596,14 +601,8 @@ class PlansPageTest(ZulipTestCase):
|
||||||
[sign_up_now, upgrade_to_standard, sponsorship_pending], result
|
[sign_up_now, upgrade_to_standard, sponsorship_pending], result
|
||||||
)
|
)
|
||||||
|
|
||||||
customer = Customer.objects.create(realm=get_realm("zulip"), stripe_customer_id="cus_id")
|
plan.status = CustomerPlan.FREE_TRIAL
|
||||||
plan = CustomerPlan.objects.create(
|
plan.save(update_fields=["status"])
|
||||||
customer=customer,
|
|
||||||
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
|
||||||
status=CustomerPlan.FREE_TRIAL,
|
|
||||||
billing_cycle_anchor=timezone_now(),
|
|
||||||
billing_schedule=CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
|
||||||
)
|
|
||||||
result = self.client_get("/plans/", subdomain="zulip")
|
result = self.client_get("/plans/", subdomain="zulip")
|
||||||
self.assert_in_success_response(["Current plan (free trial)"], result)
|
self.assert_in_success_response(["Current plan (free trial)"], result)
|
||||||
self.assert_not_in_success_response(
|
self.assert_not_in_success_response(
|
||||||
|
|
Loading…
Reference in New Issue