Crisis banners
authorRadek Czajka <rczajka@rczajka.pl>
Thu, 5 Dec 2024 21:03:46 +0000 (22:03 +0100)
committerRadek Czajka <rczajka@rczajka.pl>
Thu, 5 Dec 2024 21:03:46 +0000 (22:03 +0100)
src/annoy/apps.py
src/annoy/migrations/0017_banner_progress_banner_target_alter_banner_place.py [new file with mode: 0644]
src/annoy/models.py
src/annoy/places.py
src/annoy/signals.py [new file with mode: 0644]
src/annoy/templates/annoy/banner_crisis.html [new file with mode: 0644]
src/annoy/templatetags/annoy.py
src/club/templates/club/donation_step_base.html
src/wolnelektury/static/2022/styles/layout/_annoy.scss
src/wolnelektury/static/js/main.js
src/wolnelektury/templates/header.html

index 213a4e9..b2b7eff 100644 (file)
@@ -3,3 +3,6 @@ from django.apps import AppConfig
 
 class AnnoyConfig(AppConfig):
     name = 'annoy'
+
+    def ready(self):
+        from . import signals
diff --git a/src/annoy/migrations/0017_banner_progress_banner_target_alter_banner_place.py b/src/annoy/migrations/0017_banner_progress_banner_target_alter_banner_place.py
new file mode 100644 (file)
index 0000000..62246bf
--- /dev/null
@@ -0,0 +1,28 @@
+# Generated by Django 4.0.8 on 2024-12-04 11:54
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('annoy', '0016_alter_mediainsertset_file_format'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='banner',
+            name='progress',
+            field=models.IntegerField(blank=True, null=True, verbose_name='postęp'),
+        ),
+        migrations.AddField(
+            model_name='banner',
+            name='target',
+            field=models.IntegerField(blank=True, null=True, verbose_name='cel'),
+        ),
+        migrations.AlterField(
+            model_name='banner',
+            name='place',
+            field=models.SlugField(choices=[('top', 'U góry wszystkich stron'), ('book-page', 'Strona książki'), ('book-text-intermission', 'Przerwa w treści książki'), ('book-fragment-list', 'Obok listy fragmentów książki'), ('blackout', 'Blackout'), ('crisis', 'Kryzysowa')], verbose_name='miejsce'),
+        ),
+    ]
index e60e3c0..92b8184 100644 (file)
@@ -33,6 +33,8 @@ class Banner(models.Model):
         help_text='Bannery z wyższym priorytetem mają pierwszeństwo.')
     since = models.DateTimeField('od', null=True, blank=True)
     until = models.DateTimeField('do', null=True, blank=True)
+    target = models.IntegerField('cel', null=True, blank=True)
+    progress = models.IntegerField('postęp', null=True, blank=True)
     show_members = models.BooleanField('widoczny dla członków klubu', default=False)
     staff_preview = models.BooleanField('podgląd tylko dla zespołu', default=False)
     only_authenticated = models.BooleanField('tylko dla zalogowanych', default=False)
@@ -49,10 +51,10 @@ class Banner(models.Model):
         return Template(self.text).render(Context())
 
     @classmethod
-    def choice(cls, place, request):
+    def choice(cls, place, request, exemptions=True):
         Membership = apps.get_model('club', 'Membership')
 
-        if hasattr(request, 'annoy_banner_exempt'):
+        if exemptions and hasattr(request, 'annoy_banner_exempt'):
             return cls.objects.none()
 
         if settings.DEBUG:
@@ -78,6 +80,29 @@ class Banner(models.Model):
 
         return banners
 
+    @property
+    def progress_percent(self):
+        if not self.target:
+            return 0
+        return (self.progress or 0) / self.target * 100
+
+    def update_progress(self):
+        # Total of new payments during the action.
+        # This definition will need to change for longer timespans.
+        if not self.since or not self.until or not self.target:
+            return
+        Schedule = apps.get_model('club', 'Schedule')
+        self.progress = Schedule.objects.filter(
+            payed_at__gte=self.since,
+            payed_at__lte=self.until,
+        ).aggregate(c=models.Sum('amount'))['c']
+        self.save(update_fields=['progress'])
+
+    @classmethod
+    def update_all_progress(cls):
+        for obj in cls.objects.exclude(target=None):
+            obj.update_progress()
+
 
 class DynamicTextInsert(models.Model):
     paragraphs = models.IntegerField('akapity')
