FEATURE: lets users favorite 2 badges to show on user-card (#13151)

FEATURE: lets users favorite 2 badges to show on user-card (#13151)

diff --git a/app/assets/javascripts/discourse/app/components/badge-card.js b/app/assets/javascripts/discourse/app/components/badge-card.js
index 6e2a862..d1da83e 100644
--- a/app/assets/javascripts/discourse/app/components/badge-card.js
+++ b/app/assets/javascripts/discourse/app/components/badge-card.js
@@ -31,4 +31,9 @@ export default Component.extend({
     }
     return sanitize(description);
   },
+
+  @discourseComputed("badge.id")
+  showFavorite(badgeId) {
+    return ![1, 2, 3, 4].includes(badgeId);
+  },
 });
diff --git a/app/assets/javascripts/discourse/app/controllers/user-badges.js b/app/assets/javascripts/discourse/app/controllers/user-badges.js
index a4d5504..8634e19 100644
--- a/app/assets/javascripts/discourse/app/controllers/user-badges.js
+++ b/app/assets/javascripts/discourse/app/controllers/user-badges.js
@@ -1,14 +1,27 @@
 import Controller, { inject as controller } from "@ember/controller";
-import { alias, sort } from "@ember/object/computed";
+import { action, computed } from "@ember/object";
+import { alias, filterBy, sort } from "@ember/object/computed";
 
 export default Controller.extend({
   user: controller(),
   username: alias("user.model.username_lower"),
   sortedBadges: sort("model", "badgeSortOrder"),
+  favoriteBadges: filterBy("model", "is_favorite", true),
+  canFavoriteMoreBadges: computed(
+    "favoriteBadges.length",
+    "model.meta.max_favorites",
+    function () {
+      return this.favoriteBadges.length < this.model.meta.max_favorites;
+    }
+  ),
 
   init() {
     this._super(...arguments);
-
     this.badgeSortOrder = ["badge.badge_type.sort_order:desc", "badge.name"];
   },
+
+  @action
+  favorite(badge) {
+    return badge.favorite();
+  },
 });
diff --git a/app/assets/javascripts/discourse/app/models/user-badge.js b/app/assets/javascripts/discourse/app/models/user-badge.js
index 0aaff3f..a4c0903 100644
--- a/app/assets/javascripts/discourse/app/models/user-badge.js
+++ b/app/assets/javascripts/discourse/app/models/user-badge.js
@@ -4,8 +4,11 @@ import { Promise } from "rsvp";
 import Topic from "discourse/models/topic";
 import User from "discourse/models/user";
 import { ajax } from "discourse/lib/ajax";
+import { popupAjaxError } from "discourse/lib/ajax-error";
 import discourseComputed from "discourse-common/utils/decorators";
 
