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 %}Filter by category{% endtrans %}
+
+
+
+
+
+
+
+
+
+
+
+
+{% 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/"