Members page: show a live total member count
Count every membership account (free and paid together) on each page load and show it under the headline; no per-tier numbers. Hidden when there are no members. Also correct the reference tiers.eom.json and service-groups.eom.json to the real LDAP DNs (ou=Memberships; ou=<Service>,ou=Services), which now match the ansible-role-www defaults. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,8 @@
|
|||||||
.member-main { padding-bottom: 4rem; }
|
.member-main { padding-bottom: 4rem; }
|
||||||
.member-hero { padding-block: clamp(3rem, 8vw, 6rem); }
|
.member-hero { padding-block: clamp(3rem, 8vw, 6rem); }
|
||||||
.member-hero h1 { font-size: clamp(2.8rem, 6vw, 5rem); max-width: 900px; }
|
.member-hero h1 { font-size: clamp(2.8rem, 6vw, 5rem); max-width: 900px; }
|
||||||
|
.member-count { color: var(--muted); font-size: 1.1rem; }
|
||||||
|
.member-count strong { color: var(--accent-strong); font-size: 1.6rem; font-variant-numeric: tabular-nums; }
|
||||||
.member-intro { max-width: 700px; color: var(--muted); font-size: 1.2rem; }
|
.member-intro { max-width: 700px; color: var(--muted); font-size: 1.2rem; }
|
||||||
.member-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem; }
|
.member-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem; }
|
||||||
.member-actions form { margin: 0; }
|
.member-actions form { margin: 0; }
|
||||||
|
|||||||
@@ -51,6 +51,15 @@ class RegistrationTests(TestCase):
|
|||||||
self.assertEqual(response.headers["Cache-Control"], "no-store")
|
self.assertEqual(response.headers["Cache-Control"], "no-store")
|
||||||
self.assertContains(self.client.get("/members/register/"), "Send verification email")
|
self.assertContains(self.client.get("/members/register/"), "Send verification email")
|
||||||
|
|
||||||
|
def test_home_shows_live_total_member_count(self):
|
||||||
|
self.assertNotContains(self.client.get("/members/"), "member-count")
|
||||||
|
Account.objects.create(uid="alice", email="alice@example.org")
|
||||||
|
self.assertContains(self.client.get("/members/"), "<strong>1</strong> member and counting", html=False)
|
||||||
|
Account.objects.create(uid="bob", email="bob@example.org", membership_level=3)
|
||||||
|
response = self.client.get("/members/")
|
||||||
|
# One total across free and paid levels; no per-tier numbers.
|
||||||
|
self.assertContains(response, "<strong>2</strong> members and counting", html=False)
|
||||||
|
|
||||||
def start_signup(self):
|
def start_signup(self):
|
||||||
self.client.post("/members/register/", {"uid": "alice", "email": "ALICE@example.org"})
|
self.client.post("/members/register/", {"uid": "alice", "email": "ALICE@example.org"})
|
||||||
match = re.search(r"/verify/([^/]+)/", str(mail.outbox[-1].body))
|
match = re.search(r"/verify/([^/]+)/", str(mail.outbox[-1].body))
|
||||||
|
|||||||
@@ -79,7 +79,9 @@ def home(request):
|
|||||||
# Presentation copies only: never mutate authorization/payment settings.
|
# Presentation copies only: never mutate authorization/payment settings.
|
||||||
tiers = settings.MEMBERSHIP_TIERS
|
tiers = settings.MEMBERSHIP_TIERS
|
||||||
services = service_access_catalog(tiers, settings.MEMBERSHIP_SERVICE_GROUPS)
|
services = service_access_catalog(tiers, settings.MEMBERSHIP_SERVICE_GROUPS)
|
||||||
return page(request, account=account, tiers=tiers, services=services, subscriptions=subscriptions, crypto_invoices=invoices, shkeeper_enabled=settings.SHKEEPER_ENABLED, now=timezone.now())
|
# Public total across every level (free and paid); never a per-tier breakdown.
|
||||||
|
member_count = Account.objects.count()
|
||||||
|
return page(request, account=account, tiers=tiers, services=services, subscriptions=subscriptions, crypto_invoices=invoices, shkeeper_enabled=settings.SHKEEPER_ENABLED, now=timezone.now(), member_count=member_count)
|
||||||
|
|
||||||
|
|
||||||
@require_http_methods(["GET", "POST"])
|
@require_http_methods(["GET", "POST"])
|
||||||
|
|||||||
@@ -1,44 +1,44 @@
|
|||||||
{
|
{
|
||||||
"0": [
|
"0": [
|
||||||
"cn=Dex Users,ou=Services,dc=eom,dc=dev",
|
"cn=Dex Users,ou=Dex,ou=Services,dc=eom,dc=dev",
|
||||||
"cn=Discourse Users,ou=Services,dc=eom,dc=dev",
|
"cn=Discourse Users,ou=Discourse,ou=Services,dc=eom,dc=dev",
|
||||||
"cn=Gitea Users,ou=Services,dc=eom,dc=dev",
|
"cn=Gitea Users,ou=Gitea,ou=Services,dc=eom,dc=dev",
|
||||||
"cn=Matrix Users,ou=Services,dc=eom,dc=dev"
|
"cn=Matrix Users,ou=Matrix,ou=Services,dc=eom,dc=dev"
|
||||||
],
|
],
|
||||||
"1": [
|
"1": [
|
||||||
"cn=Dex Users,ou=Services,dc=eom,dc=dev",
|
"cn=Dex Users,ou=Dex,ou=Services,dc=eom,dc=dev",
|
||||||
"cn=Discourse Users,ou=Services,dc=eom,dc=dev",
|
"cn=Discourse Users,ou=Discourse,ou=Services,dc=eom,dc=dev",
|
||||||
"cn=Gitea Users,ou=Services,dc=eom,dc=dev",
|
"cn=Gitea Users,ou=Gitea,ou=Services,dc=eom,dc=dev",
|
||||||
"cn=Matrix Users,ou=Services,dc=eom,dc=dev"
|
"cn=Matrix Users,ou=Matrix,ou=Services,dc=eom,dc=dev"
|
||||||
],
|
],
|
||||||
"2": [
|
"2": [
|
||||||
"cn=Dex Users,ou=Services,dc=eom,dc=dev",
|
"cn=Dex Users,ou=Dex,ou=Services,dc=eom,dc=dev",
|
||||||
"cn=Discourse Users,ou=Services,dc=eom,dc=dev",
|
"cn=Discourse Users,ou=Discourse,ou=Services,dc=eom,dc=dev",
|
||||||
"cn=Gitea Users,ou=Services,dc=eom,dc=dev",
|
"cn=Gitea Users,ou=Gitea,ou=Services,dc=eom,dc=dev",
|
||||||
"cn=Matrix Users,ou=Services,dc=eom,dc=dev"
|
"cn=Matrix Users,ou=Matrix,ou=Services,dc=eom,dc=dev"
|
||||||
],
|
],
|
||||||
"3": [
|
"3": [
|
||||||
"cn=Dex Users,ou=Services,dc=eom,dc=dev",
|
"cn=Dex Users,ou=Dex,ou=Services,dc=eom,dc=dev",
|
||||||
"cn=Discourse Users,ou=Services,dc=eom,dc=dev",
|
"cn=Discourse Users,ou=Discourse,ou=Services,dc=eom,dc=dev",
|
||||||
"cn=Gitea Users,ou=Services,dc=eom,dc=dev",
|
"cn=Gitea Users,ou=Gitea,ou=Services,dc=eom,dc=dev",
|
||||||
"cn=Matrix Users,ou=Services,dc=eom,dc=dev",
|
"cn=Matrix Users,ou=Matrix,ou=Services,dc=eom,dc=dev",
|
||||||
"cn=Mastodon Users,ou=Services,dc=eom,dc=dev",
|
"cn=Mastodon Users,ou=Mastodon,ou=Services,dc=eom,dc=dev",
|
||||||
"cn=PeerTube Users,ou=Services,dc=eom,dc=dev"
|
"cn=PeerTube Users,ou=PeerTube,ou=Services,dc=eom,dc=dev"
|
||||||
],
|
],
|
||||||
"4": [
|
"4": [
|
||||||
"cn=Dex Users,ou=Services,dc=eom,dc=dev",
|
"cn=Dex Users,ou=Dex,ou=Services,dc=eom,dc=dev",
|
||||||
"cn=Discourse Users,ou=Services,dc=eom,dc=dev",
|
"cn=Discourse Users,ou=Discourse,ou=Services,dc=eom,dc=dev",
|
||||||
"cn=Gitea Users,ou=Services,dc=eom,dc=dev",
|
"cn=Gitea Users,ou=Gitea,ou=Services,dc=eom,dc=dev",
|
||||||
"cn=Matrix Users,ou=Services,dc=eom,dc=dev",
|
"cn=Matrix Users,ou=Matrix,ou=Services,dc=eom,dc=dev",
|
||||||
"cn=Mastodon Users,ou=Services,dc=eom,dc=dev",
|
"cn=Mastodon Users,ou=Mastodon,ou=Services,dc=eom,dc=dev",
|
||||||
"cn=PeerTube Users,ou=Services,dc=eom,dc=dev"
|
"cn=PeerTube Users,ou=PeerTube,ou=Services,dc=eom,dc=dev"
|
||||||
],
|
],
|
||||||
"5": [
|
"5": [
|
||||||
"cn=Dex Users,ou=Services,dc=eom,dc=dev",
|
"cn=Dex Users,ou=Dex,ou=Services,dc=eom,dc=dev",
|
||||||
"cn=Discourse Users,ou=Services,dc=eom,dc=dev",
|
"cn=Discourse Users,ou=Discourse,ou=Services,dc=eom,dc=dev",
|
||||||
"cn=Gitea Users,ou=Services,dc=eom,dc=dev",
|
"cn=Gitea Users,ou=Gitea,ou=Services,dc=eom,dc=dev",
|
||||||
"cn=Matrix Users,ou=Services,dc=eom,dc=dev",
|
"cn=Matrix Users,ou=Matrix,ou=Services,dc=eom,dc=dev",
|
||||||
"cn=Mastodon Users,ou=Services,dc=eom,dc=dev",
|
"cn=Mastodon Users,ou=Mastodon,ou=Services,dc=eom,dc=dev",
|
||||||
"cn=PeerTube Users,ou=Services,dc=eom,dc=dev"
|
"cn=PeerTube Users,ou=PeerTube,ou=Services,dc=eom,dc=dev"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
<section class="member-hero">
|
<section class="member-hero">
|
||||||
<p class="eyebrow">Music, films, projects & shared worlds</p>
|
<p class="eyebrow">Music, films, projects & shared worlds</p>
|
||||||
<h1>Support the work.<br>Get closer to what’s next.</h1>
|
<h1>Support the work.<br>Get closer to what’s next.</h1>
|
||||||
|
{% if member_count %}<p class="member-count"><strong>{{ member_count }}</strong> member{{ member_count|pluralize }} and counting</p>{% endif %}
|
||||||
<p class="member-intro">Membership supports my music, films, writing, open-source projects, and game servers. Join the community for free, or choose a paid tier to support the work and receive the benefits listed below.</p>
|
<p class="member-intro">Membership supports my music, films, writing, open-source projects, and game servers. Join the community for free, or choose a paid tier to support the work and receive the benefits listed below.</p>
|
||||||
{% if account %}
|
{% if account %}
|
||||||
<div class="member-panel account-panel">
|
<div class="member-panel account-panel">
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"price_id": "price_1Rgv4xC5KPXqTvVLfMd6z7wn",
|
"price_id": "price_1Rgv4xC5KPXqTvVLfMd6z7wn",
|
||||||
"price_label": "$4 USD / month",
|
"price_label": "$4 USD / month",
|
||||||
"usd_amount": "4.00",
|
"usd_amount": "4.00",
|
||||||
"group_dn": "cn=Level 1,ou=Members,dc=eom,dc=dev",
|
"group_dn": "cn=Level 1,ou=Memberships,dc=eom,dc=dev",
|
||||||
"description": "Go beyond the public feed.",
|
"description": "Go beyond the public feed.",
|
||||||
"benefits": [
|
"benefits": [
|
||||||
"Exclusive Discourse and Matrix content, including work-in-progress updates",
|
"Exclusive Discourse and Matrix content, including work-in-progress updates",
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
"price_id": "price_1Rgv5QC5KPXqTvVLv1PaHWb5",
|
"price_id": "price_1Rgv5QC5KPXqTvVLv1PaHWb5",
|
||||||
"price_label": "$8 USD / month",
|
"price_label": "$8 USD / month",
|
||||||
"usd_amount": "8.00",
|
"usd_amount": "8.00",
|
||||||
"group_dn": "cn=Level 2,ou=Members,dc=eom,dc=dev",
|
"group_dn": "cn=Level 2,ou=Memberships,dc=eom,dc=dev",
|
||||||
"description": "Try new work before release.",
|
"description": "Try new work before release.",
|
||||||
"benefits": [
|
"benefits": [
|
||||||
"Silver-tier giveaways",
|
"Silver-tier giveaways",
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
"price_id": "price_1Rgv5rC5KPXqTvVL8Ol8sMUf",
|
"price_id": "price_1Rgv5rC5KPXqTvVL8Ol8sMUf",
|
||||||
"price_label": "$16 USD / month",
|
"price_label": "$16 USD / month",
|
||||||
"usd_amount": "16.00",
|
"usd_amount": "16.00",
|
||||||
"group_dn": "cn=Level 3,ou=Members,dc=eom,dc=dev",
|
"group_dn": "cn=Level 3,ou=Memberships,dc=eom,dc=dev",
|
||||||
"description": "Help shape the next experiment.",
|
"description": "Help shape the next experiment.",
|
||||||
"benefits": [
|
"benefits": [
|
||||||
"Gold-tier giveaways",
|
"Gold-tier giveaways",
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
"price_id": "price_1Rgv6FC5KPXqTvVL39mAxYjm",
|
"price_id": "price_1Rgv6FC5KPXqTvVL39mAxYjm",
|
||||||
"price_label": "$32 USD / month",
|
"price_label": "$32 USD / month",
|
||||||
"usd_amount": "32.00",
|
"usd_amount": "32.00",
|
||||||
"group_dn": "cn=Level 4,ou=Members,dc=eom,dc=dev",
|
"group_dn": "cn=Level 4,ou=Memberships,dc=eom,dc=dev",
|
||||||
"description": "Have a voice in the platform's direction.",
|
"description": "Have a voice in the platform's direction.",
|
||||||
"benefits": [
|
"benefits": [
|
||||||
"Platinum-tier giveaways",
|
"Platinum-tier giveaways",
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
"price_id": "price_1Rgv6mC5KPXqTvVLkcmznWZa",
|
"price_id": "price_1Rgv6mC5KPXqTvVLkcmznWZa",
|
||||||
"price_label": "$64 USD / month",
|
"price_label": "$64 USD / month",
|
||||||
"usd_amount": "64.00",
|
"usd_amount": "64.00",
|
||||||
"group_dn": "cn=Level 5,ou=Members,dc=eom,dc=dev",
|
"group_dn": "cn=Level 5,ou=Memberships,dc=eom,dc=dev",
|
||||||
"description": "Give the independent work its biggest boost.",
|
"description": "Give the independent work its biggest boost.",
|
||||||
"benefits": [
|
"benefits": [
|
||||||
"Diamond-tier giveaways",
|
"Diamond-tier giveaways",
|
||||||
|
|||||||
Reference in New Issue
Block a user