Bigger unread badge with a real count, and a Clear all action for notifications
The header bell's unread indicator was an 8px dot -- now a proper count badge (capped at "9+"), matching the size/legibility of similar badges elsewhere in the app. Notifications also gains "Clear all" next to "Mark all read" -- a hard delete of the list (the same clear-all gesture a phone's own notification centre uses), not another read-state flip. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
This commit is contained in:
@@ -67,7 +67,11 @@
|
||||
<div class="flex-1"></div>
|
||||
<a class="relative flex h-11 w-11 shrink-0 items-center justify-center" href="{% url "mobile:notifications" %}" aria-label="{% trans "Notifications" %}">
|
||||
<svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
|
||||
{% if unread_notification_count %}<span class="absolute top-1.5 right-1.5 h-2 w-2 rounded-full bg-club" style="border: 2px solid var(--color-navy)"></span>{% endif %}
|
||||
{% if unread_notification_count %}
|
||||
<span class="absolute -top-1 -right-1 flex h-[19px] min-w-[19px] items-center justify-center rounded-full bg-club px-1 font-mono text-[11px] font-bold text-white" style="border: 2px solid var(--color-navy)">
|
||||
{% if unread_notification_count > 9 %}9+{% else %}{{ unread_notification_count }}{% endif %}
|
||||
</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
{% block header_extra %}{% endblock header_extra %}
|
||||
|
||||
@@ -25,12 +25,23 @@
|
||||
<button type="button" class="btn btn-dark shrink-0" @click="window.rosterchiefPush.subscribe(); requested = true">{% trans "Enable" %}</button>
|
||||
</div>
|
||||
|
||||
{% if unread_notification_count %}
|
||||
<form method="post" action="{% url "mobile:notifications" %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="mark_all_read">
|
||||
<button class="font-display text-xs font-extrabold tracking-wide text-club uppercase" type="submit">{% trans "Mark all read" %}</button>
|
||||
</form>
|
||||
{% if today or earlier_this_week or older %}
|
||||
<div class="flex items-center justify-between">
|
||||
{% if unread_notification_count %}
|
||||
<form method="post" action="{% url "mobile:notifications" %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="mark_all_read">
|
||||
<button class="font-display text-xs font-extrabold tracking-wide text-club uppercase" type="submit">{% trans "Mark all read" %}</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<span></span>
|
||||
{% endif %}
|
||||
<form method="post" action="{% url "mobile:notifications" %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="clear_all">
|
||||
<button class="font-display text-xs font-extrabold tracking-wide text-muted uppercase" type="submit">{% trans "Clear all" %}</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if not today and not earlier_this_week and not older %}
|
||||
|
||||
@@ -910,6 +910,19 @@ class NotificationsViewTests(TestCase):
|
||||
follow_up = self._get()
|
||||
self.assertEqual(follow_up.context["unread_notification_count"], 0)
|
||||
|
||||
def test_clear_all_deletes_every_notification_for_every_managed_person(self):
|
||||
Notification.objects.create(club=self.club, member=self.member, title="First", body="Body.")
|
||||
Notification.objects.create(club=self.club, member=self.child, title="Second", body="Body.")
|
||||
stranger = Member.objects.create(first_name="Someone", last_name="Else")
|
||||
untouched = Notification.objects.create(club=self.club, member=stranger, title="Not yours", body="Body.")
|
||||
self.client.force_login(self.user)
|
||||
|
||||
response = self._post({"action": "clear_all"})
|
||||
|
||||
self.assertRedirects(response, reverse("mobile:notifications"), fetch_redirect_response=False)
|
||||
self.assertEqual(Notification.objects.filter(member__in=[self.member, self.child]).count(), 0)
|
||||
self.assertTrue(Notification.objects.filter(pk=untouched.pk).exists())
|
||||
|
||||
def test_mark_read_marks_a_single_notification_and_redirects_back(self):
|
||||
notification = Notification.objects.create(club=self.club, member=self.member, title="First", body="Body.")
|
||||
self.client.force_login(self.user)
|
||||
|
||||
@@ -755,6 +755,13 @@ class NotificationsView(PersonScopeMixin, LoginRequiredMixin, TemplateView):
|
||||
Notification.objects.filter(club=request.club, member__in=self.managed_people, read_at__isnull=True).update(read_at=timezone.now())
|
||||
return HttpResponseRedirect(reverse("mobile:notifications"))
|
||||
|
||||
if action == "clear_all":
|
||||
# A hard delete, not another read-state flip -- "Clear" empties the
|
||||
# list itself, same as the clear-all gesture in a phone's own
|
||||
# notification centre, rather than just marking everything read.
|
||||
Notification.objects.filter(club=request.club, member__in=self.managed_people).delete()
|
||||
return HttpResponseRedirect(reverse("mobile:notifications"))
|
||||
|
||||
if action == "mark_read":
|
||||
notification = Notification.objects.filter(pk=request.POST.get("notification_id"), club=request.club, member__in=self.managed_people).first()
|
||||
if notification is None:
|
||||
|
||||
@@ -3944,6 +3944,9 @@
|
||||
.h-\[7px\] {
|
||||
height: 7px;
|
||||
}
|
||||
.h-\[19px\] {
|
||||
height: 19px;
|
||||
}
|
||||
.h-\[22px\] {
|
||||
height: 22px;
|
||||
}
|
||||
@@ -4169,6 +4172,9 @@
|
||||
.min-w-40 {
|
||||
min-width: calc(var(--spacing) * 40);
|
||||
}
|
||||
.min-w-\[19px\] {
|
||||
min-width: 19px;
|
||||
}
|
||||
.min-w-\[220px\] {
|
||||
min-width: 220px;
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user