patx/projectpay

add dark logo env variable (optional)

Commit 4484faf · patx · 2026-07-06T17:13:15-04:00

Changeset
4484fafd3edbb13506d36edfd30206fd6d368c82
Parents
884c70bf0273ee9f419f24abfc4e25fa68168ba2

View source at this commit

Comments

No comments yet.

Log in to comment

Diff

diff --git a/README.md b/README.md
index 070b9f0..132f805 100644
--- a/README.md
+++ b/README.md
@@ -52,6 +52,7 @@ ProjectPay is a small web app for creating project payment pages and collecting
    MONGODB_DB=projectpay
    APP_BASE_URL=http://127.0.0.1:8000
    PUBLIC_LOGO_URL=
+   PUBLIC_DARK_LOGO_URL=
    PUBLIC_LOGO_ALT=ProjectPay Logo
 
    STRIPE_SECRET_KEY=
@@ -77,6 +78,7 @@ ProjectPay is a small web app for creating project payment pages and collecting
 | `ADMIN_PASSWORD` | Required outside development | `admin` in development only | Password for the admin dashboard. Production startup fails if this is missing or set to `admin`. |
 | `APP_BASE_URL` | Required outside development | Derived from request headers in development only | Public base URL used to generate customer payment links and Stripe return URLs. |
 | `PUBLIC_LOGO_URL` | Optional | unset | Logo image URL shown on public project payment pages. No logo is shown when this is empty. |
+| `PUBLIC_DARK_LOGO_URL` | Optional | `PUBLIC_LOGO_URL` | Dark-mode logo image URL shown on public project payment pages. When empty, the normal logo is used. |
 | `PUBLIC_LOGO_ALT` | Optional | `ProjectPay Logo` | Alt text for the public payment page logo. |
 | `MONGODB_URI` | Optional | `mongodb://localhost:27017` | MongoDB connection string. |
 | `MONGODB_DB` | Optional | `invoice_maker` | MongoDB database name. Use `projectpay` or another explicit name for new installs. |
@@ -164,7 +166,7 @@ On Heroku dynos, login rate limiting uses the final `X-Forwarded-For` value adde
 
 ## Customization
 
-- Set `PUBLIC_LOGO_URL` and `PUBLIC_LOGO_ALT` to brand the public payment page.
+- Set `PUBLIC_LOGO_URL`, `PUBLIC_DARK_LOGO_URL`, and `PUBLIC_LOGO_ALT` to brand the public payment page.
 - Update styles in `templates/base.html`.
 - The current default database name is `invoice_maker`; set `MONGODB_DB=projectpay` for a fresh ProjectPay deployment.
 - Currency formatting is optimized for USD. Non-USD currencies display as `CURRENCY amount`.
diff --git a/app.py b/app.py
index d3141fe..3df2211 100644
--- a/app.py
+++ b/app.py
@@ -201,6 +201,9 @@ class InvoiceApp(App):
         )
         self._validate_config()
         self.public_logo_url = os.getenv("PUBLIC_LOGO_URL", "").strip()
+        self.public_dark_logo_url = (
+            os.getenv("PUBLIC_DARK_LOGO_URL", "").strip() or self.public_logo_url
+        )
         self.public_logo_alt = os.getenv("PUBLIC_LOGO_ALT", "ProjectPay Logo").strip()
         self.currency = os.getenv("STRIPE_CURRENCY", "usd").upper()
         self.stripe_secret_key = os.getenv("STRIPE_SECRET_KEY", "")
@@ -904,6 +907,7 @@ class InvoiceApp(App):
         kwargs.setdefault("csrf_token", self._csrf_token())
         kwargs.setdefault("currency", self.currency)
         kwargs.setdefault("public_logo_url", self.public_logo_url)
+        kwargs.setdefault("public_dark_logo_url", self.public_dark_logo_url)
         kwargs.setdefault("public_logo_alt", self.public_logo_alt)
         return await self._render_template(template, **kwargs)
 
diff --git a/templates/public_project.html b/templates/public_project.html
index dfb941e..e5d304e 100644
--- a/templates/public_project.html
+++ b/templates/public_project.html
@@ -4,7 +4,12 @@
 {% block content %}
   {% if public_logo_url %}
     <div class="public-logo">
-      <img src="{{ public_logo_url }}" alt="{{ public_logo_alt }}">
+      <picture>
+        {% if public_dark_logo_url and public_dark_logo_url != public_logo_url %}
+          <source srcset="{{ public_dark_logo_url }}" media="(prefers-color-scheme: dark)">
+        {% endif %}
+        <img src="{{ public_logo_url }}" alt="{{ public_logo_alt }}">
+      </picture>
     </div>
   {% endif %}
   <div class="header-row">
diff --git a/tests/test_security_hardening.py b/tests/test_security_hardening.py
index 9bfac84..5a1cbd7 100644
--- a/tests/test_security_hardening.py
+++ b/tests/test_security_hardening.py
@@ -145,6 +145,21 @@ class SecurityHardeningTests(unittest.TestCase):
     def test_direct_run_host_defaults_to_loopback(self):
         self.assertEqual(invoice_app.DEFAULT_HOST, "127.0.0.1")
 
+    def test_public_dark_logo_url_defaults_to_public_logo_url(self):
+        app = self.make_app(PUBLIC_LOGO_URL="https://example.com/logo.svg")
+
+        self.assertEqual(app.public_dark_logo_url, "https://example.com/logo.svg")
+
+    def test_public_dark_logo_url_can_be_configured(self):
+        app = self.make_app(
+            PUBLIC_LOGO_URL="https://example.com/logo.svg",
+            PUBLIC_DARK_LOGO_URL="https://example.com/logo-dark.svg",
+        )
+
+        self.assertEqual(
+            app.public_dark_logo_url, "https://example.com/logo-dark.svg"
+        )
+
     def test_development_base_url_uses_request_headers_only_in_development(self):
         app = self.make_app(APP_BASE_URL="")
         request = SimpleNamespace(