Files
discourse/frontend/discourse/app/components/modal/grant-badge.gjs
T
Jarek Radosz a6e133ceab DEV: Introduce warpdrive store (with compat layer) and migrate some models (#42147)
Adds a WarpDrive store (`service:warp-store`, LegacyMode + JSON:API
cache) alongside the existing `service:store`, routing requests through
Discourse's `ajax()` helper.

`RestCompatModel` bridges legacy `RestModel` callsites
(`get`/`set`/`setProperties`, `store.createRecord`, `save`,
`destroyRecord`) onto it.

Converts **badge, user-badge, topic-details, bookmark, tag, tag-group,
tag-info, tag-notification, tag-settings and archetype**, with per-model
schemas, request builders and payload normalizers.

Attributes outside a schema (plugin `add_to_serializer` fields, ad-hoc
`create` keys) are retained separately so nothing the server sends is
dropped.
2026-09-03 08:57:28 +02:00

133 lines
3.7 KiB
Plaintext

import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { fn, hash } from "@ember/helper";
import { action } from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import { extractError } from "discourse/lib/ajax-error";
import getURL from "discourse/lib/get-url";
import {
grantableBadgeOptions,
grantableBadges,
isBadgeGrantable,
} from "discourse/lib/grant-badge-utils";
import { autoTrackedArray } from "discourse/lib/tracked-tools";
import Badge from "discourse/models/badge";
import UserBadge from "discourse/models/user-badge";
import ComboBox from "discourse/select-kit/components/combo-box";
import DButton from "discourse/ui-kit/d-button";
import DConditionalLoadingSpinner from "discourse/ui-kit/d-conditional-loading-spinner";
import DModal from "discourse/ui-kit/d-modal";
import { i18n } from "discourse-i18n";
export default class GrantBadgeModal extends Component {
@tracked loading = true;
@tracked saving = false;
@tracked selectedBadgeId = null;
@tracked flash = null;
@tracked flashType = null;
@tracked allBadges = [];
@tracked availableBadges = [];
@autoTrackedArray userBadges = [];
get noAvailableBadges() {
!this.availableBadges.length;
}
get badgeOptions() {
return grantableBadgeOptions(this.availableBadges);
}
get post() {
return this.args.model.selectedPost;
}
get buttonDisabled() {
return (
this.saving ||
!isBadgeGrantable(this.selectedBadgeId, this.availableBadges)
);
}
#updateAvailableBadges() {
this.availableBadges = grantableBadges(this.allBadges, this.userBadges);
}
@action
async loadBadges() {
this.loading = true;
try {
this.allBadges = await Badge.findAll();
this.userBadges = await UserBadge.findByUsername(this.post.username);
this.#updateAvailableBadges();
} catch (e) {
this.flash = extractError(e);
this.flashType = "error";
} finally {
this.loading = false;
}
}
@action
async performGrantBadge() {
try {
this.saving = true;
const username = this.post.username;
const newBadge = await UserBadge.grant(
this.selectedBadgeId,
username,
getURL(this.post.url)
);
this.userBadges.push(newBadge);
this.#updateAvailableBadges();
this.selectedBadgeId = null;
this.flash = i18n("badges.successfully_granted", {
username,
badge: newBadge.get("badge.name"),
});
this.flashType = "success";
} catch (e) {
this.flash = extractError(e);
this.flashType = "error";
} finally {
this.saving = false;
}
}
<template>
<DModal
@bodyClass="grant-badge"
@closeModal={{@closeModal}}
@flash={{this.flash}}
@flashType={{this.flashType}}
@title={{i18n "admin.badges.grant_badge"}}
class="grant-badge-modal"
{{didInsert this.loadBadges}}
>
<:body>
<DConditionalLoadingSpinner @condition={{this.loading}}>
{{#if this.noAvailableBadges}}
<p>{{i18n "admin.badges.no_badges"}}</p>
{{else}}
<p>
<ComboBox
@value={{this.selectedBadgeId}}
@content={{this.badgeOptions}}
@onChange={{fn (mut this.selectedBadgeId)}}
@options={{hash filterable=true none="badges.none"}}
/>
</p>
{{/if}}
</DConditionalLoadingSpinner>
</:body>
<:footer>
<DButton
@disabled={{this.buttonDisabled}}
@action={{this.performGrantBadge}}
@label="admin.badges.grant"
class="btn-primary"
/>
</:footer>
</DModal>
</template>
}