diff --git a/CHANGELOG.md b/CHANGELOG.md
index ba9a38c04a860e303a976500177848dfd1a2b6b0..575baec5fef40ba636fd5abc1e5758421239acc8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@ Please view this file on the master branch, on stable branches it's out of date.
- Fix mobile layout issues in admin user overview page !7087
- Fix HipChat notifications rendering (airatshigapov, eisnerd)
- Refactor Jira service to use jira-ruby gem
+- Improved todos empty state
- Add hover to trash icon in notes !7008 (blackst0ne)
- Only show one error message for an invalid email !5905 (lycoperdon)
- Fix sidekiq stats in admin area (blackst0ne)
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index ea76fe188763c6f26e113f0399ebf53dbac281ec..b3aef2fdd328ecb7cfeaf8f4fe0ca3817790cd3d 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -161,3 +161,63 @@
}
}
}
+
+.todos-empty {
+ display: -webkit-flex;
+ display: flex;
+ -webkit-flex-direction: column;
+ flex-direction: column;
+ max-width: 900px;
+ margin-left: auto;
+ margin-right: auto;
+
+ @media (min-width: $screen-sm-min) {
+ -webkit-flex-direction: row;
+ flex-direction: row;
+ padding-top: 80px;
+ }
+}
+
+.todos-empty-content {
+ -webkit-align-self: center;
+ align-self: center;
+ max-width: 480px;
+ margin-right: 20px;
+}
+
+.todos-empty-hero {
+ width: 200px;
+ margin-left: auto;
+ margin-right: auto;
+
+ @media (min-width: $screen-sm-min) {
+ width: 300px;
+ margin-right: 0;
+ -webkit-order: 2;
+ order: 2;
+ }
+}
+
+.todos-all-done {
+ padding-top: 20px;
+
+ @media (min-width: $screen-sm-min) {
+ padding-top: 50px;
+ }
+
+ > svg {
+ display: block;
+ max-width: 300px;
+ margin: 0 auto 20px;
+ }
+
+ p {
+ max-width: 470px;
+ margin-left: auto;
+ margin-right: auto;
+ }
+
+ a {
+ font-weight: 600;
+ }
+}
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index 2a0302638baaca2a01d3cbb8d75e4172721d2348..2411cc457240b7a2dfbb1eafbdecacebd867f090 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -1,69 +1,70 @@
- page_title "Todos"
- header_title "Todos", dashboard_todos_path
-.top-area
- %ul.nav-links
- - todo_pending_active = ('active' if params[:state].blank? || params[:state] == 'pending')
- %li{class: "todos-pending #{todo_pending_active}"}
- = link_to todos_filter_path(state: 'pending') do
- %span
- To do
- %span.badge
- = number_with_delimiter(todos_pending_count)
- - todo_done_active = ('active' if params[:state] == 'done')
- %li{class: "todos-done #{todo_done_active}"}
- = link_to todos_filter_path(state: 'done') do
- %span
- Done
- %span.badge
- = number_with_delimiter(todos_done_count)
+- if current_user.todos.any?
+ .top-area
+ %ul.nav-links
+ - todo_pending_active = ('active' if params[:state].blank? || params[:state] == 'pending')
+ %li{class: "todos-pending #{todo_pending_active}"}
+ = link_to todos_filter_path(state: 'pending') do
+ %span
+ To do
+ %span.badge
+ = number_with_delimiter(todos_pending_count)
+ - todo_done_active = ('active' if params[:state] == 'done')
+ %li{class: "todos-done #{todo_done_active}"}
+ = link_to todos_filter_path(state: 'done') do
+ %span
+ Done
+ %span.badge
+ = number_with_delimiter(todos_done_count)
- .nav-controls
- - if @todos.any?(&:pending?)
- = link_to destroy_all_dashboard_todos_path(todos_filter_params), class: 'btn btn-loading js-todos-mark-all', method: :delete do
- Mark all as done
- = icon('spinner spin')
+ .nav-controls
+ - if @todos.any?(&:pending?)
+ = link_to destroy_all_dashboard_todos_path(todos_filter_params), class: 'btn btn-loading js-todos-mark-all', method: :delete do
+ Mark all as done
+ = icon('spinner spin')
-.todos-filters
- .row-content-block.second-block
- = form_tag todos_filter_path(without: [:project_id, :author_id, :type, :action_id]), method: :get, class: 'filter-form' do
- .filter-item.inline
- - if params[:project_id].present?
- = hidden_field_tag(:project_id, params[:project_id])
- = dropdown_tag(project_dropdown_label(params[:project_id], 'Project'), options: { toggle_class: 'js-project-search js-filter-submit', title: 'Filter by project', filter: true, filterInput: 'input#project-search', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit',
- placeholder: 'Search projects', data: { data: todo_projects_options } })
- .filter-item.inline
- - if params[:author_id].present?
- = hidden_field_tag(:author_id, params[:author_id])
- = dropdown_tag(user_dropdown_label(params[:author_id], 'Author'), options: { toggle_class: 'js-user-search js-filter-submit js-author-search', title: 'Filter by author', filter: true, filterInput: 'input#author-search', dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit',
- placeholder: 'Search authors', data: { any_user: 'Any Author', first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: 'author_id', default_label: 'Author' } })
- .filter-item.inline
- - if params[:type].present?
- = hidden_field_tag(:type, params[:type])
- = dropdown_tag(todo_types_dropdown_label(params[:type], 'Type'), options: { toggle_class: 'js-type-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-type js-filter-submit',
- data: { data: todo_types_options } })
- .filter-item.inline.actions-filter
- - if params[:action_id].present?
- = hidden_field_tag(:action_id, params[:action_id])
- = dropdown_tag(todo_actions_dropdown_label(params[:action_id], 'Action'), options: { toggle_class: 'js-action-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-action js-filter-submit',
- data: { data: todo_actions_options }})
- .pull-right
- .dropdown.inline.prepend-left-10
- %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
- %span.light
- - if @sort.present?
- = sort_options_hash[@sort]
- - else
- = sort_title_recently_created
- = icon('caret-down')
- %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort
- %li
- = link_to todos_filter_path(sort: sort_value_priority) do
- = sort_title_priority
- = link_to todos_filter_path(sort: sort_value_recently_created) do
+ .todos-filters
+ .row-content-block.second-block
+ = form_tag todos_filter_path(without: [:project_id, :author_id, :type, :action_id]), method: :get, class: 'filter-form' do
+ .filter-item.inline
+ - if params[:project_id].present?
+ = hidden_field_tag(:project_id, params[:project_id])
+ = dropdown_tag(project_dropdown_label(params[:project_id], 'Project'), options: { toggle_class: 'js-project-search js-filter-submit', title: 'Filter by project', filter: true, filterInput: 'input#project-search', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit',
+ placeholder: 'Search projects', data: { data: todo_projects_options } })
+ .filter-item.inline
+ - if params[:author_id].present?
+ = hidden_field_tag(:author_id, params[:author_id])
+ = dropdown_tag(user_dropdown_label(params[:author_id], 'Author'), options: { toggle_class: 'js-user-search js-filter-submit js-author-search', title: 'Filter by author', filter: true, filterInput: 'input#author-search', dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit',
+ placeholder: 'Search authors', data: { any_user: 'Any Author', first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: 'author_id', default_label: 'Author' } })
+ .filter-item.inline
+ - if params[:type].present?
+ = hidden_field_tag(:type, params[:type])
+ = dropdown_tag(todo_types_dropdown_label(params[:type], 'Type'), options: { toggle_class: 'js-type-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-type js-filter-submit',
+ data: { data: todo_types_options } })
+ .filter-item.inline.actions-filter
+ - if params[:action_id].present?
+ = hidden_field_tag(:action_id, params[:action_id])
+ = dropdown_tag(todo_actions_dropdown_label(params[:action_id], 'Action'), options: { toggle_class: 'js-action-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-action js-filter-submit',
+ data: { data: todo_actions_options }})
+ .pull-right
+ .dropdown.inline.prepend-left-10
+ %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
+ %span.light
+ - if @sort.present?
+ = sort_options_hash[@sort]
+ - else
= sort_title_recently_created
- = link_to todos_filter_path(sort: sort_value_oldest_created) do
- = sort_title_oldest_created
+ = icon('caret-down')
+ %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort
+ %li
+ = link_to todos_filter_path(sort: sort_value_priority) do
+ = sort_title_priority
+ = link_to todos_filter_path(sort: sort_value_recently_created) do
+ = sort_title_recently_created
+ = link_to todos_filter_path(sort: sort_value_oldest_created) do
+ = sort_title_oldest_created
.prepend-top-default
@@ -78,5 +79,29 @@
%ul.content-list.todos-list
= render group[1]
= paginate @todos, theme: "gitlab"
+ - elsif current_user.todos.any?
+ .todos-all-done
+ = render "shared/empty_states/todos_all_done.svg"
+ %h4.text-center
+ Good job! Looks like you don't have any todos left.
+ %p.text-center
+ Are you looking for things to do? Take a look at
+ = succeed "," do
+ = link_to "the opened issues", issues_dashboard_path
+ contribute to
+ = link_to "merge requests", merge_requests_dashboard_path
+ or mention someone in a comment to assign a new todo automatically.
- else
- .nothing-here-block You're all done!
+ .todos-empty
+ .todos-empty-hero
+ = render "shared/empty_states/todos_empty.svg"
+ .todos-empty-content
+ %h4
+ Todos let you see what you should do next.
+ %p
+ When an issue or merge request is assigned to you, or when you
+ %strong
+ @mention
+ in a comment, this will trigger a new item in your todo list, automatically.
+ %p
+ You will always know what to work on next.
diff --git a/app/views/shared/empty_states/_todos_all_done.svg b/app/views/shared/empty_states/_todos_all_done.svg
new file mode 100644
index 0000000000000000000000000000000000000000..94b5c2e0ea0928b6cb0c171b0147e1deb6ac1690
--- /dev/null
+++ b/app/views/shared/empty_states/_todos_all_done.svg
@@ -0,0 +1 @@
+
diff --git a/app/views/shared/empty_states/_todos_empty.svg b/app/views/shared/empty_states/_todos_empty.svg
new file mode 100644
index 0000000000000000000000000000000000000000..b1e661268fb12f8db313f9f44670dca94567daf0
--- /dev/null
+++ b/app/views/shared/empty_states/_todos_empty.svg
@@ -0,0 +1,110 @@
+
diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb
index 230543cd17548f9bff5725a5dad8fe1f1434d8d9..3ae83ac082d5ea3aff2496704cb2b1176e5607bf 100644
--- a/spec/features/todos/todos_spec.rb
+++ b/spec/features/todos/todos_spec.rb
@@ -13,7 +13,7 @@ describe 'Dashboard Todos', feature: true do
visit dashboard_todos_path
end
it 'shows "All done" message' do
- expect(page).to have_content "You're all done!"
+ expect(page).to have_content "Todos let you see what you should do next."
end
end
@@ -44,7 +44,7 @@ describe 'Dashboard Todos', feature: true do
end
it 'shows "All done" message' do
- expect(page).to have_content("You're all done!")
+ expect(page).to have_content("Good job! Looks like you don't have any todos left.")
end
end
@@ -64,7 +64,7 @@ describe 'Dashboard Todos', feature: true do
end
it 'shows "All done" message' do
- expect(page).to have_content("You're all done!")
+ expect(page).to have_content("Good job! Looks like you don't have any todos left.")
end
end
end
@@ -152,7 +152,7 @@ describe 'Dashboard Todos', feature: true do
within('.todos-pending-count') { expect(page).to have_content '0' }
expect(page).to have_content 'To do 0'
expect(page).to have_content 'Done 0'
- expect(page).to have_content "You're all done!"
+ expect(page).to have_content "Good job! Looks like you don't have any todos left."
end
end
end