@@ -146,12 +171,3 @@ class MediaInsertText(models.Model):
 
     class Meta:
         ordering = ('ordering',)
-
-
-from django.db.models.signals import post_save, post_delete
-from django.dispatch import receiver
-
-@receiver(post_delete, sender=MediaInsertText)
-@receiver(post_save, sender=MediaInsertText)
-def update_etag(sender, instance, **kwargs):
-    instance.media_insert_set.update_etag()
index c9f8031..ca33242 100644 (file)
@@ -8,6 +8,7 @@ PLACE_DEFINITIONS = [
 #        ('centre', 'Środek ekranu'),
         ('upper', 'Górna połowa ekranu'),
     )),
+    ('crisis', 'Kryzysowa', False),
 ]
 
 PLACE_CHOICES = [p[:2] for p in PLACE_DEFINITIONS]
diff --git a/src/annoy/signals.py b/src/annoy/signals.py
new file mode 100644 (file)
index 0000000..c985282
--- /dev/null
@@ -0,0 +1,18 @@
+from django.db.models.signals import post_save, post_delete
+from django.dispatch import receiver
+import club.models
+from . import models
+
+
+@receiver(post_delete, sender=models.MediaInsertText)
+@receiver(post_save, sender=models.MediaInsertText)
+def update_etag(sender, instance, **kwargs):
+    instance.media_insert_set.update_etag()
+
+
+@receiver(post_save, sender=club.models.Schedule)
+def update_progress(sender, instance, **kwargs):
+    try:
+        models.Banner.update_all_progress()
+    except:
+        pass
diff --git a/src/annoy/templates/annoy/banner_crisis.html b/src/annoy/templates/annoy/banner_crisis.html
new file mode 100644 (file)
index 0000000..cd39469
--- /dev/null
@@ -0,0 +1,60 @@
+{% load l10n %}
+{% load time_tags %}
+
+{% if banner %}
+<div class="annoy-banner_crisis-container">
+  <div class="
+              annoy-banner
+              annoy-banner_{{ banner.place }}
+              annoy-banner-style_{{ banner.style }}
+              {% if banner.image %}with-image{% endif %}
+              {% if banner.smallfont %}banner-smallfont{% endif %}
+              "
+        id="annoy-banner-{{ banner.id }}"
+       style="
+           {% if banner.text_color %}color: {{ banner.text_color }};{% endif %}
+           {% if banner.background_color %}background-color: {{ banner.background_color }};{% endif %}
+
+        ">
+    <div class="annoy-banner-inner">
+
+      <div class="image-box">
+       {% if banner.image %}
+       <img src="{{ banner.image.url }}">
+       {% endif %}
+      </div>
+
+      <div class="text-box">
+       <div class="text">
+          {{ banner.get_text|safe|linebreaks }}
+       </div>
+
+       <div class="state-box">
+         <div class="progress-box">
+           <div>
+             <div class="l-checkout__support__bar">
+               <span data-label="{{ banner.progress_percent|floatformat:'0' }}%" style="width: {% localize off %}{{ banner.progress_percent }}%{% endlocalize %};"></span>
+             </div>
+           </div>
+         </div>
+         <div class="time-box">
+           <strong class="countdown inline" data-until='{{ banner.until|date_to_utc:True|utc_for_js }}'></strong>
+         </div>
+         <div class="action-box">
+           {% if banner.action_label %}
+            <a class="action" href="{{ banner.url }}">
+              {{ banner.action_label }}
+            </a>
+           {% endif %}
+         </div>
+       </div>
+      </div>
+
+
+
+    </div>
+  </div>
+  </div>
+
+{% endif %}
+
index 2bc9314..40f7511 100644 (file)
@@ -29,3 +29,12 @@ def annoy_banners(context, place):
         'banners': Banner.choice(place, request=context['request']),
         'closable': PLACES.get(place, False),
     }
