UX: improve blank page syndrome on the user messages page (#14165)

UX: improve blank page syndrome on the user messages page (#14165)

The user-topic-list template is also in use in other places when we want to improve blank page syndrome, so this PR is a preparation for that changes as well.

diff --git a/app/assets/javascripts/discourse/app/controllers/user-topics-list.js b/app/assets/javascripts/discourse/app/controllers/user-topics-list.js
index 195adb4..6800f06 100644
--- a/app/assets/javascripts/discourse/app/controllers/user-topics-list.js
+++ b/app/assets/javascripts/discourse/app/controllers/user-topics-list.js
@@ -19,6 +19,12 @@ export default Controller.extend(BulkTopicSelection, {
   channel: null,
   tagsForUser: null,
   pmTopicTrackingState: null,
+  incomingCount: reads("pmTopicTrackingState.newIncoming.length"),
+
+  @discourseComputed("emptyState", "model.topics.length", "incomingCount")
+  showEmptyStatePlaceholder(emptyState, topicsLength, incomingCount) {
+    return emptyState && topicsLength === 0 && incomingCount === 0;
+  },
 
   saveScrollPosition() {
     this.session.set("topicListScrollPosition", $(window).scrollTop());
@@ -29,8 +35,6 @@ export default Controller.extend(BulkTopicSelection, {
     this.set("application.showFooter", !this.get("model.canLoadMore"));
   },
 
-  incomingCount: reads("pmTopicTrackingState.newIncoming.length"),
-
   @discourseComputed("filter", "model.topics.length")
   showResetNew(filter, hasTopics) {
     return filter === NEW_FILTER && hasTopics;
diff --git a/app/assets/javascripts/discourse/app/routes/build-private-messages-route.js b/app/assets/javascripts/discourse/app/routes/build-private-messages-route.js
index c2608d7..846408a 100644
--- a/app/assets/javascripts/discourse/app/routes/build-private-messages-route.js
+++ b/app/assets/javascripts/discourse/app/routes/build-private-messages-route.js
@@ -3,6 +3,8 @@ import UserAction from "discourse/models/user-action";
 import UserTopicListRoute from "discourse/routes/user-topic-list";
 import { findOrResetCachedTopicList } from "discourse/lib/cached-topic-list";
 import { action } from "@ember/object";
+import { iconHTML } from "discourse-common/lib/icon-library";
+import getURL from "discourse-common/lib/get-url";
 
 export const NEW_FILTER = "new";
 export const UNREAD_FILTER = "unread";
@@ -61,6 +63,7 @@ export default (inboxType, path, filter) => {
         inbox: inboxType,
         pmTopicTrackingState:
           userPrivateMessagesController.pmTopicTrackingState,
+        emptyState: this.emptyState(),
       });
 
       userTopicsListController.subscribe();
@@ -74,6 +77,15 @@ export default (inboxType, path, filter) => {
       this.searchService.set("contextType", "private_messages");
     },
 
+    emptyState() {
+      const title = I18n.t("user.no_messages_title");
+      const body = I18n.t("user.no_messages_body", {
+        aboutUrl: getURL("/about"),
+        icon: iconHTML("envelope"),
+      }).htmlSafe();
+      return { title, body };
+    },
+
     deactivate() {
       this.controllerFor("user-topics-list").unsubscribe();
 
diff --git a/app/assets/javascripts/discourse/app/templates/user-topics-list.hbs b/app/assets/javascripts/discourse/app/templates/user-topics-list.hbs
index 4e9d4d1..7096fec 100644
--- a/app/assets/javascripts/discourse/app/templates/user-topics-list.hbs
+++ b/app/assets/javascripts/discourse/app/templates/user-topics-list.hbs
@@ -1,11 +1,19 @@
-{{#unless site.mobileView}}
-  {{#if showToggleBulkSelect}}
-    {{bulk-select-button canDoBulkActions=true selected=selected action=(route-action "refresh")}}
-  {{/if}}
-{{/unless}}
+{{#if showEmptyStatePlaceholder}}
+  <div class="empty-state">
+    <span class="empty-state-title">{{emptyState.title}}</span>
+    <div class="empty-state-body">
+      <p>{{emptyState.body}}</p>
+    </div>
+  </div>
+{{else}}
+  {{#unless site.mobileView}}
+    {{#if showToggleBulkSelect}}
+      {{bulk-select-button canDoBulkActions=true selected=selected action=(route-action "refresh")}}
+    {{/if}}
+  {{/unless}}
 
-{{#load-more class="paginated-topics-list" selector=".paginated-topics-list .topic-list tr" action=(action "loadMore")}}
-  {{topic-dismiss-buttons
+  {{#load-more class="paginated-topics-list" selector=".paginated-topics-list .topic-list tr" action=(action "loadMore")}}
+    {{topic-dismiss-buttons
       position="top"
       selectedTopics=selected
       model=model
@@ -13,27 +21,27 @@
       showDismissRead=showDismissRead
       resetNew=(action "resetNew")}}
 
-  {{#if (gt incomingCount 0)}}
-    <div class="show-mores">
-      <a tabindex="0" href {{action "showInserted"}} class="alert alert-info clickable">
-        {{count-i18n key="topic_count_" suffix="latest" count=incomingCount}}
-      </a>
-    </div>
-  {{/if}}
+    {{#if (gt incomingCount 0)}}
+      <div class="show-mores">
+        <a tabindex="0" href {{action "showInserted"}} class="alert alert-info clickable">
+          {{count-i18n key="topic_count_" suffix="latest" count=incomingCount}}
+        </a>
+      </div>
+    {{/if}}
 
-  {{basic-topic-list topicList=model
-                     hideCategory=hideCategory
-                     showPosters=showPosters
-                     bulkSelectEnabled=bulkSelectEnabled
-                     selected=selected
-                     tagsForUser=tagsForUser
-                     onScroll=saveScrollPosition
-                     canBulkSelect=canBulkSelect
-                     scrollOnLoad=true
-                     toggleBulkSelect=(action "toggleBulkSelect")
-                     updateAutoAddTopicsToBulkSelect=(action "updateAutoAddTopicsToBulkSelect")}}
+    {{basic-topic-list topicList=model
+       hideCategory=hideCategory
+       showPosters=showPosters
+       bulkSelectEnabled=bulkSelectEnabled
+       selected=selected
+       tagsForUser=tagsForUser
+       onScroll=saveScrollPosition
+       canBulkSelect=canBulkSelect
+       scrollOnLoad=true
+       toggleBulkSelect=(action "toggleBulkSelect")
+       updateAutoAddTopicsToBulkSelect=(action "updateAutoAddTopicsToBulkSelect")}}
 
-  {{topic-dismiss-buttons
+    {{topic-dismiss-buttons
       position="bottom"
       selectedTopics=selected
       model=model
@@ -41,5 +49,6 @@
       showDismissRead=showDismissRead
       resetNew=(action "resetNew")}}
 
-  {{conditional-loading-spinner condition=model.loadingMore}}
-{{/load-more}}
+    {{conditional-loading-spinner condition=model.loadingMore}}
+  {{/load-more}}
+{{/if}}
diff --git a/app/assets/javascripts/discourse/tests/acceptance/user-private-messages-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-private-messages-test.js
index dac9e7d..d1891a8 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/user-private-messages-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/user-private-messages-test.js
@@ -546,3 +546,46 @@ acceptance(
     });
   }
 );
+
+acceptance("User Private Messages - user with no messages", function (needs) {
+  needs.user();
+
+  needs.pretender((server, helper) => {
+    const emptyResponse = {
+      topic_list: {
+        topics: [],
+      },
+    };
+
+    const apiUrls = [
+      "/topics/private-messages-all/:username.json",
+      "/topics/private-messages-all-sent/:username.json",
+      "/topics/private-messages-all-new/:username.json",
+      "/topics/private-messages-all-unread/:username.json",
+      "/topics/private-messages-all-archive/:username.json",
+    ];
+
+    apiUrls.forEach((url) => {
+      server.get(url, () => {
+        return helper.response(emptyResponse);
+      });
+    });
+  });
+
+  test("It renders the empty state panel", async function (assert) {
+    await visit("/u/charlie/messages");
+    assert.ok(exists("div.empty-state"));
+
+    await visit("/u/charlie/messages/sent");
+    assert.ok(exists("div.empty-state"));
+
+    await visit("/u/charlie/messages/new");
+    assert.ok(exists("div.empty-state"));
+
+    await visit("/u/charlie/messages/unread");
+    assert.ok(exists("div.empty-state"));
+
+    await visit("/u/charlie/messages/archive");
+    assert.ok(exists("div.empty-state"));
+  });
+});

GitHub sha: 9415fecfd05af41fa953f6d3eea98415e4ac77b4

This commit appears in #14165 which was approved by eviltrout. It was merged by AndrewPrigorshnev.