+const DEFAULT_USER_BADGES_META = { max_favorites: 2 };
+
 const UserBadge = EmberObject.extend({
   @discourseComputed
   postUrl: function () {
@@ -19,6 +22,15 @@ const UserBadge = EmberObject.extend({
       type: "DELETE",
     });
   },
+
+  favorite() {
+    return ajax(`/user_badges/${this.id}/toggle_favorite`, { type: "PUT" })
+      .then((json) => {
+        this.set("is_favorite", json.user_badge.is_favorite);
+        return this;
+      })
+      .catch(popupAjaxError);
+  },
 });
 
 UserBadge.reopenClass({
@@ -86,6 +98,7 @@ UserBadge.reopenClass({
         userBadges.grant_count = json.user_badge_info.grant_count;
         userBadges.username = json.user_badge_info.username;
       }
+      userBadges.meta = json.meta || DEFAULT_USER_BADGES_META;
       return userBadges;
     }
   },
diff --git a/app/assets/javascripts/discourse/app/templates/components/badge-card.hbs b/app/assets/javascripts/discourse/app/templates/components/badge-card.hbs
index 415e473..fc05530 100644
--- a/app/assets/javascripts/discourse/app/templates/components/badge-card.hbs
+++ b/app/assets/javascripts/discourse/app/templates/components/badge-card.hbs
@@ -4,7 +4,26 @@
 {{#if badge.has_badge}}
   <a href={{url}} class="check-display status-checked">{{d-icon "check"}}</a>
 {{/if}}
-<div class="badge-contents">
+
+{{#if canFavorite}}
+  {{#if isFavorite}}
+    {{d-button
+      icon="star"
+      class="favorite-btn"
+      action=onFavoriteClick
+    }}
+  {{else}}
+    {{d-button
+      icon="far-star"
+      class="favorite-btn"
+      action=onFavoriteClick
+      title=(if canFavoriteMoreBadges "badges.favorite_max_not_reached" "badges.favorite_max_reached")
+      disabled=(not canFavoriteMoreBadges)
+    }}
+  {{/if}}
+{{/if}}
+
+<div class="badge-contents" >
   <div class="badge-icon {{badge.badgeTypeClassName}}">
     <a href={{url}}>{{icon-or-image badge}}</a>
   </div>
diff --git a/app/assets/javascripts/discourse/app/templates/user/badges.hbs b/app/assets/javascripts/discourse/app/templates/user/badges.hbs
index 878a2f6..b60d102 100644
--- a/app/assets/javascripts/discourse/app/templates/user/badges.hbs
+++ b/app/assets/javascripts/discourse/app/templates/user/badges.hbs
@@ -1,9 +1,16 @@
 {{#d-section pageClass="user-badges" class="user-content user-badges-list"}}
+  <p class="favorite-count">
+    {{i18n "badges.favorite_count" count=this.favoriteBadges.length max=model.meta.max_favorites}}
+  </p>
   {{#each sortedBadges as |ub|}}
     {{badge-card
       badge=ub.badge
       count=ub.count
+      canFavorite=ub.can_favorite
+      isFavorite=ub.is_favorite
       username=username
+      canFavoriteMoreBadges=canFavoriteMoreBadges
+      onFavoriteClick=(action "favorite" ub)
       filterUser="true"}}
   {{/each}}
 {{/d-section}}
diff --git a/app/assets/javascripts/discourse/tests/helpers/create-pretender.js b/app/assets/javascripts/discourse/tests/helpers/create-pretender.js
index 7cbea9c..3398721 100644
--- a/app/assets/javascripts/discourse/tests/helpers/create-pretender.js
+++ b/app/assets/javascripts/discourse/tests/helpers/create-pretender.js
@@ -576,6 +576,9 @@ export function applyDefaultHandlers(pretender) {
     response(200, fixturesByUrl["/user_badges"])
   );
   pretender.delete("/user_badges/:badge_id", success);
+  pretender.put("/user_badges/:id/toggle_favorite", () =>
+    response(200, { user_badge: { is_favorite: true } })
+  );
 
   pretender.post("/posts", function (request) {
     const data = parsePostData(request.requestBody);
diff --git a/app/assets/javascripts/discourse/tests/unit/models/user-badge-test.js b/app/assets/javascripts/discourse/tests/unit/models/user-badge-test.js
index 2825778..70e3073 100644
--- a/app/assets/javascripts/discourse/tests/unit/models/user-badge-test.js
+++ b/app/assets/javascripts/discourse/tests/unit/models/user-badge-test.js
@@ -57,4 +57,12 @@ module("Unit | Model | user-badge", function () {
     const userBadge = UserBadge.create({ id: 1 });
     await userBadge.revoke();
   });
+
+  test("favorite", async function (assert) {
+    const userBadge = UserBadge.create({ id: 1 });
+    assert.notOk(userBadge.is_favorite);
+
+    await userBadge.favorite();
+    assert.ok(userBadge.is_favorite);
+  });
 });
diff --git a/app/assets/stylesheets/common/base/user-badges.scss b/app/assets/stylesheets/common/base/user-badges.scss
index 09cce81..cbb52c5 100644
--- a/app/assets/stylesheets/common/base/user-badges.scss
+++ b/app/assets/stylesheets/common/base/user-badges.scss
@@ -143,6 +143,12 @@
     font-size: $font-up-2;
   }
 
+  .favorite-btn {
+    position: absolute;
+    right: 0;
+    bottom: 0;
+  }
+
   .badge-contents {
     display: flex;
     min-height: 128px;
diff --git a/app/assets/stylesheets/desktop/user.scss b/app/assets/stylesheets/desktop/user.scss
index b1dd306..9c9d216 100644
--- a/app/assets/stylesheets/desktop/user.scss
+++ b/app/assets/stylesheets/desktop/user.scss
@@ -60,6 +60,10 @@
     flex-wrap: wrap;
   }
 
+  &.user-badges-list .favorite-count {
+    flex: 100%;
+  }
+
   .btn.right {
     float: right;
   }
diff --git a/app/controllers/user_badges_controller.rb b/app/controllers/user_badges_controller.rb
index 05d0a69..1b13f0c 100644
--- a/app/controllers/user_badges_controller.rb
+++ b/app/controllers/user_badges_controller.rb
@@ -1,13 +1,16 @@
 # frozen_string_literal: true
 
 class UserBadgesController < ApplicationController
+  MAX_FAVORITES = 2
+  MAX_BADGES = 96 # This was limited in PR#2360 to make it divisible by 8
+
   before_action :ensure_badges_enabled
 
   def index
     params.permit [:granted_before, :offset, :username]
 
     badge = fetch_badge_from_params

[... diff too long, it was truncated ...]

GitHub sha: 1cd0424c

This commit appears in #13151 which was approved by ZogStriP. It was merged by jjaffeux.