From aaf32cc09b3f46c144e912bb94b6e7e1d76a79d5 Mon Sep 17 00:00:00 2001 From: negue Date: Sat, 25 Jul 2020 14:37:10 +0200 Subject: [PATCH] Teams UI Redesign and A11y Updates (#12142) * WIP(a11y): task modal updates * fix(tasks): borders in modal * fix(tasks): circley locks * fix(task-modal): placeholders * WIP(task-modal): disabled states, hide empty options, +/- restyle * fix(task-modal): box shadows instead of borders, habit control pointer * fix(task-modal): button states? * fix(modal): tighten up layout, new spacing utils * fix(tasks): more stylin * fix(tasks): habit hovers * fix(css): checklist labels, a11y colors * fix(css): one more missed hover issue * fix(css): lock Challenges, label fixes * fix(css): scope input/textarea changes * fix(style): task tweakies * fix(style): more button fixage * WIP(component): start select list story * working example of a templated selectList * fix(style): more button corrections * fix(lint): EOL * fix(buttons): factor btn-secondary to better override Bootstrap * fix(styles): standardize more buttons * wip: difficulty select - style fixes * selectDifficulty works! :tada: - fix styles * change the dropdown-item sizes only for the selectList ones * selectTranslatedArray * changed many label margins * more correct dropdown style * fix(modals): button corrections * input-group styling + datetime picker without today button * Style/margins for "repeat every" - extract selectTag.vue * working tag-selection / update - cleanup * fix stories * fix svg color on create modal (purple) * fix task modal bottom padding * correct dropdown shadow * update dropdown-toggle caret size / color * fixed checklist style * sync checked state * selectTag padding * fix spacing between positive/negative streak inputs * toggle-checkbox + fix some spacings * disable repeat-on when its a groupTask * fix new checklist-item * fix toggle-checkbox style - fix difficulty style * fix checklist ui * add tags label , when there arent any tags selected * WORKING select-tag component :tada: * fix taglist story * show max 5 items in tag dropdown + "X more" label * fix datetime clear button * replace m-b-xs to mb-1 (bootstrap) - fix input-group-text style * fix styles of advanced settings * fix delete task styles * always show grippy on hover of the item * extract modal-text-input mixin + fix the borders/dropshadow * fix(spacing): revert most to Bootstrap * feat(checklists): make local copy of master checklist non-editable also aggressively update checklists because they weren't syncing?? * fix(checklists): handle add/remove options better * feat(teams): manager notes field * fix select/dropdown styles * input border + icon colors * delete task underline color * fix checklist "delete icon" vertical position * selectTag fixes - normal open/close toggle working again - remove icon color * fixing icons: Trash can - Delete Little X - Remove Big X - Close Block - Block * fix taglist margins / icon sizes * wip margin overview (in storybook) * fix routerlink * remove unused method * new selectTag style + add markdown inside tagList + scrollable tag selection * fix selectTag / selectList active border * fix difficulty select (svg default color) * fix input padding-left + fix reset habit streak fullwidth / padding + "repeat every" gray text (no border) * feat(teams): improved approval request > approve > reward flow * fix(tests): address failures * fix(lint): oops only * fix(tasks): short-circuit group related logic * fix(tasks): more short circuiting * fix(tasks): more lines, less lint * fix(tasks): how do i keep missing these * feat(teams): provide assigning user summary * fix(teams): don't attempt to record assiging user if not supplied * fix advanced-settings styling / margin * fix merge + hide advanced streak settings when none enabled * fix styles * set Roboto font for advanced settings * Add Challenge flag to the tag list * add tag with enter, when no other tag is found * fix styles + tag cancel button * refactor footer / margin * split repeat fields into option mt-3 groups * button all the things * fix(tasks): style updates * no hover state for non-editable tasks on team board * keep assign/claim footer on task after requesting approval * disable more fields on user copy of team task, and remove hover states for them * fix(tasks): functional revisions * "Claim Rewards" instead of "x" in task approved notif * Remove default transition supplied by Bootstrap, apply individually to some elements * Delete individual tasks and related notifications when master task deleted from team board * Manager notes now save when supplied at task initial creation * Can no longer dismiss rewards from approved task by hitting Dismiss All * fix(tasks): clean tasksOrder also adjust related test expectation * fix(tests): adjust integration expectations * fix(test): ratzen fratzen only * fix(teams): checklist, notes * fix(teams): improve disabled states * fix(teams): more style fixage * BREAKING(teams): return 202 instead of 401 for approval request * fix(teams): better taskboard sync also re-re-fix checklist borders * fix(tests): update expectations for breaking change * refactor(task-modal): lockable label component * refactor(teams): move task scoring to mixin * fix(teams): style corrections * fix(tasks): spacing and wording corrections * fix(teams): don't bork manager notes * fix(teams): assignment fix and more approval flow revisions * WIP(teams): use tag dropdown control for assignment * refactor(tasks): better spacing, generic multi select * fix(tasks): various visual and behavior updates * fix(tasks): incidental style tweaks * fix(teams): standardize approval request response * refactor(teams): correct test, use res.respond message param * fix(storybook): renamed component * fix(teams): age approval-required To Do's Fixes #8730 * fix(teams): sync personal data as well as team on mixin sync * fix(teams): hide unclaim button, not whole footer; fix switch focus * fix(achievements): unrevert width fix Co-authored-by: Sabe Jones --- test/api/unit/models/group_tasks.test.js | 7 +- .../groups/POST-group_remove_manager.test.js | 7 +- .../groups/DELETE-group_tasks_id.test.js | 33 +- ...POST-group_tasks_id_approve_userId.test.js | 79 +- ...T-group_tasks_id_needs-work_userId.test.js | 23 +- ...OST-group_tasks_id_score_direction.test.js | 32 +- .../tasks/groups/PUT-group_task_id.test.js | 10 +- website/client/config/storybook/config.js | 2 + website/client/config/storybook/margin.css | 13 + website/client/src/assets/scss/button.scss | 8 +- website/client/src/assets/scss/dropdown.scss | 43 +- website/client/src/assets/scss/form.scss | 92 +- website/client/src/assets/scss/icon.scss | 10 +- website/client/src/assets/scss/index.scss | 1 + website/client/src/assets/scss/misc.scss | 14 +- website/client/src/assets/scss/modal.scss | 1 + website/client/src/assets/scss/spacing.scss | 59 + website/client/src/assets/scss/task.scss | 33 +- .../client/src/assets/scss/typography.scss | 6 +- website/client/src/assets/svg/information.svg | 2 +- .../src/components/achievements/newStuff.vue | 8 +- website/client/src/components/appFooter.vue | 5 +- .../group-plans/taskInformation.vue | 21 +- .../components/groups/communityGuidelines.vue | 2 +- .../notifications/groupTaskApproval.vue | 9 +- .../notifications/groupTaskApproved.vue | 31 +- .../header/notificationsDropdown.vue | 2 +- .../src/components/header/userDropdown.vue | 20 +- .../client/src/components/inventory/item.vue | 4 +- .../components/inventory/stable/foodItem.vue | 2 +- .../components/inventory/stable/mountItem.vue | 2 +- .../components/inventory/stable/petItem.vue | 2 +- .../client/src/components/notifications.vue | 51 +- .../src/components/payments/sendGemsModal.vue | 62 +- .../client/src/components/settings/api.vue | 14 +- .../src/components/snackbars/notification.vue | 2 +- .../client/src/components/static/header.vue | 2 +- website/client/src/components/static/home.vue | 11 +- .../src/components/static/staticWrapper.vue | 2 +- .../src/components/tasks/approvalFooter.vue | 19 +- .../src/components/tasks/approvalHeader.vue | 16 +- .../client/src/components/tasks/column.vue | 1 + .../tasks/modal-controls/checklist.vue | 59 +- .../tasks/modal-controls/lockableLabel.vue | 65 + .../tasks/modal-controls/multiList.vue | 187 +++ .../tasks/modal-controls/selectDifficulty.vue | 182 +++ .../modal-controls/selectMulti.stories.js | 133 ++ .../tasks/modal-controls/selectMulti.vue | 294 +++++ .../modal-controls/selectTranslatedArray.vue | 74 ++ .../client/src/components/tasks/tagsPopup.vue | 187 --- website/client/src/components/tasks/task.vue | 198 +-- .../client/src/components/tasks/taskModal.vue | 1158 ++++++++--------- .../src/components/ui/checkbox.stories.js | 70 + website/client/src/components/ui/checkbox.vue | 14 +- .../src/components/ui/input-group.stories.js | 71 + .../src/components/ui/margin.stories.js | 45 + .../src/components/ui/selectList.stories.js | 110 ++ .../client/src/components/ui/selectList.vue | 68 + .../src/components/ui/toggleCheckbox.vue | 116 ++ .../client/src/components/ui/toggleSwitch.vue | 31 +- .../src/components/userMenu/profile.vue | 35 +- website/client/src/mixins/scoreTask.js | 136 ++ website/client/src/mixins/sync.js | 3 + website/client/src/store/actions/tags.js | 8 +- website/client/src/store/getters/tasks.js | 17 +- website/common/locales/en/groups.json | 13 +- website/common/locales/en/tasks.json | 5 +- website/common/script/ops/scoreTask.js | 2 +- website/server/controllers/api-v3/tasks.js | 24 +- .../server/controllers/api-v3/tasks/groups.js | 10 +- website/server/libs/taskManager.js | 1 + website/server/models/group.js | 79 +- website/server/models/task.js | 20 +- 73 files changed, 2769 insertions(+), 1409 deletions(-) create mode 100644 website/client/config/storybook/margin.css create mode 100644 website/client/src/assets/scss/spacing.scss create mode 100644 website/client/src/components/tasks/modal-controls/lockableLabel.vue create mode 100644 website/client/src/components/tasks/modal-controls/multiList.vue create mode 100644 website/client/src/components/tasks/modal-controls/selectDifficulty.vue create mode 100644 website/client/src/components/tasks/modal-controls/selectMulti.stories.js create mode 100644 website/client/src/components/tasks/modal-controls/selectMulti.vue create mode 100644 website/client/src/components/tasks/modal-controls/selectTranslatedArray.vue delete mode 100644 website/client/src/components/tasks/tagsPopup.vue create mode 100644 website/client/src/components/ui/checkbox.stories.js create mode 100644 website/client/src/components/ui/input-group.stories.js create mode 100644 website/client/src/components/ui/margin.stories.js create mode 100644 website/client/src/components/ui/selectList.stories.js create mode 100644 website/client/src/components/ui/selectList.vue create mode 100644 website/client/src/components/ui/toggleCheckbox.vue create mode 100644 website/client/src/mixins/scoreTask.js diff --git a/test/api/unit/models/group_tasks.test.js b/test/api/unit/models/group_tasks.test.js index 848b4b0792..1c60fa947b 100644 --- a/test/api/unit/models/group_tasks.test.js +++ b/test/api/unit/models/group_tasks.test.js @@ -235,15 +235,16 @@ describe('Group Task Methods', () => { }); }); - it('removes an assigned task and unlinks assignees', async () => { + it('removes assigned tasks when master task is deleted', async () => { await guild.syncTask(task, leader); await guild.removeTask(task); const updatedLeader = await User.findOne({ _id: leader._id }); - const updatedLeadersTasks = await Tasks.Task.find({ _id: { $in: updatedLeader.tasksOrder[`${taskType}s`] } }); + const updatedLeadersTasks = await Tasks.Task.find({ userId: leader._id, type: taskType }); const syncedTask = find(updatedLeadersTasks, findLinkedTask); - expect(syncedTask.group.broken).to.equal('TASK_DELETED'); + expect(updatedLeader.tasksOrder[`${taskType}s`]).to.not.include(task._id); + expect(syncedTask).to.not.exist; }); it('unlinks and deletes group tasks for a user when remove-all is specified', async () => { diff --git a/test/api/v3/integration/groups/POST-group_remove_manager.test.js b/test/api/v3/integration/groups/POST-group_remove_manager.test.js index e8cae4ab29..6c5d4cee9f 100644 --- a/test/api/v3/integration/groups/POST-group_remove_manager.test.js +++ b/test/api/v3/integration/groups/POST-group_remove_manager.test.js @@ -75,12 +75,7 @@ describe('POST /group/:groupId/remove-manager', () => { await nonLeader.post(`/tasks/${task._id}/assign/${nonManager._id}`); const memberTasks = await nonManager.get('/tasks/user'); const syncedTask = find(memberTasks, findAssignedTask); - await expect(nonManager.post(`/tasks/${syncedTask._id}/score/up`)) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('taskApprovalHasBeenRequested'), - }); + await nonManager.post(`/tasks/${syncedTask._id}/score/up`); const updatedGroup = await leader.post(`/groups/${groupToUpdate._id}/remove-manager`, { managerId: nonLeader._id, diff --git a/test/api/v3/integration/tasks/groups/DELETE-group_tasks_id.test.js b/test/api/v3/integration/tasks/groups/DELETE-group_tasks_id.test.js index 59dba9f5ed..73828accdc 100644 --- a/test/api/v3/integration/tasks/groups/DELETE-group_tasks_id.test.js +++ b/test/api/v3/integration/tasks/groups/DELETE-group_tasks_id.test.js @@ -73,12 +73,7 @@ describe('Groups DELETE /tasks/:id', () => { }); const memberTasks = await member.get('/tasks/user'); const syncedTask = find(memberTasks, findAssignedTask); - await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('taskApprovalHasBeenRequested'), - }); + await member.post(`/tasks/${syncedTask._id}/score/up`); await user.sync(); await member2.sync(); @@ -96,16 +91,16 @@ describe('Groups DELETE /tasks/:id', () => { expect(member2.notifications.length).to.equal(1); }); - it('unlinks assigned user', async () => { + it('deletes task from assigned user', async () => { await user.del(`/tasks/${task._id}`); const memberTasks = await member.get('/tasks/user'); const syncedTask = find(memberTasks, findAssignedTask); - expect(syncedTask.group.broken).to.equal('TASK_DELETED'); + expect(syncedTask).to.not.exist; }); - it('unlinks all assigned users', async () => { + it('deletes task from all assigned users', async () => { await user.del(`/tasks/${task._id}`); const memberTasks = await member.get('/tasks/user'); @@ -114,8 +109,8 @@ describe('Groups DELETE /tasks/:id', () => { const member2Tasks = await member2.get('/tasks/user'); const member2SyncedTask = find(member2Tasks, findAssignedTask); - expect(syncedTask.group.broken).to.equal('TASK_DELETED'); - expect(member2SyncedTask.group.broken).to.equal('TASK_DELETED'); + expect(syncedTask).to.not.exist; + expect(member2SyncedTask).to.not.exist; }); it('prevents a user from deleting a task they are assigned to', async () => { @@ -130,22 +125,6 @@ describe('Groups DELETE /tasks/:id', () => { }); }); - it('allows a user to delete a broken task', async () => { - const memberTasks = await member.get('/tasks/user'); - const syncedTask = find(memberTasks, findAssignedTask); - - await user.del(`/tasks/${task._id}`); - - await member.del(`/tasks/${syncedTask._id}`); - - await expect(member.get(`/tasks/${syncedTask._id}`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: 'Task not found.', - }); - }); - it('allows a user to delete a task after leaving a group', async () => { const memberTasks = await member.get('/tasks/user'); const syncedTask = find(memberTasks, findAssignedTask); 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 599086e463..1d23e5f85c 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 @@ -58,22 +58,14 @@ describe('POST /tasks/:id/approve/:userId', () => { let memberTasks = await member.get('/tasks/user'); let syncedTask = find(memberTasks, findAssignedTask); - await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('taskApprovalHasBeenRequested'), - }); - + await member.post(`/tasks/${syncedTask._id}/score/up`); await user.post(`/tasks/${task._id}/approve/${member._id}`); await member.sync(); - expect(member.notifications.length).to.equal(3); + expect(member.notifications.length).to.equal(2); expect(member.notifications[1].type).to.equal('GROUP_TASK_APPROVED'); expect(member.notifications[1].data.message).to.equal(t('yourTaskHasBeenApproved', { taskText: task.text })); - expect(member.notifications[2].type).to.equal('SCORED_TASK'); - expect(member.notifications[2].data.message).to.equal(t('yourTaskHasBeenApproved', { taskText: task.text })); memberTasks = await member.get('/tasks/user'); syncedTask = find(memberTasks, findAssignedTask); @@ -93,21 +85,13 @@ describe('POST /tasks/:id/approve/:userId', () => { let memberTasks = await member.get('/tasks/user'); let syncedTask = find(memberTasks, findAssignedTask); - await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('taskApprovalHasBeenRequested'), - }); - + await member.post(`/tasks/${syncedTask._id}/score/up`); await member2.post(`/tasks/${task._id}/approve/${member._id}`); await member.sync(); - expect(member.notifications.length).to.equal(3); + expect(member.notifications.length).to.equal(2); expect(member.notifications[1].type).to.equal('GROUP_TASK_APPROVED'); expect(member.notifications[1].data.message).to.equal(t('yourTaskHasBeenApproved', { taskText: task.text })); - expect(member.notifications[2].type).to.equal('SCORED_TASK'); - expect(member.notifications[2].data.message).to.equal(t('yourTaskHasBeenApproved', { taskText: task.text })); memberTasks = await member.get('/tasks/user'); syncedTask = find(memberTasks, findAssignedTask); @@ -125,12 +109,7 @@ describe('POST /tasks/:id/approve/:userId', () => { await member2.post(`/tasks/${task._id}/assign/${member._id}`); const memberTasks = await member.get('/tasks/user'); const syncedTask = find(memberTasks, findAssignedTask); - await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('taskApprovalHasBeenRequested'), - }); + await member.post(`/tasks/${syncedTask._id}/score/up`); await user.sync(); await member2.sync(); @@ -157,14 +136,9 @@ describe('POST /tasks/:id/approve/:userId', () => { const memberTasks = await member.get('/tasks/user'); const syncedTask = find(memberTasks, findAssignedTask); - await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('taskApprovalHasBeenRequested'), - }); - + await member.post(`/tasks/${syncedTask._id}/score/up`); await member2.post(`/tasks/${task._id}/approve/${member._id}`); + await expect(user.post(`/tasks/${task._id}/approve/${member._id}`)) .to.eventually.be.rejected.and.to.eql({ code: 401, @@ -197,13 +171,7 @@ describe('POST /tasks/:id/approve/:userId', () => { const memberTasks = await member.get('/tasks/user'); const syncedTask = find(memberTasks, findAssignedTask); - await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('taskApprovalHasBeenRequested'), - }); - + await member.post(`/tasks/${syncedTask._id}/score/up`); await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`); const groupTasks = await user.get(`/tasks/group/${guild._id}?type=completedTodos`); @@ -226,13 +194,7 @@ describe('POST /tasks/:id/approve/:userId', () => { const memberTasks = await member.get('/tasks/user'); const syncedTask = find(memberTasks, findAssignedTask); - await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('taskApprovalHasBeenRequested'), - }); - + await member.post(`/tasks/${syncedTask._id}/score/up`); await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`); const member2Tasks = await member2.get('/tasks/user'); @@ -258,13 +220,7 @@ describe('POST /tasks/:id/approve/:userId', () => { const memberTasks = await member.get('/tasks/user'); const syncedTask = find(memberTasks, findAssignedTask); - await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('taskApprovalHasBeenRequested'), - }); - + await member.post(`/tasks/${syncedTask._id}/score/up`); await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`); const groupTasks = await user.get(`/tasks/group/${guild._id}`); @@ -287,21 +243,10 @@ describe('POST /tasks/:id/approve/:userId', () => { const memberTasks = await member.get('/tasks/user'); const syncedTask = find(memberTasks, findAssignedTask); - await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('taskApprovalHasBeenRequested'), - }); - + await member.post(`/tasks/${syncedTask._id}/score/up`); const member2Tasks = await member2.get('/tasks/user'); const member2SyncedTask = find(member2Tasks, findAssignedTask); - await expect(member2.post(`/tasks/${member2SyncedTask._id}/score/up`)) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('taskApprovalHasBeenRequested'), - }); + await member2.post(`/tasks/${member2SyncedTask._id}/score/up`); await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`); await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member2._id}`); diff --git a/test/api/v3/integration/tasks/groups/POST-group_tasks_id_needs-work_userId.test.js b/test/api/v3/integration/tasks/groups/POST-group_tasks_id_needs-work_userId.test.js index 91026baeae..8472bc03aa 100644 --- a/test/api/v3/integration/tasks/groups/POST-group_tasks_id_needs-work_userId.test.js +++ b/test/api/v3/integration/tasks/groups/POST-group_tasks_id_needs-work_userId.test.js @@ -61,13 +61,7 @@ describe('POST /tasks/:id/needs-work/:userId', () => { let syncedTask = find(memberTasks, findAssignedTask); // score task to require approval - await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('taskApprovalHasBeenRequested'), - }); - + await member.post(`/tasks/${syncedTask._id}/score/up`); await user.post(`/tasks/${task._id}/needs-work/${member._id}`); [memberTasks] = await Promise.all([member.get('/tasks/user'), member.sync()]); @@ -114,12 +108,7 @@ describe('POST /tasks/:id/needs-work/:userId', () => { let syncedTask = find(memberTasks, findAssignedTask); // score task to require approval - await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('taskApprovalHasBeenRequested'), - }); + await member.post(`/tasks/${syncedTask._id}/score/up`); const initialNotifications = member.notifications.length; @@ -172,13 +161,7 @@ describe('POST /tasks/:id/needs-work/:userId', () => { const memberTasks = await member.get('/tasks/user'); const syncedTask = find(memberTasks, findAssignedTask); - await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('taskApprovalHasBeenRequested'), - }); - + await member.post(`/tasks/${syncedTask._id}/score/up`); await member2.post(`/tasks/${task._id}/approve/${member._id}`); await expect(user.post(`/tasks/${task._id}/needs-work/${member._id}`)) .to.eventually.be.rejected.and.to.eql({ 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 80710c701b..810ad517d1 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 @@ -44,12 +44,11 @@ describe('POST /tasks/:id/score/:direction', () => { const syncedTask = find(memberTasks, findAssignedTask); const direction = 'up'; - await expect(member.post(`/tasks/${syncedTask._id}/score/${direction}`)) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('taskApprovalHasBeenRequested'), - }); + const response = await member.post(`/tasks/${syncedTask._id}/score/${direction}`); + + expect(response.data.approvalRequested).to.equal(true); + expect(response.message).to.equal(t('taskApprovalHasBeenRequested')); + const updatedTask = await member.get(`/tasks/${syncedTask._id}`); await user.sync(); @@ -76,12 +75,7 @@ describe('POST /tasks/:id/score/:direction', () => { const syncedTask = find(memberTasks, findAssignedTask); const direction = 'up'; - await expect(member.post(`/tasks/${syncedTask._id}/score/${direction}`)) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('taskApprovalHasBeenRequested'), - }); + await member.post(`/tasks/${syncedTask._id}/score/${direction}`); const updatedTask = await member.get(`/tasks/${syncedTask._id}`); await user.sync(); await member2.sync(); @@ -111,12 +105,7 @@ describe('POST /tasks/:id/score/:direction', () => { const memberTasks = await member.get('/tasks/user'); const syncedTask = find(memberTasks, findAssignedTask); - await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('taskApprovalHasBeenRequested'), - }); + await member.post(`/tasks/${syncedTask._id}/score/up`); await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) .to.eventually.be.rejected.and.eql({ @@ -130,12 +119,7 @@ describe('POST /tasks/:id/score/:direction', () => { const memberTasks = await member.get('/tasks/user'); const syncedTask = find(memberTasks, findAssignedTask); - await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('taskApprovalHasBeenRequested'), - }); + await member.post(`/tasks/${syncedTask._id}/score/up`); await user.post(`/tasks/${task._id}/approve/${member._id}`); diff --git a/test/api/v3/integration/tasks/groups/PUT-group_task_id.test.js b/test/api/v3/integration/tasks/groups/PUT-group_task_id.test.js index 45443d4f06..74c76475a2 100644 --- a/test/api/v3/integration/tasks/groups/PUT-group_task_id.test.js +++ b/test/api/v3/integration/tasks/groups/PUT-group_task_id.test.js @@ -71,12 +71,10 @@ describe('PUT /tasks/:id', () => { const syncedTask = find(memberTasks, memberTask => memberTask.group.taskId === habit._id); // score up to trigger approval - await expect(member2.post(`/tasks/${syncedTask._id}/score/up`)) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('taskApprovalHasBeenRequested'), - }); + const response = await member2.post(`/tasks/${syncedTask._id}/score/up`); + + expect(response.data.approvalRequested).to.equal(true); + expect(response.message).to.equal(t('taskApprovalHasBeenRequested')); }); it('member updates a group task value - not allowed', async () => { diff --git a/website/client/config/storybook/config.js b/website/client/config/storybook/config.js index 01bba95033..fb3957a25e 100644 --- a/website/client/config/storybook/config.js +++ b/website/client/config/storybook/config.js @@ -1,6 +1,8 @@ /* eslint-disable import/no-extraneous-dependencies */ import { configure } from '@storybook/vue'; +import './margin.css'; import '../../src/assets/scss/index.scss'; +import '../../src/assets/scss/spacing.scss'; import '../../src/assets/css/sprites.css'; import '../../src/assets/css/sprites/spritesmith-main-0.css'; diff --git a/website/client/config/storybook/margin.css b/website/client/config/storybook/margin.css new file mode 100644 index 0000000000..eaad293c27 --- /dev/null +++ b/website/client/config/storybook/margin.css @@ -0,0 +1,13 @@ +.background { + background: teal; + display: inline-block; +} + +.content { + color: white; + background: grey; +} + +.inline-block { + display: inline-block; +} diff --git a/website/client/src/assets/scss/button.scss b/website/client/src/assets/scss/button.scss index 482e731700..a19e96acf4 100644 --- a/website/client/src/assets/scss/button.scss +++ b/website/client/src/assets/scss/button.scss @@ -52,9 +52,11 @@ border-color: $purple-400; } - &:not(:disabled):not(.disabled):active:focus, &:not(:disabled):not(.disabled).active:focus { - box-shadow: none; - border-color: $purple-400; + &:not(:disabled):not(.disabled) { + &:active:focus, &.active:focus { + box-shadow: none; + border-color: $purple-400; + } } &:not(:disabled):not(.disabled):active, &:not(:disabled):not(.disabled).active { diff --git a/website/client/src/assets/scss/dropdown.scss b/website/client/src/assets/scss/dropdown.scss index 0acbce7864..4a078edd25 100644 --- a/website/client/src/assets/scss/dropdown.scss +++ b/website/client/src/assets/scss/dropdown.scss @@ -1,20 +1,27 @@ .dropdown > .btn { - padding: 9px 15.5px; + padding: 0.25rem 0.75rem; font-family: 'Roboto', sans-serif; font-size: 14px; font-weight: normal; line-height: 1.43; } +.dropdown-toggle:hover { + --caret-color: #{$purple-200}; +} + .dropdown.show > .dropdown-toggle:not(.btn-success) { color: $purple-200; - border-color: $purple-500 !important; + border-color: $purple-400 !important; box-shadow: none; + + &::after { + --caret-color: #{$purple-200}; + } } .dropdown-toggle::after { - margin-left: 16px; - border-top: 6px solid; + border-top-color: var(--caret-color); border-right: 5px solid transparent; border-left: 5px solid transparent; vertical-align: 0; @@ -23,14 +30,18 @@ .dropdown-menu { padding: 0px; border: none; - border-radius: 4px; - box-shadow: 0 2px 2px 0 rgba($black, 0.15), 0 1px 4px 0 rgba($white, 0.1); + border-radius: 2px; + box-shadow: 0 3px 6px 0 rgba(26, 24, 29, 0.16), 0 3px 6px 0 rgba(26, 24, 29, 0.24); + } +// shared dropdown-item styles .dropdown-item { + // header items & not selectList-items padding-left: 24px; padding-top: 8px; padding-bottom: 8px; + font-size: 14px; line-height: 1.71; color: $gray-50; @@ -42,8 +53,8 @@ } - &:active, &:hover, &.active { - background-color: rgba(#d5c8ff, 0.32); + &:active, &:hover, &:focus, &.active { + background-color: rgba($purple-600, 0.32); color: $purple-200; } @@ -86,16 +97,28 @@ .dropdown-toggle { width: 100% !important; + height: 32px; text-align: left; } .dropdown-toggle::after { position: absolute; - right: 16px; - top: 17px; + right: 12px; + top: 14px; } .dropdown-menu.show { width: 100% !important; } } + +// selectList.vue items sizing +.selectListItem .dropdown-item { + padding: 0.25rem 0.75rem; + height: 32px; + + &:active, &:hover, &:focus, &.active { + background-color: rgba($purple-600, 0.25); + color: $purple-300; + } +} diff --git a/website/client/src/assets/scss/form.scss b/website/client/src/assets/scss/form.scss index edfb99245e..f4c2dc3ba1 100644 --- a/website/client/src/assets/scss/form.scss +++ b/website/client/src/assets/scss/form.scss @@ -16,10 +16,10 @@ label small { } } -// Inputs and texteares +// Inputs and textareas input, textarea, input.form-control, textarea.form-control { - padding: 10px 16px; + padding: 10px 12px; border-radius: 2px; font-size: 14px; line-height: 1.43; @@ -31,14 +31,14 @@ input, textarea, input.form-control, textarea.form-control { } &:active:not(:disabled), &:focus:not(:disabled) { - border-color: $purple-500; + border-color: $purple-400; outline: 0; box-shadow: none; } &:disabled { opacity: 0.64; - background: $gray-500; + background: $gray-700; } &.input-search { @@ -68,11 +68,48 @@ input, textarea, input.form-control, textarea.form-control { } } -.input-group { - .input-group-prepend , .input-group-append { +.input-group-outer { + display: flex; + flex-direction: row; + + .input-group { + flex: 1; + } +} + +/** Colored Input-Groups, ignoring checklist */ +.input-group:not(.checklist-group) { + border-radius: 2px; + border: solid 1px $gray-400; + + &:hover { + border-color: $gray-300; + } + + &:focus, &:active, &:focus-within { + border: solid 1px $purple-400; + } + + .input-group-prepend , .input-group-append { background: $gray-600; - color: $gray-300; - border-radius: 2px; + } +} + +/** Generic Input Group Styles */ +.input-group { + height: 2rem; + + .input-group-prepend , .input-group-append { + color: $gray-200; + border: 0; + height: 30px; + width: 2rem; + margin: 0; + + &.grow { + width: initial; + min-width: 2rem; + } &.input-group-text { font-size: 14px; @@ -83,28 +120,30 @@ input, textarea, input.form-control, textarea.form-control { } &.input-group-icon { - border: solid 1px $gray-400; - border-right: none; - border-top-right-radius: 0px; - border-bottom-right-radius: 0px; + display: flex; + align-self: center; + align-items: center; + justify-items: center; + justify-content: center; + } + + .svg-icon { + margin: 0 !important; } &.streak-addon .svg-icon { width: 11.6px; height: 7.1px; - margin: 15px 13.4px 15.9px 13px; } &.positive-addon .svg-icon { width: 10px; height: 10px; - margin: 14px 14px; } &.negative-addon .svg-icon { width: 10px; height: 2px; - margin: 18px 14px; } } @@ -115,6 +154,19 @@ input, textarea, input.form-control, textarea.form-control { input:first-child { border-right: none !important; } + + input { + height: 30px; + border: 0; + background: $white !important; + } +} + +.input-group-spaced { + margin-left: 12px; + height: 2rem; + border-radius: 2px; + background-color: $gray-600; } .form-check { @@ -200,9 +252,13 @@ $bg-disabled-control: #34303a; align-items: center; } - .destroy-icon { - width: 14px; - height: 16px; + .destroy-icon.svg-icon { + margin-top: 1px !important; + + svg { + width: 14px; + height: 16px; + } } } diff --git a/website/client/src/assets/scss/icon.scss b/website/client/src/assets/scss/icon.scss index 5e5a9162b9..3d570b9ac0 100644 --- a/website/client/src/assets/scss/icon.scss +++ b/website/client/src/assets/scss/icon.scss @@ -1,14 +1,14 @@ .svg-icon { display: block; - transition: none !important; fill: currentColor; svg { display: block; - } + transition: none; - * { - transition: none !important; + path { + transition: none; + } } &.color { @@ -64,4 +64,4 @@ &:hover svg path { stroke: $gray-100; } -} \ No newline at end of file +} diff --git a/website/client/src/assets/scss/index.scss b/website/client/src/assets/scss/index.scss index 6842a80947..63dfcbc85f 100644 --- a/website/client/src/assets/scss/index.scss +++ b/website/client/src/assets/scss/index.scss @@ -37,3 +37,4 @@ @import './tiers'; @import './payments'; @import './datepicker.scss'; +@import './spacing'; diff --git a/website/client/src/assets/scss/misc.scss b/website/client/src/assets/scss/misc.scss index 164a05f0e7..410ddcd13a 100644 --- a/website/client/src/assets/scss/misc.scss +++ b/website/client/src/assets/scss/misc.scss @@ -14,4 +14,16 @@ border-right: 4px solid transparent; border-left: 4px solid transparent; border-bottom: 0; -} \ No newline at end of file +} + +* { + transition: none; +} + +.transition { + transition-duration: 0.15s; + transition-property: border-color, color; + transition-property: border-color, box-shadow, color; + transition-property: border-color, box-shadow, color; + transition-timing-function: ease-in; +} diff --git a/website/client/src/assets/scss/modal.scss b/website/client/src/assets/scss/modal.scss index efaf47bb0c..ae4ce79d9d 100644 --- a/website/client/src/assets/scss/modal.scss +++ b/website/client/src/assets/scss/modal.scss @@ -13,6 +13,7 @@ .modal { z-index: 1350; + padding-left: 0px !important; } .modal-dialog { diff --git a/website/client/src/assets/scss/spacing.scss b/website/client/src/assets/scss/spacing.scss new file mode 100644 index 0000000000..325c36003b --- /dev/null +++ b/website/client/src/assets/scss/spacing.scss @@ -0,0 +1,59 @@ +.m-75 { + margin: 0.75rem; // 12px +} + +.mx-75 { + margin-left: 0.75rem; + margin-right: 0.75rem; +} + +.my-75 { + margin-bottom: 0.75rem; + margin-top: 0.75rem; +} + +.mb-75 { + margin-bottom: 0.75rem; +} + +.ml-75 { + margin-left: 0.75rem; +} + +.mr-75 { + margin-right: 0.75rem; +} + +.mt-75 { + margin-top: 0.75rem; +} + +.p-75 { + padding: 0.75rem; +} + +.px-75 { + padding-left: 0.75rem; + padding-right: 0.75rem; +} + +.py-75 { + padding-bottom: 0.75rem; + padding-top: 0.75rem; +} + +.pb-75 { + padding-bottom: 0.75rem; +} + +.pl-75 { + padding-left: 0.75rem; +} + +.pr-75 { + padding-right: 0.75rem; +} + +.pt-75 { + padding-top: 0.75rem; +} diff --git a/website/client/src/assets/scss/task.scss b/website/client/src/assets/scss/task.scss index d651a574e7..66f6f35bf7 100644 --- a/website/client/src/assets/scss/task.scss +++ b/website/client/src/assets/scss/task.scss @@ -3,7 +3,8 @@ .habit-option-button { border: 2px solid $disabled-color; } - &:hover { + // TODO refactor to use more css-vars and less duplicate generated css code + &:hover, &:focus, &:active { .habit-option-button { border: 2px solid $active-color; } @@ -28,7 +29,7 @@ &-control { &-bg { background: $maroon-100 !important; - .habit-control:hover { background: rgba($black, 0.5) !important; } + .habit-control:not(.task-not-scoreable):hover { background: rgba($black, 0.5) !important; } .daily-todo-control:hover { background: rgba($white, 0.75) !important; } } &-bg-noninteractive { background: $maroon-100 !important; } @@ -40,6 +41,7 @@ &-modal { &-bg { background: $maroon-100 !important; } + &-headings { color: $white; } &-icon { color: $maroon-100 !important; } &-text { color: $red-1 !important; } &-content { @@ -58,7 +60,7 @@ &-control { &-bg { background: $red-100 !important; - .habit-control:hover { background: rgba($black, 0.5) !important; } + .habit-control:not(.task-not-scoreable):hover { background: rgba($black, 0.5) !important; } .daily-todo-control:hover { background: rgba($white, 0.75) !important; } } &-bg-noninteractive { background: $red-100 !important; } @@ -70,8 +72,8 @@ &-modal { &-bg { background: $red-100 !important; } + &-headings, &-text { color: $red-1 !important; } &-icon { color: $red-100 !important; } - &-text { color: $red-1 !important; } &-content { --svg-color: #{$red-100}; } @@ -89,7 +91,7 @@ &-control { &-bg { background: $orange-100 !important; - .habit-control:hover { background: rgba($orange-1, 0.5) !important; } + .habit-control:not(.task-not-scoreable):hover { background: rgba($orange-1, 0.5) !important; } .daily-todo-control:hover { background: rgba($white, 0.75) !important; } } &-bg-noninteractive { background: $orange-100 !important; } @@ -101,8 +103,8 @@ &-modal { &-bg { background: $orange-100 !important; } + &-headings, &-text { color: $orange-1 !important; } &-icon { color: $orange-100 !important; } - &-text { color: $orange-1 !important; } &-content { --svg-color: #{$orange-100}; } @@ -120,7 +122,7 @@ &-control { &-bg { background: $yellow-100 !important; - .habit-control:hover { background: rgba($yellow-1, 0.5) !important; } + .habit-control:not(.task-not-scoreable):hover { background: rgba($yellow-1, 0.5) !important; } .daily-todo-control:hover { background: rgba($white, 0.75) !important; } } &-bg-noninteractive { background: $yellow-100 !important; } @@ -132,8 +134,8 @@ &-modal { &-bg { background: $yellow-100 !important; } + &-headings, &-text { color: $yellow-1 !important; } &-icon { color: $yellow-100 !important; } - &-text { color: $yellow-1 !important; } @include modal-text-input($yellow-1); &-option-disabled:hover { .svg-icon { color: $yellow-100 !important; } @@ -151,7 +153,7 @@ &-control { &-bg { background: $green-100 !important; - .habit-control:hover { background: rgba($black, 0.5) !important; } + .habit-control:not(.task-not-scoreable):hover { background: rgba($black, 0.5) !important; } .daily-todo-control:hover { background: rgba($white, 0.75) !important; } } &-bg-noninteractive { background: $green-100 !important; } @@ -163,8 +165,8 @@ &-modal { &-bg { background: $green-100 !important; } + &-headings, &-text { color: $green-1 !important; } &-icon { color: $green-10 !important; } - &-text { color: $green-1 !important; } &-content { --svg-color: #{$green-100}; } @@ -183,7 +185,7 @@ &-control { &-bg { background: $teal-100 !important; - .habit-control:hover { background: rgba($black, 0.5) !important; } + .habit-control:not(.task-not-scoreable):hover { background: rgba($black, 0.5) !important; } .daily-todo-control:hover { background: rgba($white, 0.75) !important; } } &-bg-noninteractive { background: $teal-100 !important; } @@ -195,8 +197,8 @@ &-modal { &-bg { background: $teal-100 !important; } + &-headings, &-text { color: $teal-1 !important; } &-icon { color: $teal-100 !important; } - &-text { color: $teal-1 !important; } &-content { --svg-color: #{$teal-100}; } @@ -214,7 +216,7 @@ &-control { &-bg { background: $blue-100 !important; - .habit-control:hover { background: rgba($black, 0.5) !important; } + .habit-control:not(.task-not-scoreable):hover { background: rgba($black, 0.5) !important; } .daily-todo-control:hover { background: rgba($white, 0.75) !important; } } &-bg-noninteractive { background: $blue-100 !important; } @@ -226,8 +228,8 @@ &-modal { &-bg { background: $blue-100 !important; } + &-headings, &-text { color: $blue-1 !important; } &-icon { color: $blue-100 !important; } - &-text { color: $blue-1 !important; } &-content { --svg-color: #{$blue-100}; } @@ -246,7 +248,7 @@ &-control { &-bg { background: $purple-task !important; - .habit-control:hover { background: rgba($black, 0.5) !important; } + .habit-control:not(.task-not-scoreable):hover { background: rgba($black, 0.5) !important; } .daily-todo-control:hover { background: rgba($white, 0.75) !important; } } &-inner-habit { background: rgba($black, 0.25) !important; } @@ -256,6 +258,7 @@ &-modal { &-bg { background: $purple-300 !important; } + &-headings { color: $white; } &-icon { color: $purple-300 !important; } &-text { color: $black !important; } &-content { diff --git a/website/client/src/assets/scss/typography.scss b/website/client/src/assets/scss/typography.scss index a9caa6b285..267327b09b 100644 --- a/website/client/src/assets/scss/typography.scss +++ b/website/client/src/assets/scss/typography.scss @@ -53,7 +53,7 @@ h1 { h2 { font-size: 20px; - line-height: 1.2; + line-height: 1.4; margin-bottom: 16px; } @@ -73,3 +73,7 @@ h4 { font-family: 'Roboto Condensed', sans-serif; font-weight: bold; } + +.opacity-75 { + opacity: 0.75; +} diff --git a/website/client/src/assets/svg/information.svg b/website/client/src/assets/svg/information.svg index cb4a531a1e..c3d91e08ee 100644 --- a/website/client/src/assets/svg/information.svg +++ b/website/client/src/assets/svg/information.svg @@ -1,3 +1,3 @@ - + diff --git a/website/client/src/components/achievements/newStuff.vue b/website/client/src/components/achievements/newStuff.vue index e59592ee2a..53c427a383 100644 --- a/website/client/src/components/achievements/newStuff.vue +++ b/website/client/src/components/achievements/newStuff.vue @@ -13,20 +13,20 @@ v-html="html" > -