diff --git a/corporate/urls.py b/corporate/urls.py index 0b62682049..c0c81164b8 100644 --- a/corporate/urls.py +++ b/corporate/urls.py @@ -9,6 +9,7 @@ from corporate.views.event_status import event_status, event_status_page from corporate.views.portico import ( app_download_link_redirect, apps_view, + communities_view, hello_view, landing_view, plans_view, @@ -125,6 +126,7 @@ landing_page_urls = [ landing_view, {"template_name": "corporate/case-studies/recurse-center-case-study.html"}, ), + path("communities/", communities_view), ] i18n_urlpatterns += landing_page_urls diff --git a/corporate/views/portico.py b/corporate/views/portico.py index 9814294578..9f5595e43f 100644 --- a/corporate/views/portico.py +++ b/corporate/views/portico.py @@ -10,6 +10,8 @@ from django.template.response import TemplateResponse from zerver.context_processors import get_realm_from_request, latest_info_context from zerver.decorator import add_google_analytics from zerver.lib.github import InvalidPlatform, get_latest_github_release_download_link_for_platform +from zerver.lib.realm_description import get_realm_text_description +from zerver.lib.realm_icon import get_realm_icon_url from zerver.lib.subdomains import is_subdomain_root_or_alias from zerver.models import Realm @@ -103,3 +105,47 @@ def landing_view(request: HttpRequest, template_name: str) -> HttpResponse: @add_google_analytics def hello_view(request: HttpRequest) -> HttpResponse: return TemplateResponse(request, "corporate/hello.html", latest_info_context()) + + +@add_google_analytics +def communities_view(request: HttpRequest) -> HttpResponse: + eligible_realms = [] + unique_org_type_ids = set() + want_to_be_advertised_realms = Realm.objects.filter( + want_advertise_in_communities_directory=True + ).order_by("name") + for realm in want_to_be_advertised_realms: + if realm.allow_web_public_streams_access(): + eligible_realms.append( + { + "id": realm.id, + "name": realm.name, + "realm_url": realm.uri, + "logo_url": get_realm_icon_url(realm), + "description": get_realm_text_description(realm), + "org_type_key": [ + org_type + for org_type in Realm.ORG_TYPES + if Realm.ORG_TYPES[org_type]["id"] == realm.org_type + ][0], + } + ) + unique_org_type_ids.add(realm.org_type) + + # Remove org_types for which there are no open organizations. + org_types = dict() + for org_type in Realm.ORG_TYPES: + if Realm.ORG_TYPES[org_type]["id"] in unique_org_type_ids: + org_types[org_type] = Realm.ORG_TYPES[org_type] + + # Remove `Unspecified` ORG_TYPE + org_types.pop("unspecified", None) + + return TemplateResponse( + request, + "corporate/communities.html", + context={ + "eligible_realms": eligible_realms, + "org_types": org_types, + }, + ) diff --git a/static/js/portico/communities.js b/static/js/portico/communities.js new file mode 100644 index 0000000000..83fbbeee67 --- /dev/null +++ b/static/js/portico/communities.js @@ -0,0 +1,23 @@ +import $ from "jquery"; + +function sync_open_organizations_page_with_current_hash() { + const hash = window.location.hash; + if (!hash || hash === "#all" || hash === "#undefined") { + $(".eligible_realm").show(); + $(".realm-category").removeClass("selected"); + $(`[data-category="all"]`).addClass("selected"); + } else { + $(".eligible_realm").hide(); + $(`.eligible_realm[data-org-type="${CSS.escape(hash.slice(1))}"]`).show(); + $(".realm-category").removeClass("selected"); + $(`[data-category="${CSS.escape(hash.slice(1))}"]`).addClass("selected"); + } +} + +// init +$(() => { + sync_open_organizations_page_with_current_hash(); + $(window).on("hashchange", () => { + sync_open_organizations_page_with_current_hash(); + }); +}); diff --git a/static/styles/portico/integrations.css b/static/styles/portico/integrations.css index 6ee9afd6ce..90db87b73c 100644 --- a/static/styles/portico/integrations.css +++ b/static/styles/portico/integrations.css @@ -342,7 +342,8 @@ $category-text: hsl(219, 23%, 33%); font-weight: 400; } - .integration-category { + .integration-category, + .realm-category { font-size: 0.85em; margin-top: 5px; font-weight: 400; @@ -390,7 +391,8 @@ $category-text: hsl(219, 23%, 33%); font-size: 1em; } - .integration-category { + .integration-category, + .realm-category { font-size: 0.8em; } } @@ -416,7 +418,8 @@ $category-text: hsl(219, 23%, 33%); font-size: 0.9em; } - .integration-category { + .integration-category, + .realm-category { font-size: 0.77em; } } @@ -465,7 +468,8 @@ $category-text: hsl(219, 23%, 33%); margin: 0 21px 20px; order: 1; - .integration-category { + .integration-category, + .realm-category { display: none; } @@ -501,7 +505,8 @@ $category-text: hsl(219, 23%, 33%); .categories { order: 2; - .integration-category { + .integration-category, + .realm-category { font-size: 0.9em; font-weight: 400; padding: 6px 3px; @@ -585,3 +590,89 @@ $category-text: hsl(219, 23%, 33%); } } } + +.portico-landing.integrations.communities { + .main { + visibility: visible; + } + + .portico-page-heading { + color: hsl(0, 0%, 0%); + font-size: 44px; + } + + .portico-page-subheading { + font-size: 16px; + font-weight: 400; + color: hsl(0, 0%, 20%); + max-width: 340px; + margin: auto; + } + + .integration-categories-sidebar h3 { + color: hsl(0, 0%, 0%); + font-size: 20px; + font-weight: 600; + } + + .catalog { + margin-top: 50px; + } + + .eligible_realms { + display: flex; + flex-direction: column; + gap: 30px; + + .eligible_realm { + display: flex; + gap: 20px; + padding: 5px 10px; + border: 1px solid transparent; + border-radius: 4px; + + &:hover { + border: 1px solid hsl(167, 34%, 56%); + } + + .eligible_realm_logo { + display: flex; + width: 60px; + height: 60px; + } + + .eligible_realm_details { + display: flex; + flex-direction: column; + justify-content: center; + + .eligible_realm_name { + font-weight: 400; + font-size: 21px; + color: hsl(220.6, 20%, 33.3%); + line-height: 23px; + margin: 0; + } + + .eligible_realm_description { + font-weight: 400; + font-size: 15px; + color: hsl(220, 2.7%, 56.5%); + line-height: 19px; + margin: 0; + width: min(600px, 50vw); + /* For restricting text to only two lines. + See https://caniuse.com/?search=display%3A%20-webkit-box for support. */ + overflow: hidden; + display: -webkit-box; /* stylelint-disable-line value-no-vendor-prefix */ + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + } + } + } + + .eligible_realm_end_notice { + text-align: center; + } + } +} diff --git a/templates/corporate/communities.html b/templates/corporate/communities.html new file mode 100644 index 0000000000..e6abc0e8e6 --- /dev/null +++ b/templates/corporate/communities.html @@ -0,0 +1,89 @@ +{% extends "zerver/portico.html" %} +{% set entrypoint = "communities" %} + +{% block customhead %} + +{% endblock %} + +{% block hello_page_container %} hello-main{% endblock %} + +{% block portico_content %} + +{% include 'zerver/landing_nav.html' %} +{% include 'zerver/gradients.html' %} + +
+
+
+
+ +
+
+

