From 20eea61eb58ef193365977a8806fd3e8ae2d3a48 Mon Sep 17 00:00:00 2001 From: octavioamu Date: Tue, 27 Nov 2018 21:10:21 -0200 Subject: [PATCH 01/41] add inbox app --- app/inbox/__init__.py | 0 app/inbox/admin.py | 3 +++ app/inbox/apps.py | 5 +++++ app/inbox/migrations/__init__.py | 0 app/inbox/models.py | 3 +++ app/inbox/tests.py | 3 +++ app/inbox/views.py | 3 +++ 7 files changed, 17 insertions(+) create mode 100644 app/inbox/__init__.py create mode 100644 app/inbox/admin.py create mode 100644 app/inbox/apps.py create mode 100644 app/inbox/migrations/__init__.py create mode 100644 app/inbox/models.py create mode 100644 app/inbox/tests.py create mode 100644 app/inbox/views.py diff --git a/app/inbox/__init__.py b/app/inbox/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/inbox/admin.py b/app/inbox/admin.py new file mode 100644 index 00000000000..8c38f3f3dad --- /dev/null +++ b/app/inbox/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/app/inbox/apps.py b/app/inbox/apps.py new file mode 100644 index 00000000000..9c260e0b160 --- /dev/null +++ b/app/inbox/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class NotificationsConfig(AppConfig): + name = 'notifications' diff --git a/app/inbox/migrations/__init__.py b/app/inbox/migrations/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/inbox/models.py b/app/inbox/models.py new file mode 100644 index 00000000000..71a83623907 --- /dev/null +++ b/app/inbox/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/app/inbox/tests.py b/app/inbox/tests.py new file mode 100644 index 00000000000..7ce503c2dd9 --- /dev/null +++ b/app/inbox/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/app/inbox/views.py b/app/inbox/views.py new file mode 100644 index 00000000000..91ea44a218f --- /dev/null +++ b/app/inbox/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. From 287b3c49e9b3dad0284c3de3ab9b085e6c9800ea Mon Sep 17 00:00:00 2001 From: octavioamu Date: Wed, 28 Nov 2018 19:41:54 -0200 Subject: [PATCH 02/41] add initial markup --- app/app/settings.py | 2 +- app/assets/v2/css/gitcoin.css | 11 +---- app/assets/v2/css/notifications.css | 45 +++++++++++++++++++ app/dashboard/templates/shared/nav_auth.html | 4 +- .../templates/shared/notifications.html | 36 +++++++++++++++ app/inbox/apps.py | 4 +- app/retail/templates/shared/head.html | 1 + app/retail/templates/shared/nav.html | 1 + 8 files changed, 88 insertions(+), 16 deletions(-) create mode 100644 app/assets/v2/css/notifications.css create mode 100644 app/dashboard/templates/shared/notifications.html diff --git a/app/app/settings.py b/app/app/settings.py index 07f5e2259db..3076fad66fe 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -138,7 +138,7 @@ TEMPLATES = [{ 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': ['retail/templates/', 'external_bounties/templates/', 'dataviz/templates', 'kudos/templates'], + 'DIRS': ['retail/templates/', 'external_bounties/templates/', 'dataviz/templates', 'kudos/templates', 'inbox/templates'], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ diff --git a/app/assets/v2/css/gitcoin.css b/app/assets/v2/css/gitcoin.css index 37f7422293b..f91de4915b7 100644 --- a/app/assets/v2/css/gitcoin.css +++ b/app/assets/v2/css/gitcoin.css @@ -343,7 +343,7 @@ div.button-pink { text-decoration: none; padding-right: 1rem; padding-left: 1rem; - font-size: 0.9rem !important; + font-size: 0.9rem; } .footer .nav-link:hover { @@ -620,12 +620,3 @@ div.button-pink { } } -.notification-dot { - background-color: #ef3427; - width: 0.5rem; - height: 0.5rem; - position: absolute; - top: 6px; - border-radius: 30px; - left: 6px; -} diff --git a/app/assets/v2/css/notifications.css b/app/assets/v2/css/notifications.css new file mode 100644 index 00000000000..95c1680d9ea --- /dev/null +++ b/app/assets/v2/css/notifications.css @@ -0,0 +1,45 @@ +:root { + --color-notification: 37, 232, 153; + +} + + +.navbar-nav .nav-link.notifications { + position: relative; + font-size: 18px; + color: white; +} + +.dropdown-toggle::after { + content: none; +} + +.notification-dot { + background: rgba(var(--color-notification), 1); + display: block; + width: 9px; + height: 9px; + border-radius: 50%; + box-shadow: 0 0 0 rgba(var(--color-notification), .4); + animation: BadgePulse 1.8s infinite; + position: absolute; + top: 0.5em; + right: 0.9em; +} + +@keyframes BadgePulse{ + 0%{ + -moz-box-shadow:0 0 0 0 rgba(var(--color-notification), .4); + box-shadow:0 0 0 0 rgba(var(--color-notification), .4) + } + + 70%{ + -moz-box-shadow:0 0 0 10px rgba(var(--color-notification), 0); + box-shadow:0 0 0 10px rgba(var(--color-notification), 0) + } + + 100%{ + -moz-box-shadow:0 0 0 20px rgba(var(--color-notification), 0); + box-shadow:0 0 0 20px rgba(var(--color-notification), 0) + } +} diff --git a/app/dashboard/templates/shared/nav_auth.html b/app/dashboard/templates/shared/nav_auth.html index 4bcd8acce9f..e928d96e901 100644 --- a/app/dashboard/templates/shared/nav_auth.html +++ b/app/dashboard/templates/shared/nav_auth.html @@ -21,9 +21,6 @@ + {% include 'shared/notifications.html' %} {% if request.path|matches:"^\/((explorer)|(issue.*)|(.*new.*))$" %} From 97b2a720eb3a8178bf4f9ce4806a9cad089ee913 Mon Sep 17 00:00:00 2001 From: octavioamu Date: Thu, 29 Nov 2018 01:40:20 -0200 Subject: [PATCH 03/41] improve dropdown --- app/assets/v2/css/gitcoin.css | 23 ++++++++- app/assets/v2/css/notifications.css | 48 +++++++++++++++++-- app/assets/v2/js/base.js | 2 +- app/dashboard/templates/shared/nav_auth.html | 2 +- .../templates/shared/notifications.html | 40 +++++++++------- app/retail/templates/shared/nav.html | 4 +- 6 files changed, 93 insertions(+), 26 deletions(-) diff --git a/app/assets/v2/css/gitcoin.css b/app/assets/v2/css/gitcoin.css index f91de4915b7..7e4430e1c2c 100644 --- a/app/assets/v2/css/gitcoin.css +++ b/app/assets/v2/css/gitcoin.css @@ -486,8 +486,6 @@ div.button-pink { background-color: #FAFAFA; border-radius: 0px; padding: 0; - right: 0; - left: auto; } .dropdown-menu .dropdown-divider { @@ -620,3 +618,24 @@ div.button-pink { } } +/* dropdown animation */ +.animation { + -webkit-animation-duration: 80ms; + animation-duration: 80ms; + -webkit-animation-fill-mode: forwards; + animation-fill-mode: forwards; +} +.slideDownIn { + -webkit-animation-name: slideDownIn; + animation-name: slideDownIn; +} + +@keyframes slideDownIn { + 0% { + -webkit-transform: translateY(-20px) scale(0.9, 0.7); + } + 50% { + -webkit-transform: translateY(0) scale(1, 1); + } + +} diff --git a/app/assets/v2/css/notifications.css b/app/assets/v2/css/notifications.css index 95c1680d9ea..ec9132db382 100644 --- a/app/assets/v2/css/notifications.css +++ b/app/assets/v2/css/notifications.css @@ -1,20 +1,22 @@ :root { --color-notification: 37, 232, 153; + --badge-bg: #D6FBEB; + --badge-text: #00A55E; } -.navbar-nav .nav-link.notifications { +.navbar-nav .nav-link.notification__icon { position: relative; font-size: 18px; color: white; } -.dropdown-toggle::after { +.notification__icon.dropdown-toggle::after { content: none; } -.notification-dot { +.notification__dot { background: rgba(var(--color-notification), 1); display: block; width: 9px; @@ -27,6 +29,45 @@ right: 0.9em; } +.badge--greenlight { + background-color: var(--badge-bg); + color: var(--badge-text); +} + +.notifications__box { + width: 430px; + border-radius: 3px; + padding: 1em; + max-width: calc(100vw - 2em); + will-change: transform; +} + +.notifications__header { + display: flex; + justify-content: space-between; +} + +.notifications__list { + max-height: 328px; + overflow-y: auto; + padding: 0; +} + +.notifications__item { + display: flex; + align-items: center; + border-bottom: 1px solid #D8D8D8; +} + +.notifications__item-readed { + background: rgba(var(--color-notification), 1); + display: block; + width: 9px; + height: 9px; + border-radius: 50%; + padding: 0.3em; +} + @keyframes BadgePulse{ 0%{ -moz-box-shadow:0 0 0 0 rgba(var(--color-notification), .4); @@ -43,3 +84,4 @@ box-shadow:0 0 0 20px rgba(var(--color-notification), 0) } } + diff --git a/app/assets/v2/js/base.js b/app/assets/v2/js/base.js index e280402215d..2703ec5e8d6 100644 --- a/app/assets/v2/js/base.js +++ b/app/assets/v2/js/base.js @@ -43,7 +43,7 @@ $(document).ready(function() { var parentSiblings = parent.siblings('.nav-item'); - parent.find('.dropdown-menu').toggle(); + parent.find('.dropdown-menu').toggle().toggleClass('show'); parentSiblings.find('.dropdown-menu').hide(); }); diff --git a/app/dashboard/templates/shared/nav_auth.html b/app/dashboard/templates/shared/nav_auth.html index e928d96e901..7cbe5bce358 100644 --- a/app/dashboard/templates/shared/nav_auth.html +++ b/app/dashboard/templates/shared/nav_auth.html @@ -23,7 +23,7 @@ data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> {{ user }} - From e47ac1da68ca982b5a9077579505075f64cc8ea1 Mon Sep 17 00:00:00 2001 From: Muhammad Usman Date: Sun, 9 Dec 2018 17:10:56 +0530 Subject: [PATCH 37/41] Handled json execptions --- app/inbox/views.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/app/inbox/views.py b/app/inbox/views.py index d488d7f6fd0..64bb20ae5b0 100644 --- a/app/inbox/views.py +++ b/app/inbox/views.py @@ -38,7 +38,10 @@ def delete_notifications(request): if request.method == 'DELETE' and profile is not None: params = dict() params['success'] = [] - req_body = json.loads(request.body.decode('utf-8')) + try: + req_body = json.loads(request.body.decode('utf-8')) + except: + pass if 'delete' in req_body: for i in req_body['delete']: entry = Notification.objects.filter(id=i) @@ -59,7 +62,10 @@ def unread_notifications(request): if request.method == 'PUT' and profile is not None: params = dict() params['success'] = [] - req_body = json.loads(request.body.decode('utf-8')) + try: + req_body = json.loads(request.body.decode('utf-8')) + except: + pass if 'unread' in req_body: for i in req_body['unread']: entry = Notification.objects.filter(id=i) @@ -82,7 +88,10 @@ def read_notifications(request): if request.method == 'PUT' and profile is not None: params = dict() params['success'] = [] - req_body = json.loads(request.body.decode('utf-8')) + try: + req_body = json.loads(request.body.decode('utf-8')) + except: + pass if 'read' in req_body: for i in req_body['read']: entry = Notification.objects.filter(id=i) From 15064e802b2ec26c5dafd519b225b76388f39c03 Mon Sep 17 00:00:00 2001 From: Muhammad Usman Date: Sun, 9 Dec 2018 17:33:34 +0530 Subject: [PATCH 38/41] Added pagination for notifications --- app/inbox/views.py | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/app/inbox/views.py b/app/inbox/views.py index 64bb20ae5b0..9a2df0acc21 100644 --- a/app/inbox/views.py +++ b/app/inbox/views.py @@ -1,12 +1,13 @@ +import json +from inbox.models import Notification from django.contrib.staticfiles.templatetags.staticfiles import static from django.shortcuts import render from django.template.response import TemplateResponse from django.utils import timezone from django.utils.translation import gettext_lazy as _ -from inbox.models import Notification from django.contrib.auth import get_user_model -import json from django.http import JsonResponse, HttpResponseForbidden +from django.core.paginator import Paginator def notifications(request): @@ -15,17 +16,29 @@ def notifications(request): profile = request.user.profile if request.user.is_authenticated and request.user.profile else None if profile is None: return HttpResponseForbidden('Not Allowed') - + limit = 10 + page = 1 + if 'limit' in request.GET: + limit = int(request.GET['limit']) + if 'page' in request.GET: + page = int(request.GET['page']) + print(limit, page) all_notifs = Notification.objects.filter(to_user_id=request.user.id).order_by('id')[::-1] - params = [] - for i in all_notifs: - new_notif = i.to_standard_dict() - new_notif['created_on'] = i.created_on - new_notif['modified_on'] = i.modified_on - new_notif['username'] = get_user_model().objects.get(id=new_notif['from_user_id']).username - - params.append(new_notif) - + params = dict() + all_pages = Paginator(all_notifs, limit) + if page > 0 and page <= all_pages.num_pages: + all_notifications = [] + for i in all_pages.page(page): + new_notif = i.to_standard_dict() + new_notif['created_on'] = i.created_on + new_notif['modified_on'] = i.modified_on + new_notif['username'] = get_user_model().objects.get(id=new_notif['from_user_id']).username + + all_notifications.append(new_notif) + params['data'] = all_notifications + params['has_next'] = all_pages.page(page).has_next() + else: + params['total_pages'] = all_pages.num_pages return JsonResponse(params, status=200, safe=False) else: From 5d25b337b3e4a1e9a7dea21b94e7df8c73e973d0 Mon Sep 17 00:00:00 2001 From: Muhammad Usman Date: Mon, 10 Dec 2018 07:35:10 +0530 Subject: [PATCH 39/41] Fix AttributeError in inbox.views --- app/inbox/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/inbox/views.py b/app/inbox/views.py index 9a2df0acc21..26eb826fbd9 100644 --- a/app/inbox/views.py +++ b/app/inbox/views.py @@ -58,7 +58,7 @@ def delete_notifications(request): if 'delete' in req_body: for i in req_body['delete']: entry = Notification.objects.filter(id=i) - if entry.to_user_id == request.user.id and len(entry) != 0: + if entry.to_user_id.id == request.user.id and len(entry) != 0: entry.delete() params['success'].append(True) else: @@ -82,7 +82,7 @@ def unread_notifications(request): if 'unread' in req_body: for i in req_body['unread']: entry = Notification.objects.filter(id=i) - if entry.to_user_id == request.user.id and len(entry) != 0: + if entry.to_user_id.id == request.user.id and len(entry) != 0: obj = entry[0] obj.is_read = False obj.save() @@ -108,7 +108,7 @@ def read_notifications(request): if 'read' in req_body: for i in req_body['read']: entry = Notification.objects.filter(id=i) - if entry.to_user_id == request.user.id and len(entry) != 0: + if entry.to_user_id.id == request.user.id and len(entry) != 0: obj = entry[0] obj.is_read = True obj.save() From 80acc0ee213c205dbd01c8257094da86b569e56c Mon Sep 17 00:00:00 2001 From: octavioamu Date: Mon, 10 Dec 2018 00:31:34 -0200 Subject: [PATCH 40/41] change add read --- app/assets/v2/js/notifications.js | 57 +++++++++++++++++++++++----- app/inbox/migrations/0001_initial.py | 2 +- app/inbox/views.py | 4 ++ 3 files changed, 52 insertions(+), 11 deletions(-) diff --git a/app/assets/v2/js/notifications.js b/app/assets/v2/js/notifications.js index de83d48470d..96d84d4e68f 100644 --- a/app/assets/v2/js/notifications.js +++ b/app/assets/v2/js/notifications.js @@ -1,16 +1,23 @@ var notifications = []; var newNotifications = []; var isHidden = false; +var page = 1; const container = $('.notifications__list'); function requestNotifications() { - var getNotifications = fetchData ('/api/v0.1/notifications/', 'GET'); + console.log(page) + var getNotifications = fetchData (`/api/v0.1/notifications/?page=${page}`, 'GET'); $.when(getNotifications).then(function(response) { - var loadTmp = true; + // var loadTmp = response.data.length && response.data[0].id !== notifications[0].id; + var loadTmp = response.data.length !== notifications.length + // console.log(response.data[0].id) + // if (response.data.length !== notifications.length) { + if (loadTmp) { + newNotifications = newData(response.data, notifications); - if (response.length !== notifications.length) { - newNotifications = newData(response, notifications); + page = response.has_next ? page+1 : page = 1 + console.log(page) newNotifications.forEach(element => { notifications.push(element); @@ -43,7 +50,11 @@ function templateSuggestions(notifications) { `).join(' ')}`; - container.prepend(tmp); + // if (newItems) { + container.prepend(tmp); + // } else { + // container.append(tmp); + // } $('.notifications__item'); } @@ -95,26 +106,32 @@ function setDot(hasNewData, newNotifications) { } } -function markAsRead(notificationsA) { - console.log(notificationsA) +function markAsRead(notificationToRead) { + console.log(notificationToRead) var notificationRead = parseInt(sessionStorage.getItem('notificationRead')) if (notificationRead) { console.log('ping') sessionStorage.removeItem('notificationRead'); console.log('api request readed', notificationRead); + let data = JSON.stringify({'read':[notificationRead]}); + var putRead = fetchData (`api/v0.1/notifications/read/`, 'PUT', data); + $.when(putRead).then(function(response) { + console.log(response) + }) + // notificationRead = parseInt(notificationRead) - const resulta = notificationsA.findIndex(item => { + const index = notificationToRead.findIndex(item => { return item.id === notificationRead }) - notificationsA[resulta].is_read = true; + notificationToRead[index].is_read = true; } } requestNotifications(); -var intervalNotifications = window.setInterval(requestNotifications, 5000); +var intervalNotifications = window.setInterval(requestNotifications, 10000); $(document).ready(function() { @@ -124,3 +141,23 @@ $(document).ready(function() { }) +$('.notifications__list').scroll( function() { + console.log($(this)[0]) + const scrollContainer = $(this)[0] + if (scrollContainer.scrollTop + scrollContainer.clientHeight >= scrollContainer.scrollHeight) { + requestNotifications(page); + } +}); + + + +var array = []; +var observedArray = new Proxy(notifications, { + set: function (target, propertyKey, value, receiver) { + console.log(propertyKey+'='+value); + console.log(target,propertyKey, value ) + target[propertyKey] = value; + return true + } +}); + diff --git a/app/inbox/migrations/0001_initial.py b/app/inbox/migrations/0001_initial.py index f84ad578bb5..0c88b39331a 100644 --- a/app/inbox/migrations/0001_initial.py +++ b/app/inbox/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.1.2 on 2018-12-08 12:52 +# Generated by Django 2.1.2 on 2018-12-10 02:25 from django.conf import settings from django.db import migrations, models diff --git a/app/inbox/views.py b/app/inbox/views.py index 26eb826fbd9..8e0b327c9e0 100644 --- a/app/inbox/views.py +++ b/app/inbox/views.py @@ -8,6 +8,7 @@ from django.contrib.auth import get_user_model from django.http import JsonResponse, HttpResponseForbidden from django.core.paginator import Paginator +from django.views.decorators.csrf import csrf_exempt def notifications(request): @@ -45,6 +46,7 @@ def notifications(request): return HttpResponseForbidden('Not Allowed') +@csrf_exempt def delete_notifications(request): """For deleting a notification""" profile = request.user.profile if request.user.is_authenticated and request.user.profile else None @@ -69,6 +71,7 @@ def delete_notifications(request): return HttpResponseForbidden('Not Allowed') +@csrf_exempt def unread_notifications(request): """Mark a notification as unread""" profile = request.user.profile if request.user.is_authenticated and request.user.profile else None @@ -95,6 +98,7 @@ def unread_notifications(request): return HttpResponseForbidden('Not Allowed') +@csrf_exempt def read_notifications(request): """Mark a notification as read""" profile = request.user.profile if request.user.is_authenticated and request.user.profile else None From 1dc59cc9093cd09adbf9131edd797e03ac55efa7 Mon Sep 17 00:00:00 2001 From: Muhammad Usman Date: Mon, 10 Dec 2018 08:16:00 +0530 Subject: [PATCH 41/41] Fix in inbox.views --- app/inbox/views.py | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/app/inbox/views.py b/app/inbox/views.py index 8e0b327c9e0..2acd66cae79 100644 --- a/app/inbox/views.py +++ b/app/inbox/views.py @@ -60,9 +60,13 @@ def delete_notifications(request): if 'delete' in req_body: for i in req_body['delete']: entry = Notification.objects.filter(id=i) - if entry.to_user_id.id == request.user.id and len(entry) != 0: - entry.delete() - params['success'].append(True) + if len(entry) != 0: + obj = entry[0] + if obj.to_user_id.id == request.user.id: + obj.delete() + params['success'].append(True) + else: + params['success'].append(False) else: params['success'].append(False) return JsonResponse(params, status=200) @@ -85,11 +89,14 @@ def unread_notifications(request): if 'unread' in req_body: for i in req_body['unread']: entry = Notification.objects.filter(id=i) - if entry.to_user_id.id == request.user.id and len(entry) != 0: + if len(entry) != 0: obj = entry[0] - obj.is_read = False - obj.save() - params['success'].append(True) + if obj.to_user_id.id == request.user.id: + obj.is_read = False + obj.save() + params['success'].append(True) + else: + params['success'].append(False) else: params['success'].append(False) return JsonResponse(params, status=200) @@ -112,11 +119,15 @@ def read_notifications(request): if 'read' in req_body: for i in req_body['read']: entry = Notification.objects.filter(id=i) - if entry.to_user_id.id == request.user.id and len(entry) != 0: + #if entry.to_user_id.id == request.user.id and len(entry) != 0: + if len(entry) != 0: obj = entry[0] - obj.is_read = True - obj.save() - params['success'].append(True) + if obj.to_user_id.id == request.user.id: + obj.is_read = True + obj.save() + params['success'].append(True) + else: + params['success'].append(False) else: params['success'].append(False) return JsonResponse(params, status=200)