DEV: Update create-invite modal to component-based API (#23797)
This commit is contained in:
parent
07c93918ec
commit
8a7b5b00ea
|
@ -0,0 +1,204 @@
|
||||||
|
<DModal
|
||||||
|
class="create-invite-modal"
|
||||||
|
@title={{i18n
|
||||||
|
(if
|
||||||
|
this.editing
|
||||||
|
"user.invited.invite.edit_title"
|
||||||
|
"user.invited.invite.new_title"
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
@closeModal={{@closeModal}}
|
||||||
|
>
|
||||||
|
<:belowHeader>
|
||||||
|
{{#if this.flashText}}
|
||||||
|
<div
|
||||||
|
id="modal-alert"
|
||||||
|
role="alert"
|
||||||
|
class="alert alert-{{this.flashClass}}"
|
||||||
|
>
|
||||||
|
{{#if this.flashLink}}
|
||||||
|
<div class="input-group invite-link">
|
||||||
|
<label for="invite-link">{{html-safe this.flashText}}
|
||||||
|
{{i18n "user.invited.invite.instructions"}}</label>
|
||||||
|
<div class="link-share-container">
|
||||||
|
<Input
|
||||||
|
name="invite-link"
|
||||||
|
class="invite-link"
|
||||||
|
@value={{this.invite.link}}
|
||||||
|
readonly={{true}}
|
||||||
|
/>
|
||||||
|
<CopyButton
|
||||||
|
@selector="input.invite-link"
|
||||||
|
@copied={{action "copied"}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
{{html-safe this.flashText}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</:belowHeader>
|
||||||
|
<:body>
|
||||||
|
<form>
|
||||||
|
{{#if this.editing}}
|
||||||
|
<div class="input-group invite-link">
|
||||||
|
<label for="invite-link">{{i18n
|
||||||
|
"user.invited.invite.instructions"
|
||||||
|
}}</label>
|
||||||
|
<div class="link-share-container">
|
||||||
|
<Input
|
||||||
|
name="invite-link"
|
||||||
|
class="invite-link"
|
||||||
|
@value={{this.invite.link}}
|
||||||
|
readonly={{true}}
|
||||||
|
/>
|
||||||
|
<CopyButton
|
||||||
|
@selector="input.invite-link"
|
||||||
|
@copied={{action "copied"}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<div class="input-group input-email">
|
||||||
|
<label for="invite-email">
|
||||||
|
{{d-icon "envelope"}}
|
||||||
|
{{#if this.isEmail}}
|
||||||
|
{{i18n "user.invited.invite.restrict_email"}}
|
||||||
|
{{else if this.isDomain}}
|
||||||
|
{{i18n "user.invited.invite.restrict_domain"}}
|
||||||
|
{{else}}
|
||||||
|
{{i18n "user.invited.invite.restrict"}}
|
||||||
|
{{/if}}
|
||||||
|
</label>
|
||||||
|
<div class="invite-email-container">
|
||||||
|
<Input
|
||||||
|
id="invite-email"
|
||||||
|
@value={{this.buffered.emailOrDomain}}
|
||||||
|
placeholder={{i18n
|
||||||
|
"user.invited.invite.email_or_domain_placeholder"
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{{#if this.capabilities.hasContactPicker}}
|
||||||
|
<DButton
|
||||||
|
@icon="address-book"
|
||||||
|
@action={{this.searchContact}}
|
||||||
|
class="btn-primary open-contact-picker"
|
||||||
|
/>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#if this.isLink}}
|
||||||
|
<div class="input-group invite-max-redemptions">
|
||||||
|
<label for="invite-max-redemptions">{{d-icon "users"}}{{i18n
|
||||||
|
"user.invited.invite.max_redemptions_allowed"
|
||||||
|
}}</label>
|
||||||
|
<Input
|
||||||
|
id="invite-max-redemptions"
|
||||||
|
@type="number"
|
||||||
|
@value={{this.buffered.max_redemptions_allowed}}
|
||||||
|
min="1"
|
||||||
|
max={{this.maxRedemptionsAllowedLimit}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.isEmail}}
|
||||||
|
<div class="input-group invite-custom-message">
|
||||||
|
<label for="invite-message">{{i18n
|
||||||
|
"user.invited.invite.custom_message"
|
||||||
|
}}</label>
|
||||||
|
<Textarea
|
||||||
|
id="invite-message"
|
||||||
|
@value={{this.buffered.custom_message}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.canArriveAtTopic}}
|
||||||
|
<div class="input-group invite-to-topic">
|
||||||
|
<label for="invite-topic">{{d-icon "hand-point-right"}}{{i18n
|
||||||
|
"user.invited.invite.invite_to_topic"
|
||||||
|
}}</label>
|
||||||
|
<TopicChooser
|
||||||
|
@value={{this.buffered.topicId}}
|
||||||
|
@content={{this.topics}}
|
||||||
|
@onChange={{action "onChangeTopic"}}
|
||||||
|
@options={{hash additionalFilters="status:public"}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{{else if this.buffered.topicTitle}}
|
||||||
|
<div class="input-group invite-to-topic">
|
||||||
|
<label for="invite-topic">{{d-icon "hand-point-right"}}{{i18n
|
||||||
|
"user.invited.invite.invite_to_topic"
|
||||||
|
}}</label>
|
||||||
|
<Input
|
||||||
|
name="invite-topic"
|
||||||
|
class="invite-topic"
|
||||||
|
@value={{this.buffered.topicTitle}}
|
||||||
|
readonly={{true}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.canInviteToGroup}}
|
||||||
|
<div class="input-group invite-to-groups">
|
||||||
|
<label>{{d-icon "users"}}{{i18n
|
||||||
|
"user.invited.invite.add_to_groups"
|
||||||
|
}}</label>
|
||||||
|
<GroupChooser
|
||||||
|
@content={{this.allGroups}}
|
||||||
|
@value={{this.buffered.groupIds}}
|
||||||
|
@labelProperty="name"
|
||||||
|
@onChange={{action (mut this.buffered.groupIds)}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.currentUser.staff}}
|
||||||
|
<div class="input-group invite-expires-at">
|
||||||
|
<FutureDateInput
|
||||||
|
@displayLabelIcon="far-clock"
|
||||||
|
@displayLabel={{i18n "user.invited.invite.expires_at"}}
|
||||||
|
@customShortcuts={{this.timeShortcuts}}
|
||||||
|
@clearable={{true}}
|
||||||
|
@input={{this.buffered.expires_at}}
|
||||||
|
@onChangeInput={{action (mut this.buffered.expires_at)}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="input-group input-expires-at">
|
||||||
|
<label>{{d-icon "far-clock"}}{{this.expiresAtLabel}}</label>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</form>
|
||||||
|
</:body>
|
||||||
|
<:footer>
|
||||||
|
|
||||||
|
<DButton
|
||||||
|
@icon="link"
|
||||||
|
@label="user.invited.invite.save_invite"
|
||||||
|
@action={{this.saveInvite}}
|
||||||
|
class="btn-primary save-invite"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DButton
|
||||||
|
@icon="envelope"
|
||||||
|
@label={{if
|
||||||
|
this.invite.emailed
|
||||||
|
"user.invited.reinvite"
|
||||||
|
"user.invited.invite.send_invite_email"
|
||||||
|
}}
|
||||||
|
@action={{fn this.saveInvite true}}
|
||||||
|
@title={{unless
|
||||||
|
this.isEmail
|
||||||
|
"user.invited.invite.send_invite_email_instructions"
|
||||||
|
}}
|
||||||
|
@disabled={{not this.isEmail}}
|
||||||
|
class="btn-primary send-invite"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</:footer>
|
||||||
|
</DModal>
|
|
@ -0,0 +1,205 @@
|
||||||
|
import Component from "@ember/component";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import { not, readOnly } from "@ember/object/computed";
|
||||||
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
import { extractError } from "discourse/lib/ajax-error";
|
||||||
|
import { getNativeContact } from "discourse/lib/pwa-utils";
|
||||||
|
import { emailValid, hostnameValid } from "discourse/lib/utilities";
|
||||||
|
import { bufferedProperty } from "discourse/mixins/buffered-content";
|
||||||
|
import Group from "discourse/models/group";
|
||||||
|
import Invite from "discourse/models/invite";
|
||||||
|
import I18n from "I18n";
|
||||||
|
import { FORMAT } from "select-kit/components/future-date-input-selector";
|
||||||
|
import { sanitize } from "discourse/lib/text";
|
||||||
|
import { timeShortcuts } from "discourse/lib/time-shortcut";
|
||||||
|
|
||||||
|
export default Component.extend(bufferedProperty("invite"), {
|
||||||
|
allGroups: null,
|
||||||
|
topics: null,
|
||||||
|
|
||||||
|
flashText: null,
|
||||||
|
flashClass: null,
|
||||||
|
flashLink: false,
|
||||||
|
|
||||||
|
editing: readOnly("model.editing"),
|
||||||
|
inviteToTopic: false,
|
||||||
|
limitToEmail: false,
|
||||||
|
|
||||||
|
@discourseComputed("buffered.emailOrDomain")
|
||||||
|
isEmail(emailOrDomain) {
|
||||||
|
return emailValid(emailOrDomain?.trim());
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("buffered.emailOrDomain")
|
||||||
|
isDomain(emailOrDomain) {
|
||||||
|
return hostnameValid(emailOrDomain?.trim());
|
||||||
|
},
|
||||||
|
|
||||||
|
isLink: not("isEmail"),
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this._super();
|
||||||
|
|
||||||
|
Group.findAll().then((groups) => {
|
||||||
|
this.set("allGroups", groups.filterBy("automatic", false));
|
||||||
|
});
|
||||||
|
|
||||||
|
this.set("invite", this.model.invite || Invite.create());
|
||||||
|
this.set("topics", this.invite?.topics || []);
|
||||||
|
|
||||||
|
this.buffered.setProperties({
|
||||||
|
max_redemptions_allowed: 1,
|
||||||
|
expires_at: moment()
|
||||||
|
.add(this.siteSettings.invite_expiry_days, "days")
|
||||||
|
.format(FORMAT),
|
||||||
|
groupIds: this.model?.groupIds,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
save(opts) {
|
||||||
|
const data = { ...this.buffered.buffer };
|
||||||
|
|
||||||
|
if (data.emailOrDomain) {
|
||||||
|
if (emailValid(data.emailOrDomain)) {
|
||||||
|
data.email = data.emailOrDomain?.trim();
|
||||||
|
} else if (hostnameValid(data.emailOrDomain)) {
|
||||||
|
data.domain = data.emailOrDomain?.trim();
|
||||||
|
}
|
||||||
|
delete data.emailOrDomain;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.groupIds !== undefined) {
|
||||||
|
data.group_ids = data.groupIds.length > 0 ? data.groupIds : "";
|
||||||
|
delete data.groupIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.topicId !== undefined) {
|
||||||
|
data.topic_id = data.topicId;
|
||||||
|
delete data.topicId;
|
||||||
|
delete data.topicTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isLink) {
|
||||||
|
if (this.invite.email) {
|
||||||
|
data.email = data.custom_message = "";
|
||||||
|
}
|
||||||
|
} else if (this.isEmail) {
|
||||||
|
if (this.invite.max_redemptions_allowed > 1) {
|
||||||
|
data.max_redemptions_allowed = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.sendEmail) {
|
||||||
|
data.send_email = true;
|
||||||
|
if (this.inviteToTopic) {
|
||||||
|
data.invite_to_topic = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data.skip_email = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.invite
|
||||||
|
.save(data)
|
||||||
|
.then(() => {
|
||||||
|
this.rollbackBuffer();
|
||||||
|
|
||||||
|
const invites = this.model?.invites;
|
||||||
|
if (invites && !invites.any((i) => i.id === this.invite.id)) {
|
||||||
|
invites.unshiftObject(this.invite);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isEmail && opts.sendEmail) {
|
||||||
|
this.closeModal();
|
||||||
|
} else {
|
||||||
|
this.setProperties({
|
||||||
|
flashText: sanitize(I18n.t("user.invited.invite.invite_saved")),
|
||||||
|
flashClass: "success",
|
||||||
|
flashLink: !this.editing,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e) =>
|
||||||
|
this.setProperties({
|
||||||
|
flashText: sanitize(extractError(e)),
|
||||||
|
flashClass: "error",
|
||||||
|
flashLink: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed(
|
||||||
|
"currentUser.staff",
|
||||||
|
"siteSettings.invite_link_max_redemptions_limit",
|
||||||
|
"siteSettings.invite_link_max_redemptions_limit_users"
|
||||||
|
)
|
||||||
|
maxRedemptionsAllowedLimit(staff, staffLimit, usersLimit) {
|
||||||
|
return staff ? staffLimit : usersLimit;
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("buffered.expires_at")
|
||||||
|
expiresAtLabel(expires_at) {
|
||||||
|
const expiresAt = moment(expires_at);
|
||||||
|
|
||||||
|
return expiresAt.isBefore()
|
||||||
|
? I18n.t("user.invited.invite.expired_at_time", {
|
||||||
|
time: expiresAt.format("LLL"),
|
||||||
|
})
|
||||||
|
: I18n.t("user.invited.invite.expires_in_time", {
|
||||||
|
time: moment.duration(expiresAt - moment()).humanize(),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("currentUser.staff", "currentUser.groups")
|
||||||
|
canInviteToGroup(staff, groups) {
|
||||||
|
return staff || groups.any((g) => g.owner);
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("currentUser.staff")
|
||||||
|
canArriveAtTopic(staff) {
|
||||||
|
if (staff && !this.siteSettings.must_approve_users) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed
|
||||||
|
timeShortcuts() {
|
||||||
|
const timezone = this.currentUser.user_option.timezone;
|
||||||
|
const shortcuts = timeShortcuts(timezone);
|
||||||
|
return [
|
||||||
|
shortcuts.laterToday(),
|
||||||
|
shortcuts.tomorrow(),
|
||||||
|
shortcuts.laterThisWeek(),
|
||||||
|
shortcuts.monday(),
|
||||||
|
shortcuts.twoWeeks(),
|
||||||
|
shortcuts.nextMonth(),
|
||||||
|
shortcuts.twoMonths(),
|
||||||
|
shortcuts.threeMonths(),
|
||||||
|
shortcuts.fourMonths(),
|
||||||
|
shortcuts.sixMonths(),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
@action
|
||||||
|
copied() {
|
||||||
|
this.save({ sendEmail: false, copy: true });
|
||||||
|
},
|
||||||
|
|
||||||
|
@action
|
||||||
|
saveInvite(sendEmail) {
|
||||||
|
this.save({ sendEmail });
|
||||||
|
},
|
||||||
|
|
||||||
|
@action
|
||||||
|
searchContact() {
|
||||||
|
getNativeContact(this.capabilities, ["email"], false).then((result) => {
|
||||||
|
this.set("buffered.email", result[0].email[0]);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
@action
|
||||||
|
onChangeTopic(topicId, topic) {
|
||||||
|
this.set("topics", [topic]);
|
||||||
|
this.set("buffered.topicId", topicId);
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,232 +0,0 @@
|
||||||
import Controller from "@ember/controller";
|
|
||||||
import { action } from "@ember/object";
|
|
||||||
import { not } from "@ember/object/computed";
|
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
|
||||||
import { extractError } from "discourse/lib/ajax-error";
|
|
||||||
import { getNativeContact } from "discourse/lib/pwa-utils";
|
|
||||||
import { emailValid, hostnameValid } from "discourse/lib/utilities";
|
|
||||||
import { bufferedProperty } from "discourse/mixins/buffered-content";
|
|
||||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
|
||||||
import Group from "discourse/models/group";
|
|
||||||
import Invite from "discourse/models/invite";
|
|
||||||
import I18n from "I18n";
|
|
||||||
import { FORMAT } from "select-kit/components/future-date-input-selector";
|
|
||||||
import { sanitize } from "discourse/lib/text";
|
|
||||||
import { timeShortcuts } from "discourse/lib/time-shortcut";
|
|
||||||
|
|
||||||
export default Controller.extend(
|
|
||||||
ModalFunctionality,
|
|
||||||
bufferedProperty("invite"),
|
|
||||||
{
|
|
||||||
allGroups: null,
|
|
||||||
topics: null,
|
|
||||||
|
|
||||||
flashText: null,
|
|
||||||
flashClass: null,
|
|
||||||
flashLink: false,
|
|
||||||
|
|
||||||
invite: null,
|
|
||||||
invites: null,
|
|
||||||
|
|
||||||
editing: false,
|
|
||||||
inviteToTopic: false,
|
|
||||||
limitToEmail: false,
|
|
||||||
|
|
||||||
@discourseComputed("buffered.emailOrDomain")
|
|
||||||
isEmail(emailOrDomain) {
|
|
||||||
return emailValid(emailOrDomain?.trim());
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("buffered.emailOrDomain")
|
|
||||||
isDomain(emailOrDomain) {
|
|
||||||
return hostnameValid(emailOrDomain?.trim());
|
|
||||||
},
|
|
||||||
|
|
||||||
isLink: not("isEmail"),
|
|
||||||
|
|
||||||
onShow() {
|
|
||||||
Group.findAll().then((groups) => {
|
|
||||||
this.set("allGroups", groups.filterBy("automatic", false));
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setProperties({
|
|
||||||
topics: [],
|
|
||||||
flashText: null,
|
|
||||||
flashClass: null,
|
|
||||||
flashLink: false,
|
|
||||||
invite: null,
|
|
||||||
invites: null,
|
|
||||||
editing: false,
|
|
||||||
inviteToTopic: false,
|
|
||||||
limitToEmail: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setInvite(Invite.create());
|
|
||||||
this.buffered.setProperties({
|
|
||||||
max_redemptions_allowed: 1,
|
|
||||||
expires_at: moment()
|
|
||||||
.add(this.siteSettings.invite_expiry_days, "days")
|
|
||||||
.format(FORMAT),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onClose() {
|
|
||||||
this.appEvents.trigger("modal-body:clearFlash");
|
|
||||||
},
|
|
||||||
|
|
||||||
setInvite(invite) {
|
|
||||||
this.setProperties({ invite, topics: invite.topics });
|
|
||||||
},
|
|
||||||
|
|
||||||
save(opts) {
|
|
||||||
const data = { ...this.buffered.buffer };
|
|
||||||
|
|
||||||
if (data.emailOrDomain) {
|
|
||||||
if (emailValid(data.emailOrDomain)) {
|
|
||||||
data.email = data.emailOrDomain?.trim();
|
|
||||||
} else if (hostnameValid(data.emailOrDomain)) {
|
|
||||||
data.domain = data.emailOrDomain?.trim();
|
|
||||||
}
|
|
||||||
delete data.emailOrDomain;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.groupIds !== undefined) {
|
|
||||||
data.group_ids = data.groupIds.length > 0 ? data.groupIds : "";
|
|
||||||
delete data.groupIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.topicId !== undefined) {
|
|
||||||
data.topic_id = data.topicId;
|
|
||||||
delete data.topicId;
|
|
||||||
delete data.topicTitle;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isLink) {
|
|
||||||
if (this.invite.email) {
|
|
||||||
data.email = data.custom_message = "";
|
|
||||||
}
|
|
||||||
} else if (this.isEmail) {
|
|
||||||
if (this.invite.max_redemptions_allowed > 1) {
|
|
||||||
data.max_redemptions_allowed = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.sendEmail) {
|
|
||||||
data.send_email = true;
|
|
||||||
if (this.inviteToTopic) {
|
|
||||||
data.invite_to_topic = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
data.skip_email = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.invite
|
|
||||||
.save(data)
|
|
||||||
.then(() => {
|
|
||||||
this.rollbackBuffer();
|
|
||||||
|
|
||||||
if (
|
|
||||||
this.invites &&
|
|
||||||
!this.invites.any((i) => i.id === this.invite.id)
|
|
||||||
) {
|
|
||||||
this.invites.unshiftObject(this.invite);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isEmail && opts.sendEmail) {
|
|
||||||
this.send("closeModal");
|
|
||||||
} else {
|
|
||||||
this.setProperties({
|
|
||||||
flashText: sanitize(I18n.t("user.invited.invite.invite_saved")),
|
|
||||||
flashClass: "success",
|
|
||||||
flashLink: !this.editing,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((e) =>
|
|
||||||
this.setProperties({
|
|
||||||
flashText: sanitize(extractError(e)),
|
|
||||||
flashClass: "error",
|
|
||||||
flashLink: false,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed(
|
|
||||||
"currentUser.staff",
|
|
||||||
"siteSettings.invite_link_max_redemptions_limit",
|
|
||||||
"siteSettings.invite_link_max_redemptions_limit_users"
|
|
||||||
)
|
|
||||||
maxRedemptionsAllowedLimit(staff, staffLimit, usersLimit) {
|
|
||||||
return staff ? staffLimit : usersLimit;
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("buffered.expires_at")
|
|
||||||
expiresAtLabel(expires_at) {
|
|
||||||
const expiresAt = moment(expires_at);
|
|
||||||
|
|
||||||
return expiresAt.isBefore()
|
|
||||||
? I18n.t("user.invited.invite.expired_at_time", {
|
|
||||||
time: expiresAt.format("LLL"),
|
|
||||||
})
|
|
||||||
: I18n.t("user.invited.invite.expires_in_time", {
|
|
||||||
time: moment.duration(expiresAt - moment()).humanize(),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("currentUser.staff", "currentUser.groups")
|
|
||||||
canInviteToGroup(staff, groups) {
|
|
||||||
return staff || groups.any((g) => g.owner);
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("currentUser.staff")
|
|
||||||
canArriveAtTopic(staff) {
|
|
||||||
if (staff && !this.siteSettings.must_approve_users) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed
|
|
||||||
timeShortcuts() {
|
|
||||||
const timezone = this.currentUser.user_option.timezone;
|
|
||||||
const shortcuts = timeShortcuts(timezone);
|
|
||||||
return [
|
|
||||||
shortcuts.laterToday(),
|
|
||||||
shortcuts.tomorrow(),
|
|
||||||
shortcuts.laterThisWeek(),
|
|
||||||
shortcuts.monday(),
|
|
||||||
shortcuts.twoWeeks(),
|
|
||||||
shortcuts.nextMonth(),
|
|
||||||
shortcuts.twoMonths(),
|
|
||||||
shortcuts.threeMonths(),
|
|
||||||
shortcuts.fourMonths(),
|
|
||||||
shortcuts.sixMonths(),
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
@action
|
|
||||||
copied() {
|
|
||||||
this.save({ sendEmail: false, copy: true });
|
|
||||||
},
|
|
||||||
|
|
||||||
@action
|
|
||||||
saveInvite(sendEmail) {
|
|
||||||
this.appEvents.trigger("modal-body:clearFlash");
|
|
||||||
|
|
||||||
this.save({ sendEmail });
|
|
||||||
},
|
|
||||||
|
|
||||||
@action
|
|
||||||
searchContact() {
|
|
||||||
getNativeContact(this.capabilities, ["email"], false).then((result) => {
|
|
||||||
this.set("buffered.email", result[0].email[0]);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
@action
|
|
||||||
onChangeTopic(topicId, topic) {
|
|
||||||
this.set("topics", [topic]);
|
|
||||||
this.set("buffered.topicId", topicId);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
|
@ -7,11 +7,11 @@ import discourseComputed, {
|
||||||
observes,
|
observes,
|
||||||
} from "discourse-common/utils/decorators";
|
} from "discourse-common/utils/decorators";
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
import showModal from "discourse/lib/show-modal";
|
|
||||||
import Invite from "discourse/models/invite";
|
import Invite from "discourse/models/invite";
|
||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
import { inject as service } from "@ember/service";
|
import { inject as service } from "@ember/service";
|
||||||
import CreateInviteBulk from "discourse/components/modal/create-invite-bulk";
|
import CreateInviteBulk from "discourse/components/modal/create-invite-bulk";
|
||||||
|
import CreateInvite from "discourse/components/modal/create-invite";
|
||||||
|
|
||||||
export default Controller.extend({
|
export default Controller.extend({
|
||||||
dialog: service(),
|
dialog: service(),
|
||||||
|
@ -73,8 +73,7 @@ export default Controller.extend({
|
||||||
|
|
||||||
@action
|
@action
|
||||||
createInvite() {
|
createInvite() {
|
||||||
const controller = showModal("create-invite");
|
this.modal.show(CreateInvite, { model: { invites: this.model.invites } });
|
||||||
controller.set("invites", this.model.invites);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -84,9 +83,7 @@ export default Controller.extend({
|
||||||
|
|
||||||
@action
|
@action
|
||||||
editInvite(invite) {
|
editInvite(invite) {
|
||||||
const controller = showModal("create-invite");
|
this.modal.show(CreateInvite, { model: { editing: true, invite } });
|
||||||
controller.set("editing", true);
|
|
||||||
controller.setInvite(invite);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import DiscourseRoute from "discourse/routes/discourse";
|
import DiscourseRoute from "discourse/routes/discourse";
|
||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import showModal from "discourse/lib/show-modal";
|
|
||||||
import { inject as service } from "@ember/service";
|
import { inject as service } from "@ember/service";
|
||||||
import GroupAddMembersModal from "discourse/components/modal/group-add-members";
|
import GroupAddMembersModal from "discourse/components/modal/group-add-members";
|
||||||
|
import CreateInvite from "discourse/components/modal/create-invite";
|
||||||
|
|
||||||
export default DiscourseRoute.extend({
|
export default DiscourseRoute.extend({
|
||||||
modal: service(),
|
modal: service(),
|
||||||
|
@ -34,9 +34,8 @@ export default DiscourseRoute.extend({
|
||||||
|
|
||||||
@action
|
@action
|
||||||
showInviteModal() {
|
showInviteModal() {
|
||||||
const model = this.modelFor("group");
|
const group = this.modelFor("group");
|
||||||
const controller = showModal("create-invite");
|
this.modal.show(CreateInvite, { model: { groupIds: [group.id] } });
|
||||||
controller.buffered.set("groupIds", [model.id]);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
|
|
@ -1,187 +0,0 @@
|
||||||
{{#if this.flashText}}
|
|
||||||
<div id="modal-alert" role="alert" class="alert alert-{{this.flashClass}}">
|
|
||||||
{{#if this.flashLink}}
|
|
||||||
<div class="input-group invite-link">
|
|
||||||
<label for="invite-link">{{html-safe this.flashText}}
|
|
||||||
{{i18n "user.invited.invite.instructions"}}</label>
|
|
||||||
<div class="link-share-container">
|
|
||||||
<Input
|
|
||||||
name="invite-link"
|
|
||||||
class="invite-link"
|
|
||||||
@value={{this.invite.link}}
|
|
||||||
readonly={{true}}
|
|
||||||
/>
|
|
||||||
<CopyButton
|
|
||||||
@selector="input.invite-link"
|
|
||||||
@copied={{action "copied"}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
{{html-safe this.flashText}}
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<DModalBody
|
|
||||||
@title={{if
|
|
||||||
this.editing
|
|
||||||
"user.invited.invite.edit_title"
|
|
||||||
"user.invited.invite.new_title"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<form>
|
|
||||||
{{#if this.editing}}
|
|
||||||
<div class="input-group invite-link">
|
|
||||||
<label for="invite-link">{{i18n
|
|
||||||
"user.invited.invite.instructions"
|
|
||||||
}}</label>
|
|
||||||
<div class="link-share-container">
|
|
||||||
<Input
|
|
||||||
name="invite-link"
|
|
||||||
class="invite-link"
|
|
||||||
@value={{this.invite.link}}
|
|
||||||
readonly={{true}}
|
|
||||||
/>
|
|
||||||
<CopyButton
|
|
||||||
@selector="input.invite-link"
|
|
||||||
@copied={{action "copied"}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<div class="input-group input-email">
|
|
||||||
<label for="invite-email">
|
|
||||||
{{d-icon "envelope"}}
|
|
||||||
{{#if this.isEmail}}
|
|
||||||
{{i18n "user.invited.invite.restrict_email"}}
|
|
||||||
{{else if this.isDomain}}
|
|
||||||
{{i18n "user.invited.invite.restrict_domain"}}
|
|
||||||
{{else}}
|
|
||||||
{{i18n "user.invited.invite.restrict"}}
|
|
||||||
{{/if}}
|
|
||||||
</label>
|
|
||||||
<div class="invite-email-container">
|
|
||||||
<Input
|
|
||||||
id="invite-email"
|
|
||||||
@value={{this.buffered.emailOrDomain}}
|
|
||||||
placeholder={{i18n "user.invited.invite.email_or_domain_placeholder"}}
|
|
||||||
/>
|
|
||||||
{{#if this.capabilities.hasContactPicker}}
|
|
||||||
<DButton
|
|
||||||
@icon="address-book"
|
|
||||||
@action={{this.searchContact}}
|
|
||||||
class="btn-primary open-contact-picker"
|
|
||||||
/>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{#if this.isLink}}
|
|
||||||
<div class="input-group invite-max-redemptions">
|
|
||||||
<label for="invite-max-redemptions">{{d-icon "users"}}{{i18n
|
|
||||||
"user.invited.invite.max_redemptions_allowed"
|
|
||||||
}}</label>
|
|
||||||
<Input
|
|
||||||
id="invite-max-redemptions"
|
|
||||||
@type="number"
|
|
||||||
@value={{this.buffered.max_redemptions_allowed}}
|
|
||||||
min="1"
|
|
||||||
max={{this.maxRedemptionsAllowedLimit}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if this.isEmail}}
|
|
||||||
<div class="input-group invite-custom-message">
|
|
||||||
<label for="invite-message">{{i18n
|
|
||||||
"user.invited.invite.custom_message"
|
|
||||||
}}</label>
|
|
||||||
<Textarea id="invite-message" @value={{this.buffered.custom_message}} />
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if this.canArriveAtTopic}}
|
|
||||||
<div class="input-group invite-to-topic">
|
|
||||||
<label for="invite-topic">{{d-icon "hand-point-right"}}{{i18n
|
|
||||||
"user.invited.invite.invite_to_topic"
|
|
||||||
}}</label>
|
|
||||||
<TopicChooser
|
|
||||||
@value={{this.buffered.topicId}}
|
|
||||||
@content={{this.topics}}
|
|
||||||
@onChange={{action "onChangeTopic"}}
|
|
||||||
@options={{hash additionalFilters="status:public"}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{{else if this.buffered.topicTitle}}
|
|
||||||
<div class="input-group invite-to-topic">
|
|
||||||
<label for="invite-topic">{{d-icon "hand-point-right"}}{{i18n
|
|
||||||
"user.invited.invite.invite_to_topic"
|
|
||||||
}}</label>
|
|
||||||
<Input
|
|
||||||
name="invite-topic"
|
|
||||||
class="invite-topic"
|
|
||||||
@value={{this.buffered.topicTitle}}
|
|
||||||
readonly={{true}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if this.canInviteToGroup}}
|
|
||||||
<div class="input-group invite-to-groups">
|
|
||||||
<label>{{d-icon "users"}}{{i18n
|
|
||||||
"user.invited.invite.add_to_groups"
|
|
||||||
}}</label>
|
|
||||||
<GroupChooser
|
|
||||||
@content={{this.allGroups}}
|
|
||||||
@value={{this.buffered.groupIds}}
|
|
||||||
@labelProperty="name"
|
|
||||||
@onChange={{action (mut this.buffered.groupIds)}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if this.currentUser.staff}}
|
|
||||||
<div class="input-group invite-expires-at">
|
|
||||||
<FutureDateInput
|
|
||||||
@displayLabelIcon="far-clock"
|
|
||||||
@displayLabel={{i18n "user.invited.invite.expires_at"}}
|
|
||||||
@customShortcuts={{this.timeShortcuts}}
|
|
||||||
@clearable={{true}}
|
|
||||||
@input={{this.buffered.expires_at}}
|
|
||||||
@onChangeInput={{action (mut this.buffered.expires_at)}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
<div class="input-group input-expires-at">
|
|
||||||
<label>{{d-icon "far-clock"}}{{this.expiresAtLabel}}</label>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</form>
|
|
||||||
</DModalBody>
|
|
||||||
|
|
||||||
<div class="modal-footer">
|
|
||||||
<DButton
|
|
||||||
@icon="link"
|
|
||||||
@label="user.invited.invite.save_invite"
|
|
||||||
@action={{this.saveInvite}}
|
|
||||||
class="btn-primary save-invite"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<DButton
|
|
||||||
@icon="envelope"
|
|
||||||
@label={{if
|
|
||||||
this.invite.emailed
|
|
||||||
"user.invited.reinvite"
|
|
||||||
"user.invited.invite.send_invite_email"
|
|
||||||
}}
|
|
||||||
@action={{fn this.saveInvite true}}
|
|
||||||
@title={{unless
|
|
||||||
this.isEmail
|
|
||||||
"user.invited.invite.send_invite_email_instructions"
|
|
||||||
}}
|
|
||||||
@disabled={{not this.isEmail}}
|
|
||||||
class="btn-primary send-invite"
|
|
||||||
/>
|
|
||||||
</div>
|
|
Loading…
Reference in New Issue