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)}} +
    + + +
    + {{/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