Allow some badges to be awarded multiple times (#20876)

* Add migration, update badgeachievements model and admin view to set flag on badges

* Show multiple badges on profile

* update factory

* badge model validation

* safe navigation to badge?

* add null: false to migratrion
This commit is contained in:
Philip How 2024-04-23 21:33:43 +01:00 committed by GitHub
parent c44a2dcdb0
commit 3ceb9a178a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 57 additions and 7 deletions

View file

@ -179,3 +179,16 @@
border-bottom: unset;
}
}
.badge-indicator {
font-size: var(--fs-s);
color: var(--base-0);
width: var(--su-6);
height: var(--su-6);
line-height: var(--su-6);
position: absolute;
background-color: var(--accent-brand);
right: var(--su-1);
bottom: -5px;
border-radius: 100%;
}

View file

@ -41,7 +41,7 @@ module Admin
private
def badge_params
params.require(:badge).permit(:title, :description, :badge_image, :credits_awarded)
params.require(:badge).permit(:title, :description, :badge_image, :credits_awarded, :allow_multiple_awards)
end
end
end

View file

@ -10,6 +10,7 @@ class Badge < ApplicationRecord
validates :description, presence: true
validates :slug, presence: true
validates :title, presence: true, uniqueness: true
validates :allow_multiple_awards, inclusion: { in: [true, false] }
before_validation :generate_slug

View file

@ -14,7 +14,7 @@ class BadgeAchievement < ApplicationRecord
counter_culture :user, column_name: "badge_achievements_count"
validates :badge_id, uniqueness: { scope: :user_id }
validates :badge_id, uniqueness: { scope: :user_id, if: :single_award_badge? }
before_validation :render_rewarding_context_message_html
after_create :award_credits
@ -53,4 +53,8 @@ class BadgeAchievement < ApplicationRecord
Credit.add_to(user, badge.credits_awarded)
end
def single_award_badge?
badge&.allow_multiple_awards == false
end
end

View file

@ -33,6 +33,14 @@
<%= form.text_field :credits_awarded, class: "crayons-textfield" %>
</div>
<div class="crayons-field crayons-field--checkbox ">
<%= form.check_box :allow_multiple_awards, class: "crayons-checkbox" %>
<%= form.label :allow_multiple_awards, class: "crayons-field__label" do %>
Allow multiple awards
<p class="crayons-field__description">Allows this badge to be awarded multiple times to the same user</p>
<% end %>
</div>
<div>
<%= submit_tag "Update badge", class: "c-btn c-btn--primary" %>
</div>

View file

@ -22,6 +22,15 @@
<%= form.text_field :credits_awarded, class: "crayons-textfield" %>
</div>
<div class="crayons-field crayons-field--checkbox ">
<%= form.check_box :allow_multiple_awards, class: "crayons-checkbox" %>
<%= form.label :allow_multiple_awards, class: "crayons-field__label" do %>
Allow multiple awards
<p class="crayons-field__description">Allows this badge to be awarded multiple times to the same user</p>
<% end %>
</div>
<div>
<%= submit_tag "Create Badge", class: "c-btn c-btn--primary" %>
</div>

View file

@ -5,15 +5,17 @@
<div class="crayons-card__body">
<div class="grid gap-4 grid-cols-3 s:grid-cols-4 m:grid-cols-2 align-center items-center js-profile-badges">
<% achievements.each_with_index do |achievement, i| %>
<% achievements.group_by(&:badge_id).each_with_index do |(badge_id, badge_achievements), i| %>
<% achievement = badge_achievements.first %>
<div role="button" onclick="window.Forem.showModal({size: 'medium', showHeader: false, contentSelector: '#badge-<%= achievement.badge_id %>', overlay: true})"
title="<%= achievement.badge_title %>"
class="js-profile-badge <% if i >= limit %>hidden<% end %>">
class="js-profile-badge <% if i >= limit %>hidden<% end %> relative">
<img src="<%= optimized_image_url(achievement.badge_image_url, width: 180) %>"
alt="<%= achievement.badge_title %>"
class="mx-auto max-w-75 h-auto align-middle"
style="object-fit: contain; cursor: pointer; width: 100%; aspect-ratio: 1 / 1; transform: rotate(<%= rand(-10..10) %>deg);"
style="object-fit: contain; cursor: pointer; width: 100%; aspect-ratio: 1 / 1;"
loading="lazy" />
<% if badge_achievements.length > 1 %><div class="badge-indicator" title="<%= t("views.badges.indicator", user: @user.name, number: badge_achievements.length) %>"><%= badge_achievements.length %></div><% end %>
</div>
<div id="badge-<%= achievement.badge_id %>" class="hidden">

View file

@ -11,6 +11,7 @@ en:
desc: Receive badges for being awesome! Click on a badge to see how you can earn it.
icon_close: Close
gotit: Got it
indicator: "%{user} earned this badge %{number} times."
campaign:
subtitle: Stories (%{count})
view: See all posts

View file

@ -11,6 +11,7 @@ fr:
desc: Receive badges for being awesome! Click on a badge to see how you can earn it.
icon_close: Close
gotit: Got it
indicator: "%{user} a obtenu ce badge %{number} fois."
campaign:
subtitle: Stories (%{count})
view: See all posts

View file

@ -0,0 +1,10 @@
class AddAllowMultipleAwardsToBadges < ActiveRecord::Migration[7.0]
disable_ddl_transaction!
def change
add_column :badges, :allow_multiple_awards, :boolean, null: false, default: false
# remove unique index
remove_index :badge_achievements, column: [:badge_id, :user_id], algorithm: :concurrently if index_exists?(:badge_achievements, [:badge_id, :user_id])
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2024_04_08_101038) do
ActiveRecord::Schema[7.0].define(version: 2024_04_19_201108) do
# These are extensions that must be enabled in order to support this database
enable_extension "citext"
enable_extension "ltree"
@ -207,11 +207,11 @@ ActiveRecord::Schema[7.0].define(version: 2024_04_08_101038) do
t.text "rewarding_context_message_markdown"
t.datetime "updated_at", precision: nil, null: false
t.bigint "user_id", null: false
t.index ["badge_id", "user_id"], name: "index_badge_achievements_on_badge_id_and_user_id", unique: true
t.index ["user_id", "badge_id"], name: "index_badge_achievements_on_user_id_and_badge_id"
end
create_table "badges", force: :cascade do |t|
t.boolean "allow_multiple_awards", default: false, null: false
t.string "badge_image"
t.datetime "created_at", precision: nil, null: false
t.integer "credits_awarded", default: 0, null: false

View file

@ -5,5 +5,6 @@ FactoryBot.define do
sequence(:title) { |n| "#{Faker::Book.title}-#{n}" }
description { Faker::Lorem.sentence }
badge_image { Rack::Test::UploadedFile.new(image_path, "image/jpeg") }
allow_multiple_awards { false }
end
end