From da3e72c2b4ec9c717321b30ccb65b46da930e1d5 Mon Sep 17 00:00:00 2001 From: Alan Guo Xiang Tan Date: Wed, 21 Sep 2022 12:32:47 +0800 Subject: [PATCH] DEV: Ship first pass of new user page navigation behind feature flag (#18285) This commits introduces a new SiteSetting.enable_new_user_profile_nav_groups feature flag. When configured, users of the configured groups will see the new user page navigation links. As of this commit, only the user activity navigation link has been converted to the newly proposed dropdown of navigation links. Mobile support has not been considered. --- .../discourse/app/components/user-nav.hbs | 100 ++++++++++++++++++ .../discourse/app/components/user-nav.js | 18 ++++ .../app/components/user-nav/dropdown-list.hbs | 18 ++++ .../app/components/user-nav/dropdown-list.js | 54 ++++++++++ .../discourse/app/controllers/user.js | 4 +- .../discourse/app/templates/user.hbs | 98 ++++++++++------- .../discourse/app/templates/user/activity.hbs | 71 +++++++------ .../stylesheets/common/base/_index.scss | 1 + .../stylesheets/common/base/new-user.scss | 81 ++++++++++++++ .../stylesheets/common/components/navs.scss | 6 +- app/serializers/current_user_serializer.rb | 12 ++- config/site_settings.yml | 8 ++ .../current_user_serializer_spec.rb | 22 ++++ 13 files changed, 414 insertions(+), 79 deletions(-) create mode 100644 app/assets/javascripts/discourse/app/components/user-nav.hbs create mode 100644 app/assets/javascripts/discourse/app/components/user-nav.js create mode 100644 app/assets/javascripts/discourse/app/components/user-nav/dropdown-list.hbs create mode 100644 app/assets/javascripts/discourse/app/components/user-nav/dropdown-list.js create mode 100644 app/assets/stylesheets/common/base/new-user.scss diff --git a/app/assets/javascripts/discourse/app/components/user-nav.hbs b/app/assets/javascripts/discourse/app/components/user-nav.hbs new file mode 100644 index 00000000000..e13b2b0b604 --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/user-nav.hbs @@ -0,0 +1,100 @@ +
+ +
diff --git a/app/assets/javascripts/discourse/app/components/user-nav.js b/app/assets/javascripts/discourse/app/components/user-nav.js new file mode 100644 index 00000000000..d03668ac62c --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/user-nav.js @@ -0,0 +1,18 @@ +import I18n from "I18n"; + +import Component from "@glimmer/component"; +import { inject as service } from "@ember/service"; + +export default class UserNav extends Component { + @service currentUser; + @service site; + @service router; + + get draftLabel() { + const count = this.currentUser.draft_count; + + return count > 0 + ? I18n.t("drafts.label_with_count", { count }) + : I18n.t("drafts.label"); + } +} diff --git a/app/assets/javascripts/discourse/app/components/user-nav/dropdown-list.hbs b/app/assets/javascripts/discourse/app/components/user-nav/dropdown-list.hbs new file mode 100644 index 00000000000..7839afe4a8c --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/user-nav/dropdown-list.hbs @@ -0,0 +1,18 @@ +
  • + + + {{#if (and (has-block "submenu") this.displayList)}} +
    + +
      + {{yield to="submenu"}} +
    +
    + {{/if}} +
  • diff --git a/app/assets/javascripts/discourse/app/components/user-nav/dropdown-list.js b/app/assets/javascripts/discourse/app/components/user-nav/dropdown-list.js new file mode 100644 index 00000000000..0e6d095ca26 --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/user-nav/dropdown-list.js @@ -0,0 +1,54 @@ +import Component from "@glimmer/component"; +import { action } from "@ember/object"; +import { tracked } from "@glimmer/tracking"; +import { bind } from "discourse-common/utils/decorators"; + +export default class UserNavDropdownList extends Component { + @tracked displayList = false; + + get chevron() { + return this.displayList ? "chevron-up" : "chevron-down"; + } + + get defaultButtonClass() { + return "user-nav-dropdown-button"; + } + + get buttonClass() { + const props = [this.defaultButtonClass]; + + if (this.args.isActive) { + props.push("active"); + } + + return props.join(" "); + } + + @action + toggleList() { + this.displayList = !this.displayList; + } + + @bind + collapseList(e) { + const isClickOnButton = e.composedPath().some((element) => { + if (element?.classList?.contains(this.defaultButtonClass)) { + return true; + } + }); + + if (!isClickOnButton) { + this.displayList = false; + } + } + + @action + registerClickListener() { + document.addEventListener("click", this.collapseList); + } + + @action + deregisterClickListener() { + document.removeEventListener("click", this.collapseList); + } +} diff --git a/app/assets/javascripts/discourse/app/controllers/user.js b/app/assets/javascripts/discourse/app/controllers/user.js index d1e053ef347..47d2b1c59d9 100644 --- a/app/assets/javascripts/discourse/app/controllers/user.js +++ b/app/assets/javascripts/discourse/app/controllers/user.js @@ -1,6 +1,6 @@ import Controller, { inject as controller } from "@ember/controller"; import EmberObject, { computed, set } from "@ember/object"; -import { and, equal, gt, not, or } from "@ember/object/computed"; +import { and, equal, gt, not, or, readOnly } from "@ember/object/computed"; import CanCheckEmails from "discourse/mixins/can-check-emails"; import User from "discourse/models/user"; import I18n from "I18n"; @@ -164,6 +164,8 @@ export default Controller.extend(CanCheckEmails, { } }, + currentParentRoute: readOnly("router.currentRoute.parent.name"), + userNotificationLevel: computed( "currentUser.ignored_ids", "model.ignored", diff --git a/app/assets/javascripts/discourse/app/templates/user.hbs b/app/assets/javascripts/discourse/app/templates/user.hbs index 47dae2d8a3c..489af32e937 100644 --- a/app/assets/javascripts/discourse/app/templates/user.hbs +++ b/app/assets/javascripts/discourse/app/templates/user.hbs @@ -224,47 +224,63 @@ {{/unless}} -
    -
    - - {{#unless this.model.profile_hidden}} -
  • - - {{d-icon "user"}} - {{i18n 'user.summary.title'}} - -
  • -
  • - - {{d-icon "stream"}} - {{i18n 'user.activity_stream'}} - -
  • - {{/unless}} - {{#if this.showNotificationsTab}} -
  • - - {{d-icon "comment" class="glyph"}}{{i18n 'user.notifications'}} - -
  • - {{/if}} - {{#if this.showPrivateMessages}} -
  • {{d-icon "far-envelope"}}{{i18n 'user.private_messages'}}
  • - {{/if}} - {{#if this.canInviteToForum}} -
  • {{d-icon "user-plus"}}{{i18n 'user.invited.title'}}
  • - {{/if}} - {{#if this.showBadges}} -
  • {{d-icon "certificate"}}{{i18n 'badges.title'}}
  • - {{/if}} - - {{#if this.model.can_edit}} -
  • {{d-icon "cog"}}{{i18n 'user.preferences'}}
  • - {{/if}} -
    -
    - {{outlet}} -
    + {{#if this.currentUser.redesigned_user_page_nav_enabled}} +
    + + +
    + {{outlet}} +
    +
    + {{else}} +
    +
    + + {{#unless this.model.profile_hidden}} +
  • {{i18n 'user.summary.title'}}
  • +
  • {{i18n 'user.activity_stream'}}
  • + {{/unless}} + + {{#if this.showNotificationsTab}} +
  • + + {{d-icon "comment" class="glyph"}}{{i18n 'user.notifications'}} + +
  • + {{/if}} + + {{#if this.showPrivateMessages}} +
  • {{d-icon "far-envelope"}}{{i18n 'user.private_messages'}}
  • + {{/if}} + + {{#if this.canInviteToForum}} +
  • {{d-icon "user-plus"}}{{i18n 'user.invited.title'}}
  • + {{/if}} + + {{#if this.showBadges}} +
  • {{d-icon "certificate"}}{{i18n 'badges.title'}}
  • + {{/if}} + + + + {{#if this.model.can_edit}} +
  • {{d-icon "cog"}}{{i18n 'user.preferences'}}
  • + {{/if}} +
    +
    + + {{outlet}} +
    + {{/if}} diff --git a/app/assets/javascripts/discourse/app/templates/user/activity.hbs b/app/assets/javascripts/discourse/app/templates/user/activity.hbs index 5709d95e79d..a115f1cf064 100644 --- a/app/assets/javascripts/discourse/app/templates/user/activity.hbs +++ b/app/assets/javascripts/discourse/app/templates/user/activity.hbs @@ -1,43 +1,46 @@ - - + + + {{#if this.canDownloadPosts}} +
    + +
    + {{/if}} +{{/unless}}
    {{outlet}} diff --git a/app/assets/stylesheets/common/base/_index.scss b/app/assets/stylesheets/common/base/_index.scss index dff01e90eb0..0af5ac85810 100644 --- a/app/assets/stylesheets/common/base/_index.scss +++ b/app/assets/stylesheets/common/base/_index.scss @@ -32,6 +32,7 @@ @import "magnific-popup"; @import "menu-panel"; @import "modal"; +@import "new-user"; @import "not-found"; @import "onebox"; @import "personal-message"; diff --git a/app/assets/stylesheets/common/base/new-user.scss b/app/assets/stylesheets/common/base/new-user.scss new file mode 100644 index 00000000000..f19e96f5b50 --- /dev/null +++ b/app/assets/stylesheets/common/base/new-user.scss @@ -0,0 +1,81 @@ +.new-user-wrapper { + .new-user-content-wrapper { + // Grid layout + width: 100%; + display: grid; + grid-template-columns: 1fr 5fr; + grid-template-rows: auto 1fr; + grid-gap: 20px; + + .user-secondary-navigation { + grid-column-start: 1; + grid-column-end: 2; + grid-row-start: 1; + grid-row-end: 2; + } + + .user-content { + grid-column-start: 1; + grid-column-end: 3; + grid-row-start: 1; + grid-row-end: 3; + } + + .user-additional-controls { + align-self: start; + justify-self: start; + grid-row-start: 2; + } + + .user-secondary-navigation ~ .user-content { + grid-column-start: 2; + grid-column-end: 3; + } + } + + .user-nav-dropdown-list-item { + position: relative; + } + + .user-nav-dropdown-button { + background: transparent; + } + + .user-nav-dropdown-submenu-wrapper { + position: absolute; + top: 2em; + min-width: 10em; + padding: 0; + box-shadow: shadow("dropdown"); + z-index: z("dropdown"); + } + + .user-nav-dropdown-submenu { + background: var(--secondary); + list-style-type: none; + margin: 0; + + li a { + padding: 0.5em 1em; + color: var(--primary); + .discourse-no-touch & { + &:hover { + background: var(--highlight-medium); + color: currentColor; + } + } + + &.active { + background: var(--tertiary-low); + color: currentColor; + } + + &:first-of-type { + padding-top: 0.5em; + } + &:last-of-type { + padding-bottom: 0.5em; + } + } + } +} diff --git a/app/assets/stylesheets/common/components/navs.scss b/app/assets/stylesheets/common/components/navs.scss index 49a6a767fdc..4e0f2b2a521 100644 --- a/app/assets/stylesheets/common/components/navs.scss +++ b/app/assets/stylesheets/common/components/navs.scss @@ -29,7 +29,8 @@ display: flex; margin-right: 0.5em; - > a { + > a, + button { border: none; padding: 6px 12px; color: var(--primary); @@ -52,7 +53,8 @@ } } - a.active { + a.active, + button.active { color: var(--secondary); background-color: var(--quaternary); diff --git a/app/serializers/current_user_serializer.rb b/app/serializers/current_user_serializer.rb index 64597982ae5..ead635ea801 100644 --- a/app/serializers/current_user_serializer.rb +++ b/app/serializers/current_user_serializer.rb @@ -78,13 +78,15 @@ class CurrentUserSerializer < BasicUserSerializer :sidebar_category_ids, :likes_notifications_disabled, :grouped_unread_notifications, - :redesigned_user_menu_enabled + :redesigned_user_menu_enabled, + :redesigned_user_page_nav_enabled delegate :user_stat, to: :object, private: true delegate :any_posts, :draft_count, :pending_posts_count, :read_faq?, to: :user_stat def groups owned_group_ids = GroupUser.where(user_id: id, owner: true).pluck(:group_id).to_set + object.visible_groups.pluck(:id, :name, :has_messages).map do |id, name, has_messages| group = { id: id, name: name, has_messages: has_messages } group[:owner] = true if owned_group_ids.include?(id) @@ -342,4 +344,12 @@ class CurrentUserSerializer < BasicUserSerializer def include_unseen_reviewable_count? redesigned_user_menu_enabled end + + def redesigned_user_page_nav_enabled + if SiteSetting.enable_new_user_profile_nav_groups.present? + GroupUser.exists?(user_id: object.id, group_id: SiteSetting.enable_new_user_profile_nav_groups.split("|")) + else + false + end + end end diff --git a/config/site_settings.yml b/config/site_settings.yml index 40663a80a19..ba7d2671ea9 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -2002,6 +2002,14 @@ developer: type: tag_list default: "" client: true + enable_new_user_profile_nav_groups: + client: true + type: group_list + list_type: compact + default: "" + allow_any: false + refresh: true + hidden: true embedding: embed_by_username: diff --git a/spec/serializers/current_user_serializer_spec.rb b/spec/serializers/current_user_serializer_spec.rb index 7e5a79038b8..b8f3ced1819 100644 --- a/spec/serializers/current_user_serializer_spec.rb +++ b/spec/serializers/current_user_serializer_spec.rb @@ -350,4 +350,26 @@ RSpec.describe CurrentUserSerializer do expect(serializer.as_json[:likes_notifications_disabled]).to eq(false) end end + + describe '#redesigned_user_page_nav_enabled' do + fab!(:group) { Fabricate(:group) } + fab!(:group2) { Fabricate(:group) } + + it "is false when enable_new_user_profile_nav_groups site setting has not been set" do + expect(serializer.as_json[:redesigned_user_page_nav_enabled]).to eq(false) + end + + it 'is false if user does not belong to any of the configured groups in the enable_new_user_profile_nav_groups site setting' do + SiteSetting.enable_new_user_profile_nav_groups = "#{group.id}|#{group2.id}" + + expect(serializer.as_json[:redesigned_user_page_nav_enabled]).to eq(false) + end + + it 'is true if user belongs one of the configured groups in the enable_new_user_profile_nav_groups site setting' do + SiteSetting.enable_new_user_profile_nav_groups = "#{group.id}|#{group2.id}" + group.add(user) + + expect(serializer.as_json[:redesigned_user_page_nav_enabled]).to eq(true) + end + end end