+ {% trans %}Open communities directory{% endtrans %} +

+

+ These Zulip communities are open to the public, and have + asked to be listed. +

+
+
+ +
+ + +
+ +
+
+

{% trans %}Categories{% endtrans %}

+ +

{% trans %}All{% endtrans %}

+
+ {% for org_type in org_types.keys() %} + +

+ {{ org_types[org_type]["name"] }} +

+
+ {% endfor %} +
+
+ {% for eligible_realm in eligible_realms %} + + +
+

{{ eligible_realm.name }}

+

+ {{ eligible_realm.description }} +

+
+
+ {% endfor %} +
+
+

Learn how Zulip can be a home for your community.

+
+
+
+ +
+
+
+
+ +{% endblock %} diff --git a/tools/webpack.assets.json b/tools/webpack.assets.json index 33ae55e102..d829facff7 100644 --- a/tools/webpack.assets.json +++ b/tools/webpack.assets.json @@ -57,6 +57,12 @@ "./static/styles/portico/landing_page.css", "./static/styles/portico/integrations.css" ], + "communities": [ + "./static/js/bundles/portico", + "./static/js/portico/communities", + "./static/styles/portico/landing_page.css", + "./static/styles/portico/integrations.css" + ], "signup": ["./static/js/bundles/portico", "jquery-validation", "./static/js/portico/signup"], "register": ["./static/js/bundles/portico", "jquery-validation", "./static/js/portico/signup"], "confirm-preregistrationuser": [ diff --git a/zerver/tests/test_docs.py b/zerver/tests/test_docs.py index f095544d9e..49a369a96f 100644 --- a/zerver/tests/test_docs.py +++ b/zerver/tests/test_docs.py @@ -199,6 +199,13 @@ class DocPageTest(ZulipTestCase): result = self.client_get("/for/companies/", follow=True) self.assert_in_success_response(["Communication efficiency represents"], result) + def test_open_organizations_endpoint(self) -> None: + realm = get_realm("zulip") + realm.want_advertise_in_communities_directory = True + realm.save() + + self._test("/communities/", "Open communities directory") + def test_portico_pages_open_graph_metadata(self) -> None: # Why Zulip url = "/why-zulip/"