diff --git a/test/api/v3/integration/tasks/groups/GET-approvals_group_id.test.js b/test/api/v3/integration/tasks/groups/GET-approvals_group_id.test.js index cf5935e443..eeeec85b37 100644 --- a/test/api/v3/integration/tasks/groups/GET-approvals_group_id.test.js +++ b/test/api/v3/integration/tasks/groups/GET-approvals_group_id.test.js @@ -34,7 +34,12 @@ describe('GET /approvals/group/:groupId', () => { let memberTasks = await member.get('/tasks/user'); syncedTask = find(memberTasks, findAssignedTask); - await member.post(`/tasks/${syncedTask._id}/score/up`); + + try { + await member.post(`/tasks/${syncedTask._id}/score/up`); + } catch (e) { + // eslint-disable-next-line no-empty + } }); it('errors when user is not the group leader', async () => { diff --git a/test/api/v3/integration/tasks/groups/POST-group_tasks_id_approve_userId.test.js b/test/api/v3/integration/tasks/groups/POST-group_tasks_id_approve_userId.test.js index 0fa9807e07..d5d81cd4c7 100644 --- a/test/api/v3/integration/tasks/groups/POST-group_tasks_id_approve_userId.test.js +++ b/test/api/v3/integration/tasks/groups/POST-group_tasks_id_approve_userId.test.js @@ -60,7 +60,7 @@ describe('POST /tasks/:id/approve/:userId', () => { await member.sync(); expect(member.notifications.length).to.equal(1); - expect(member.notifications[0].type).to.equal('GROUP_TASK_APPROVAL'); + expect(member.notifications[0].type).to.equal('GROUP_TASK_APPROVED'); expect(member.notifications[0].data.message).to.equal(t('yourTaskHasBeenApproved')); expect(syncedTask.group.approval.approved).to.be.true; diff --git a/test/api/v3/integration/tasks/groups/POST-group_tasks_id_score_direction.test.js b/test/api/v3/integration/tasks/groups/POST-group_tasks_id_score_direction.test.js index 8a42ae9f75..192808ca8b 100644 --- a/test/api/v3/integration/tasks/groups/POST-group_tasks_id_score_direction.test.js +++ b/test/api/v3/integration/tasks/groups/POST-group_tasks_id_score_direction.test.js @@ -37,7 +37,12 @@ describe('POST /tasks/:id/score/:direction', () => { let memberTasks = await member.get('/tasks/user'); let syncedTask = find(memberTasks, findAssignedTask); - let response = await member.post(`/tasks/${syncedTask._id}/score/up`); + await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) + .to.eventually.be.rejected.and.to.eql({ + code: 401, + error: 'NotAuthorized', + message: t('taskApprovalHasBeenRequested'), + }); let updatedTask = await member.get(`/tasks/${syncedTask._id}`); await user.sync(); @@ -48,8 +53,8 @@ describe('POST /tasks/:id/score/:direction', () => { user: member.auth.local.username, taskName: updatedTask.text, })); + expect(user.notifications[0].data.groupId).to.equal(guild._id); - expect(response.message).to.equal(t('taskApprovalHasBeenRequested')); expect(updatedTask.group.approval.requested).to.equal(true); expect(updatedTask.group.approval.requestedDate).to.be.a('string'); // date gets converted to a string as json doesn't have a Date type }); @@ -58,7 +63,12 @@ describe('POST /tasks/:id/score/:direction', () => { let memberTasks = await member.get('/tasks/user'); let syncedTask = find(memberTasks, findAssignedTask); - await member.post(`/tasks/${syncedTask._id}/score/up`); + await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) + .to.eventually.be.rejected.and.to.eql({ + code: 401, + error: 'NotAuthorized', + message: t('taskApprovalHasBeenRequested'), + }); await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) .to.eventually.be.rejected.and.eql({ diff --git a/test/api/v3/unit/models/group_tasks.test.js b/test/api/v3/unit/models/group_tasks.test.js index bb43c20d30..c0d6741fa2 100644 --- a/test/api/v3/unit/models/group_tasks.test.js +++ b/test/api/v3/unit/models/group_tasks.test.js @@ -107,6 +107,7 @@ describe('Group Task Methods', () => { let updatedTaskName = 'Update Task name'; task.text = updatedTaskName; + task.group.approval.required = true; await guild.updateTask(task); @@ -121,10 +122,12 @@ describe('Group Task Methods', () => { expect(task.group.assignedUsers).to.contain(leader._id); expect(syncedTask).to.exist; expect(syncedTask.text).to.equal(task.text); + expect(syncedTask.group.approval.required).to.equal(true); expect(task.group.assignedUsers).to.contain(newMember._id); expect(syncedMemberTask).to.exist; expect(syncedMemberTask.text).to.equal(task.text); + expect(syncedMemberTask.group.approval.required).to.equal(true); }); it('removes an assigned task and unlinks assignees', async () => { diff --git a/website/client-old/js/app.js b/website/client-old/js/app.js index f811eab17b..1de037eba1 100644 --- a/website/client-old/js/app.js +++ b/website/client-old/js/app.js @@ -178,15 +178,15 @@ window.habitrpg = angular.module('habitrpg', $scope.group.challenges = response.data.data; }); //@TODO: Add this back when group tasks go live - // return Tasks.getGroupTasks($scope.group._id); + return Tasks.getGroupTasks($scope.group._id); }) - // .then(function (response) { - // var tasks = response.data.data; - // tasks.forEach(function (element, index, array) { - // if (!$scope.group[element.type + 's']) $scope.group[element.type + 's'] = []; - // $scope.group[element.type + 's'].push(element); - // }) - // }); + .then(function (response) { + var tasks = response.data.data; + tasks.forEach(function (element, index, array) { + if (!$scope.group[element.type + 's']) $scope.group[element.type + 's'] = []; + $scope.group[element.type + 's'].push(element); + }) + }); }] }) diff --git a/website/client-old/js/components/groupApprovals/groupApprovalsController.js b/website/client-old/js/components/groupApprovals/groupApprovalsController.js new file mode 100644 index 0000000000..b51cd917ec --- /dev/null +++ b/website/client-old/js/components/groupApprovals/groupApprovalsController.js @@ -0,0 +1,21 @@ +habitrpg.controller('GroupApprovalsCtrl', ['$scope', 'Tasks', + function ($scope, Tasks) { + $scope.approvals = []; + + Tasks.getGroupApprovals($scope.group._id) + .then(function (response) { + $scope.approvals = response.data.data; + }); + + $scope.approve = function (taskId, userId, $index) { + if (!confirm(env.t('confirmTaskApproval'))) return; + Tasks.approve(taskId, userId) + .then(function (response) { + $scope.approvals.splice($index, 1); + }); + }; + + $scope.approvalTitle = function (approval) { + return env.t('approvalTitle', {text: approval.text, userName: approval.userId.profile.name}); + }; + }]); diff --git a/website/client-old/js/components/groupApprovals/groupApprovalsDirective.js b/website/client-old/js/components/groupApprovals/groupApprovalsDirective.js new file mode 100644 index 0000000000..a2b37727cb --- /dev/null +++ b/website/client-old/js/components/groupApprovals/groupApprovalsDirective.js @@ -0,0 +1,21 @@ +'use strict'; + +(function(){ + angular + .module('habitrpg') + .directive('groupApprovals', groupApprovals); + + groupApprovals.$inject = [ + ]; + + function groupApprovals() { + + return { + scope: { + group: '=', + }, + templateUrl: 'partials/groups.tasks.approvals.html', + controller: 'GroupApprovalsCtrl', + }; + } +}()); diff --git a/website/client-old/js/components/groupTaskActions/groupTaskActionsController.js b/website/client-old/js/components/groupTaskActions/groupTaskActionsController.js index 677ead8bfc..5e6427ca1a 100644 --- a/website/client-old/js/components/groupTaskActions/groupTaskActionsController.js +++ b/website/client-old/js/components/groupTaskActions/groupTaskActionsController.js @@ -2,6 +2,11 @@ habitrpg.controller('GroupTaskActionsCtrl', ['$scope', 'Shared', 'Tasks', 'User' function ($scope, Shared, Tasks, User) { $scope.assignedMembers = []; $scope.user = User.user; + + $scope.task._edit.requiresApproval = false; + if ($scope.task.group.approval.required) { + $scope.task._edit.requiresApproval = $scope.task.group.approval.required; + } $scope.$on('addedGroupMember', function(evt, userId) { Tasks.assignTask($scope.task.id, userId); diff --git a/website/client-old/js/components/groupTasks/groupTasksController.js b/website/client-old/js/components/groupTasks/groupTasksController.js index fa7eded874..5314c705c7 100644 --- a/website/client-old/js/components/groupTasks/groupTasksController.js +++ b/website/client-old/js/components/groupTasks/groupTasksController.js @@ -3,14 +3,16 @@ habitrpg.controller('GroupTasksCtrl', ['$scope', 'Shared', 'Tasks', 'User', func $scope.toggleBulk = Tasks.toggleBulk; $scope.cancelTaskEdit = Tasks.cancelTaskEdit; - function addTask (listDef, task) { - var task = Shared.taskDefaults({text: task, type: listDef.type}); - //If the group has not been created, we bulk add tasks on save - var group = $scope.obj; - if (group._id) Tasks.createGroupTasks(group._id, task); - if (!group[task.type + 's']) group[task.type + 's'] = []; - group[task.type + 's'].unshift(task); - delete listDef.newTask; + function addTask (listDef, taskTexts) { + taskTexts.forEach(function (taskText) { + var task = Shared.taskDefaults({text: taskText, type: listDef.type}); + + //If the group has not been created, we bulk add tasks on save + var group = $scope.obj; + if (group._id) Tasks.createGroupTasks(group._id, task); + if (!group[task.type + 's']) group[task.type + 's'] = []; + group[task.type + 's'].unshift(task); + }); }; $scope.addTask = function(listDef) { diff --git a/website/client-old/js/controllers/menuCtrl.js b/website/client-old/js/controllers/menuCtrl.js index 51220be532..1dd73d522f 100644 --- a/website/client-old/js/controllers/menuCtrl.js +++ b/website/client-old/js/controllers/menuCtrl.js @@ -1,15 +1,15 @@ 'use strict'; angular.module('habitrpg') - .controller('MenuCtrl', ['$scope', '$rootScope', '$http', 'Chat', 'Content', - function($scope, $rootScope, $http, Chat, Content) { + .controller('MenuCtrl', ['$scope', '$rootScope', '$http', 'Chat', 'Content', 'User', '$state', + function($scope, $rootScope, $http, Chat, Content, User, $state) { $scope.logout = function() { localStorage.clear(); window.location.href = '/logout'; }; - function selectNotificationValue(mysteryValue, invitationValue, cardValue, unallocatedValue, messageValue, noneValue) { + function selectNotificationValue(mysteryValue, invitationValue, cardValue, unallocatedValue, messageValue, noneValue, groupApprovalRequested, groupApproved) { var user = $scope.user; if (user.purchased && user.purchased.plan && user.purchased.plan.mysteryItems && user.purchased.plan.mysteryItems.length) { return mysteryValue; @@ -21,6 +21,14 @@ angular.module('habitrpg') return unallocatedValue; } else if (!(_.isEmpty(user.newMessages))) { return messageValue; + } else if (!_.isEmpty(user.groupNotifications)) { + var groupNotificationTypes = _.pluck(user.groupNotifications, 'type'); + if (groupNotificationTypes.indexOf('GROUP_TASK_APPROVAL') !== -1) { + return groupApprovalRequested; + } else if (groupNotificationTypes.indexOf('GROUP_TASK_APPROVED') !== -1) { + return groupApproved; + } + return noneValue; } else { return noneValue; } @@ -97,12 +105,28 @@ angular.module('habitrpg') 'glyphicon-envelope', 'glyphicon-plus-sign', 'glyphicon-comment', - 'glyphicon-comment inactive' + 'glyphicon-comment inactive', + 'glyphicon-question-sign', + 'glyphicon glyphicon-ok-sign' ); }; $scope.hasNoNotifications = function() { - return selectNotificationValue(false, false, false, false, false, true); - } + return selectNotificationValue(false, false, false, false, false, true, false); + }; + + $scope.viewGroupApprovalNotification = function (notification, $index) { + User.readNotification(notification.id); + User.user.groupNotifications.splice($index, 1); + $state.go("options.social.guilds.detail", {gid: notification.data.groupId}); + }; + + $scope.groupApprovalNotificationIcon = function (notification) { + if (notification.type === 'GROUP_TASK_APPROVAL') { + return 'glyphicon glyphicon-question-sign'; + } else if (notification.type === 'GROUP_TASK_APPROVED') { + return 'glyphicon glyphicon-ok-sign'; + } + }; } ]); diff --git a/website/client-old/js/controllers/notificationCtrl.js b/website/client-old/js/controllers/notificationCtrl.js index b729d0508b..7d1f9592ce 100644 --- a/website/client-old/js/controllers/notificationCtrl.js +++ b/website/client-old/js/controllers/notificationCtrl.js @@ -74,6 +74,11 @@ habitrpg.controller('NotificationCtrl', // Avoid showing the same notiication more than once var lastShownNotifications = []; + function trasnferGroupNotification(notification) { + if (!User.user.groupNotifications) User.user.groupNotifications = []; + User.user.groupNotifications.push(notification); + } + function handleUserNotifications (after) { if (!after || after.length === 0) return; @@ -123,6 +128,14 @@ habitrpg.controller('NotificationCtrl', if (notification.data.mp) Notification.mp(notification.data.mp); } break; + case 'GROUP_TASK_APPROVAL': + trasnferGroupNotification(notification); + markAsRead = false; + break; + case 'GROUP_TASK_APPROVED': + trasnferGroupNotification(notification); + markAsRead = false; + break; default: markAsRead = false; // If the notification is not implemented, skip it break; diff --git a/website/client-old/js/services/taskServices.js b/website/client-old/js/services/taskServices.js index 833ccefba7..732b161c17 100644 --- a/website/client-old/js/services/taskServices.js +++ b/website/client-old/js/services/taskServices.js @@ -106,6 +106,20 @@ angular.module('habitrpg') }); }; + function getGroupApprovals (groupId) { + return $http({ + method: 'GET', + url: '/api/v3/approvals/group/' + groupId, + }); + }; + + function approve (taskId, userId) { + return $http({ + method: 'POST', + url: '/api/v3/tasks/' + taskId + '/approve/' + userId, + }); + }; + function getTask (taskId) { return $http({ method: 'GET', @@ -367,5 +381,8 @@ angular.module('habitrpg') navigateChecklist: navigateChecklist, checklistCompletion: checklistCompletion, collapseChecklist: collapseChecklist, + + getGroupApprovals: getGroupApprovals, + approve: approve, }; }]); diff --git a/website/client-old/manifest.json b/website/client-old/manifest.json index 14c6bea92e..d0725ede30 100644 --- a/website/client-old/manifest.json +++ b/website/client-old/manifest.json @@ -107,7 +107,17 @@ "js/controllers/sortableInventoryCtrl.js", "js/controllers/tavernCtrl.js", "js/controllers/tasksCtrl.js", - "js/controllers/userCtrl.js" + "js/controllers/userCtrl.js", + + "js/components/groupTasks/groupTasksController.js", + "js/components/groupTasks/groupTasksDirective.js", + "js/components/groupTaskActions/groupTaskActionsController.js", + "js/components/groupTaskActions/groupTaskActionsDirective.js", + "js/components/groupMembersAutocomplete/groupMembersAutocompleteDirective.js", + "js/components/groupTaskMetaActions/groupTaskMetaActionsDirective.js", + "js/components/groupTaskMetaActions/groupTaskMetaActionsController.js", + "js/components/groupApprovals/groupApprovalsDirective.js", + "js/components/groupApprovals/groupApprovalsController.js" ], "css": [ "bower_components/bootstrap/dist/css/bootstrap.css", @@ -118,7 +128,11 @@ "bower_components/pnotify/jquery.pnotify.default.icons.css", "css/habitrpg-shared.css", "bower_components/bootstrap-tour/build/css/bootstrap-tour.css", - "fontello/css/fontelico.css" + "fontello/css/fontelico.css", + + "bower_components/jquery-ui/themes/base/minified/jquery.ui.autocomplete.min.css", + "bower_components/jquery-ui/themes/base/minified/jquery.ui.menu.min.css", + "bower_components/jquery-ui/themes/ui-lightness/jquery-ui.min.css" ] }, "static": { diff --git a/website/common/locales/en/groups.json b/website/common/locales/en/groups.json index d37de9587a..5670122c20 100644 --- a/website/common/locales/en/groups.json +++ b/website/common/locales/en/groups.json @@ -218,5 +218,8 @@ "claim": "Claim", "onlyGroupLeaderCanManageSubscription": "Only the group leader can manage the group's subscription", "yourTaskHasBeenApproved": "Your task has been approved", - "userHasRequestedTaskApproval": "<%= user %> has requested task approval for <%= taskName %>" + "userHasRequestedTaskApproval": "<%= user %> has requested task approval for <%= taskName %>", + "confirmTaskApproval": "Are you sure you want to approve this task?", + "approve": "Approve", + "approvalTitle": "<%= text %> for user: <%= userName %>" } diff --git a/website/common/locales/en/tasks.json b/website/common/locales/en/tasks.json index be0578c984..0793668e95 100644 --- a/website/common/locales/en/tasks.json +++ b/website/common/locales/en/tasks.json @@ -136,5 +136,7 @@ "perceptionExample": "Relating to work or financial tasks", "constitutionExample": "Relating to health, wellness, and social interaction", "taskRequiresApproval": "This task must be approved before you can complete it. Approval has already been requested", - "taskApprovalHasBeenRequested": "Approval has been requested" + "taskApprovalHasBeenRequested": "Approval has been requested", + "approvals": "Approvals", + "approvalRequired": "Approval Required" } diff --git a/website/server/controllers/api-v3/tasks.js b/website/server/controllers/api-v3/tasks.js index 4e4ea6acf9..4b4c62adbf 100644 --- a/website/server/controllers/api-v3/tasks.js +++ b/website/server/controllers/api-v3/tasks.js @@ -263,6 +263,10 @@ api.updateTask = { // repeat is always among modifiedPaths because mongoose changes the other of the keys when using .toObject() // see https://github.com/Automattic/mongoose/issues/2749 + if (sanitizedObj.requiresApproval) { + task.group.approval.required = true; + } + let savedTask = await task.save(); if (group && task.group.id && task.group.assignedUsers.length > 0) { @@ -331,11 +335,12 @@ api.scoreTask = { user: user.profile.name, taskName: task.text, }), + groupId: group._id, }); await Bluebird.all([groupLeader.save(), task.save()]); - return res.respond(200, {message: res.t('taskApprovalHasBeenRequested'), task}); + throw new NotAuthorized(res.t('taskApprovalHasBeenRequested')); } let wasCompleted = task.completed; diff --git a/website/server/controllers/api-v3/tasks/groups.js b/website/server/controllers/api-v3/tasks/groups.js index db522e86b1..adca492d01 100644 --- a/website/server/controllers/api-v3/tasks/groups.js +++ b/website/server/controllers/api-v3/tasks/groups.js @@ -225,7 +225,10 @@ api.approveTask = { task.group.approval.approvingUser = user._id; task.group.approval.approved = true; - assignedUser.addNotification('GROUP_TASK_APPROVAL', {message: res.t('yourTaskHasBeenApproved')}); + assignedUser.addNotification('GROUP_TASK_APPROVED', { + message: res.t('yourTaskHasBeenApproved'), + groupId: group._id, + }); await Bluebird.all([assignedUser.save(), task.save()]); @@ -266,7 +269,9 @@ api.getGroupApprovals = { 'group.id': groupId, 'group.approval.approved': false, 'group.approval.requested': true, - }, 'userId group').exec(); + }, 'userId group text') + .populate('userId', 'profile') + .exec(); res.respond(200, approvals); }, diff --git a/website/server/models/group.js b/website/server/models/group.js index 4f31f976a4..d04d3c9506 100644 --- a/website/server/models/group.js +++ b/website/server/models/group.js @@ -893,6 +893,8 @@ schema.methods.updateTask = async function updateTask (taskToSync) { updateCmd.$set[key] = syncableAttributes[key]; } + updateCmd.$set['group.approval.required'] = taskToSync.group.approval.required; + let taskSchema = Tasks[taskToSync.type]; // Updating instead of loading and saving for performances, risks becoming a problem if we introduce more complexity in tasks await taskSchema.update({ diff --git a/website/server/models/userNotification.js b/website/server/models/userNotification.js index 0b338b2e57..4a6ff847c0 100644 --- a/website/server/models/userNotification.js +++ b/website/server/models/userNotification.js @@ -13,6 +13,7 @@ const NOTIFICATION_TYPES = [ 'NEW_CONTRIBUTOR_LEVEL', 'CRON', 'GROUP_TASK_APPROVAL', + 'GROUP_TASK_APPROVED', ]; const Schema = mongoose.Schema; diff --git a/website/views/options/social/group.jade b/website/views/options/social/group.jade index 33f118985b..03c582fe70 100644 --- a/website/views/options/social/group.jade +++ b/website/views/options/social/group.jade @@ -147,14 +147,16 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter h3.popover-title {{group.leader.profile.name}} .popover-content markdown(text='group._editing ? groupCopy.leaderMessage : group.leaderMessage') - - ul.options-menu(ng-init="groupPane = 'chat'", style="display:none;") - //- li - //- a(ng-click="groupPane = 'chat'")=env.t('chat') - //- li - //- a(ng-click="groupPane = 'tasks'", ng-if='group.purchased.active')=env.t('tasks') - //- li - //- a(ng-click="groupPane = 'subscription'", ng-show='group.leader._id === user._id')=env.t('subscription') + + ul.options-menu(ng-init="groupPane = 'chat'", ng-hide="true") + //- li + //- a(ng-click="groupPane = 'chat'")=env.t('chat') + //- li + //- a(ng-click="groupPane = 'tasks'", ng-show='group.purchased.active')=env.t('tasks') + //- li + //- a(ng-click="groupPane = 'approvals'", ng-show='group.purchased.active && group.leader._id === user._id')=env.t('approvals') + //- li + //- a(ng-click="groupPane = 'subscription'", ng-show='group.leader._id === user._id')=env.t('subscription') .tab-content .tab-pane.active @@ -170,6 +172,8 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter group-tasks(ng-show="groupPane == 'tasks'") + group-approvals(ng-show="groupPane == 'approvals'", ng-if="group.leader._id === user._id", group="group") + //TODO: This can be a directive and the group/user can be an object passed via attribute div(ng-show="groupPane == 'subscription'") .col-md-12 diff --git a/website/views/options/social/groups/group-tasks-actions.jade b/website/views/options/social/groups/group-tasks-actions.jade index cf6be842b7..ce274cd345 100644 --- a/website/views/options/social/groups/group-tasks-actions.jade +++ b/website/views/options/social/groups/group-tasks-actions.jade @@ -2,3 +2,9 @@ script(type='text/ng-template', id='partials/groups.tasks.actions.html') div(ng-if="group.leader._id === user._id", class="col-md-12") strong=env.t('assignTask') group-members-autocomplete(ng-model="assignedMembers") + + ul.priority-multiplier + li {{requiresApproval}} + button(type='button', ng-class='{active: task._edit.requiresApproval==true}', + ng-click='task._edit.requiresApproval = !task._edit.requiresApproval') + =env.t('approvalRequired') diff --git a/website/views/options/social/groups/group-tasks-approvals.jade b/website/views/options/social/groups/group-tasks-approvals.jade new file mode 100644 index 0000000000..0d704ec36c --- /dev/null +++ b/website/views/options/social/groups/group-tasks-approvals.jade @@ -0,0 +1,6 @@ +script(type='text/ng-template', id='partials/groups.tasks.approvals.html') + .panel-group(ng-repeat="approval in approvals") + .panel.panel-default + .panel-heading + span {{approvalTitle(approval)}} + a.btn.btn-sm.btn-success.pull-right(ng-click="approve(approval.group.taskId, approval.userId._id)")=env.t('approve') diff --git a/website/views/options/social/groups/group-tasks.jade b/website/views/options/social/groups/group-tasks.jade index d04a112537..89c8fe8239 100644 --- a/website/views/options/social/groups/group-tasks.jade +++ b/website/views/options/social/groups/group-tasks.jade @@ -1,6 +1,7 @@ include ./group-tasks-actions include ./group-tasks-meta-actions include ./group-members-autocomplete +include ./group-tasks-approvals script(type='text/ng-template', id='partials/groups.tasks.html') habitrpg-tasks(main=false) diff --git a/website/views/shared/header/menu.jade b/website/views/shared/header/menu.jade index 5f69101870..ab5090e09d 100644 --- a/website/views/shared/header/menu.jade +++ b/website/views/shared/header/menu.jade @@ -218,6 +218,11 @@ nav.toolbar(ng-controller='MenuCtrl') span {{v.name}} a(ng-click='clearMessages(k)', popover=env.t('clear'),popover-placement='right',popover-trigger='mouseenter',popover-append-to-body='true') span.glyphicon.glyphicon-remove-circle + li(ng-repeat='notification in user.groupNotifications') + a(ng-click='viewGroupApprovalNotification(notification, $index)', data-close-menu) + span(class="{{::groupApprovalNotificationIcon(notification)}}") + span + | {{notification.data.message}} ul.toolbar-controls li.toolbar-controls-button diff --git a/website/views/shared/tasks/index.jade b/website/views/shared/tasks/index.jade index 30f40824e9..f50455cdf7 100644 --- a/website/views/shared/tasks/index.jade +++ b/website/views/shared/tasks/index.jade @@ -6,7 +6,7 @@ include ./task_view/mixins script(id='templates/habitrpg-tasks.html', type="text/ng-template") .tasks-lists.container-fluid .row - .col-md-3.col-sm-6(ng-repeat='list in lists', ng-class='::{ "rewards-module": list.type==="reward", "new-row-md": list.type==="todo", "col-md-2": obj.type }') + .col-md-3.col-sm-6(ng-repeat='list in lists', ng-class='::{ "rewards-module": list.type==="reward", "new-row-md": list.type==="todo", "col-md-3": obj.type }') .task-column(class='{{::list.type}}s') include ./task_view/graph