+
+
+@register.inclusion_tag('annoy/banner_crisis.html', takes_context=True)
+def annoy_banner_crisis(context):
+    banners = Banner.choice('crisis', request=context['request'], exemptions=False)
+    return {
+        'banner': banners.first(),
+        'closable': True,
+    }
index 52256fe..cede1b4 100644 (file)
@@ -14,6 +14,8 @@
 
 
   <main class="l-main">
+    {% comment %}
+    <!-- TODO: hide when crisis banner on -->
     <div class="l-checkout__support">
       <div class="l-checkout__support__bar">
         <span data-label="{% club_count_recurring as c %}{% blocktrans %}Jest nas {{ c }}{% endblocktrans %}" style="width: calc({{ c }}% / 5);"></span>
@@ -23,6 +25,7 @@
         <p>{% blocktrans with c=500 %}Potrzebujemy <strong>{{ c }}</strong> regularnych darczyńców, by Wolne Lektury mogły działać!{% endblocktrans %}</p>
       </div>
     </div>
+    {% endcomment %}
 
     <div class="l-checkout__box">
       <div class="l-checkout__box__header">
index f0f0636..2415250 100644 (file)
         }
     }
 }
+
+
+.annoy-banner_crisis-container {
+    position: sticky;
+    top: 0;
+    height: 160px;
+    z-index: 10;
+    box-shadow: 0 0 10px black;
+    display: flex;
+    background: #c32721;
+    color: black;
+    align-items:center;
+    cursor: pointer;
+
+    @media screen and (min-height: 480px) {
+       height: 33vh;
+       top: calc(-33vh + 160px);
+    }
+
+    @media screen and (max-width: 940px) {
+       padding: 10px 0;
+       height: auto;
+       top: 0;
+    }
+
+    .annoy-banner_crisis {
+       position: sticky;
+       top: 0;
+       width: 100%;
+
+       .annoy-banner-inner {
+           max-width: 1172px;
+           margin: auto;
+           padding-right: 16px;
+           padding-left: 16px;
+
+           display: flex;
+           gap: 20px;
+           align-items: flex-start;
+
+           .image-box {
+               position: relative;
+               img {
+                   height: 160px;
+                   display: block;
+
+                   @media screen and (max-width: 700px) {
+                       height: 120px;
+                   }
+               }
+           }
+
+           .text-box {
+               flex-grow: 1;
+               display: flex;
+               flex-direction: column;
+               gap: 10px;
+
+               @media screen and (max-width: 700px) {
+                   p {
+                       font-size: .9em;
+                   }
+               }
+
+               .text {
+                   background: #edc016;
+                   padding: 1em;
+                   border: 3px solid black;
+               }
+               a {
+                   color: #c32721;
+               }
+               .state-box {
+                   display: flex;
+                   gap: 10px;
+                   align-items: center;
+                   @media screen and (max-width: 700px) {
+                       flex-direction: column;
+                       align-items: stretch;
+                       text-align: center;
+                   }
+                   .progress-box {
+                       flex-grow: 1;
+
+                       .l-checkout__support__bar span::after {
+                           right: auto;
+                           left: 5px;
+                           color: black;
+                       }
+                   }
+               }
+           }
+
+           p {
+               margin: 0;
+           }
+
+           a.action {
+               background: #edc016;
+               color: black;
+               padding: .8em 1em;
+               border: 3px solid black;
+               border-radius: 10px;
+               display: block;
+               transition: background-color .2s;
+
+               &:hover {
+                   background: #ffd430;
+                   text-decoration: none;
+               }
+           }
+       }
+    }
+}
index acc38a9..efa442e 100644 (file)
         $(".c-media__settings").toggleClass('active');
     });
 
+    const crisis = document.querySelector(".annoy-banner_crisis-container");
+    const crisisLink = document.querySelector('.annoy-banner_crisis-container a.action');
+    crisis.addEventListener("click", function() {
+       crisisLink.click();
+    });
+
 })();
index e7a29b8..17e2379 100644 (file)
@@ -6,6 +6,8 @@
 {% load latest_blog_posts from blog %}
 {% load preview_ad from catalogue_tags %}
 
+{% annoy_banner_crisis %}
+
 {% annoy_banner_blackout %}
 
 <nav class="l-navigation">