Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
FEATURE: Various improvements to invite system (#12023)
The user interface has been reorganized to show email and link invites in the same screen. Staff has more control over creating and updating invites. Bulk invite has also been improved with better explanations. On the server side, many code paths for email and link invites have been merged to avoid duplicated logic. The API returns better responses with more appropriate HTTP status codes.
- Loading branch information
Showing
37 changed files
with
1,265 additions
and
1,061 deletions.
There are no files selected for viewing
16 changes: 16 additions & 0 deletions
16
app/assets/javascripts/discourse/app/components/copy-button.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import Component from "@ember/component"; | ||
import { action } from "@ember/object"; | ||
|
||
export default Component.extend({ | ||
tagName: "", | ||
|
||
@action | ||
copy() { | ||
const target = document.querySelector(this.selector); | ||
target.select(); | ||
target.setSelectionRange(0, target.value.length); | ||
try { | ||
document.execCommand("copy"); | ||
} catch (err) {} | ||
}, | ||
}); |
111 changes: 111 additions & 0 deletions
111
app/assets/javascripts/discourse/app/components/create-invite-uploader.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import Component from "@ember/component"; | ||
import getUrl from "discourse-common/lib/get-url"; | ||
import discourseComputed from "discourse-common/utils/decorators"; | ||
import { | ||
displayErrorForUpload, | ||
validateUploadedFiles, | ||
} from "discourse/lib/uploads"; | ||
|
||
export default Component.extend({ | ||
tagName: "", | ||
|
||
data: null, | ||
uploading: false, | ||
progress: 0, | ||
uploaded: null, | ||
|
||
@discourseComputed("messageBus.clientId") | ||
clientId() { | ||
return this.messageBus && this.messageBus.clientId; | ||
}, | ||
|
||
@discourseComputed("data", "uploading") | ||
submitDisabled(data, uploading) { | ||
return !data || uploading; | ||
}, | ||
|
||
didInsertElement() { | ||
this._super(...arguments); | ||
|
||
this.setProperties({ | ||
data: null, | ||
uploading: false, | ||
progress: 0, | ||
uploaded: null, | ||
}); | ||
|
||
const $upload = $("#csv-file"); | ||
|
||
$upload.fileupload({ | ||
url: getUrl("/invites/upload_csv.json") + "?client_id=" + this.clientId, | ||
dataType: "json", | ||
dropZone: null, | ||
replaceFileInput: false, | ||
autoUpload: false, | ||
}); | ||
|
||
$upload.on("fileuploadadd", (e, data) => { | ||
this.set("data", data); | ||
}); | ||
|
||
$upload.on("fileuploadsubmit", (e, data) => { | ||
const isValid = validateUploadedFiles(data.files, { | ||
user: this.currentUser, | ||
siteSettings: this.siteSettings, | ||
bypassNewUserRestriction: true, | ||
csvOnly: true, | ||
}); | ||
|
||
data.formData = { type: "csv" }; | ||
this.setProperties({ progress: 0, uploading: isValid }); | ||
|
||
return isValid; | ||
}); | ||
|
||
$upload.on("fileuploadprogress", (e, data) => { | ||
const progress = parseInt((data.loaded / data.total) * 100, 10); | ||
this.set("progress", progress); | ||
}); | ||
|
||
$upload.on("fileuploaddone", (e, data) => { | ||
const upload = data.result; | ||
this.set("uploaded", upload); | ||
this.reset(); | ||
}); | ||
|
||
$upload.on("fileuploadfail", (e, data) => { | ||
if (data.errorThrown !== "abort") { | ||
displayErrorForUpload(data, this.siteSettings); | ||
} | ||
this.reset(); | ||
}); | ||
}, | ||
|
||
willDestroyElement() { | ||
this._super(...arguments); | ||
|
||
if (this.messageBus) { | ||
this.messageBus.unsubscribe("/uploads/csv"); | ||
} | ||
|
||
const $upload = $(this.element); | ||
|
||
try { | ||
$upload.fileupload("destroy"); | ||
} catch (e) { | ||
/* wasn't initialized yet */ | ||
} finally { | ||
$upload.off(); | ||
} | ||
}, | ||
|
||
reset() { | ||
this.setProperties({ | ||
data: null, | ||
uploading: false, | ||
progress: 0, | ||
}); | ||
|
||
document.getElementById("csv-file").value = ""; | ||
}, | ||
}); |
49 changes: 0 additions & 49 deletions
49
app/assets/javascripts/discourse/app/components/csv-uploader.js
This file was deleted.
Oops, something went wrong.
24 changes: 24 additions & 0 deletions
24
app/assets/javascripts/discourse/app/controllers/create-invite-bulk.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import Controller from "@ember/controller"; | ||
import { action } from "@ember/object"; | ||
import ModalFunctionality from "discourse/mixins/modal-functionality"; | ||
|
||
export default Controller.extend(ModalFunctionality, { | ||
data: null, | ||
|
||
onShow() { | ||
this.set("data", null); | ||
}, | ||
|
||
onClose() { | ||
if (this.data) { | ||
this.data.abort(); | ||
this.set("data", null); | ||
} | ||
}, | ||
|
||
@action | ||
submit(data) { | ||
this.set("data", data); | ||
data.submit(); | ||
}, | ||
}); |
144 changes: 144 additions & 0 deletions
144
app/assets/javascripts/discourse/app/controllers/create-invite.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
import Controller from "@ember/controller"; | ||
import { action } from "@ember/object"; | ||
import { equal } from "@ember/object/computed"; | ||
import discourseComputed from "discourse-common/utils/decorators"; | ||
import { extractError } from "discourse/lib/ajax-error"; | ||
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"; | ||
|
||
export default Controller.extend( | ||
ModalFunctionality, | ||
bufferedProperty("invite"), | ||
{ | ||
allGroups: null, | ||
|
||
invite: null, | ||
invites: null, | ||
|
||
autogenerated: false, | ||
showAdvanced: false, | ||
showOnly: false, | ||
|
||
type: "link", | ||
|
||
topicId: null, | ||
topicTitle: null, | ||
groupIds: null, | ||
|
||
onShow() { | ||
Group.findAll().then((groups) => { | ||
this.set("allGroups", groups.filterBy("automatic", false)); | ||
}); | ||
|
||
this.setProperties({ | ||
autogenerated: false, | ||
showAdvanced: false, | ||
showOnly: false, | ||
}); | ||
|
||
this.setInvite(Invite.create()); | ||
}, | ||
|
||
onClose() { | ||
if (this.autogenerated) { | ||
this.invite | ||
.destroy() | ||
.then(() => this.invites.removeObject(this.invite)); | ||
} | ||
}, | ||
|
||
setInvite(invite) { | ||
this.setProperties({ | ||
invite, | ||
type: invite.email ? "email" : "link", | ||
groupIds: invite.groups ? invite.groups.map((g) => g.id) : null, | ||
}); | ||
|
||
if (invite.topics && invite.topics.length > 0) { | ||
this.setProperties({ | ||
topicId: invite.topics[0].id, | ||
topicTitle: invite.topics[0].title, | ||
}); | ||
} else { | ||
this.setProperties({ topicId: null, topicTitle: null }); | ||
} | ||
}, | ||
|
||
save(autogenerated) { | ||
this.set("autogenerated", autogenerated); | ||
|
||
const data = { | ||
group_ids: this.groupIds, | ||
topic_id: this.topicId, | ||
expires_at: this.buffered.get("expires_at"), | ||
}; | ||
|
||
if (this.type === "link") { | ||
data.max_redemptions_allowed = this.buffered.get( | ||
"max_redemptions_allowed" | ||
); | ||
} else if (this.type === "email") { | ||
data.email = this.buffered.get("email"); | ||
data.custom_message = this.buffered.get("custom_message"); | ||
} | ||
|
||
const newRecord = !this.invite.id; | ||
return this.invite | ||
.save(data) | ||
.then(() => { | ||
this.rollbackBuffer(); | ||
|
||
if (newRecord) { | ||
this.invites.unshiftObject(this.invite); | ||
} | ||
|
||
if (!this.autogenerated) { | ||
this.appEvents.trigger("modal-body:flash", { | ||
text: I18n.t("user.invited.invite.invite_saved"), | ||
messageClass: "success", | ||
}); | ||
} | ||
}) | ||
.catch((e) => | ||
this.appEvents.trigger("modal-body:flash", { | ||
text: extractError(e), | ||
messageClass: "error", | ||
}) | ||
); | ||
}, | ||
|
||
isLink: equal("type", "link"), | ||
isEmail: equal("type", "email"), | ||
|
||
@discourseComputed("buffered.expires_at") | ||
expiresAtRelative(expires_at) { | ||
return moment.duration(moment(expires_at) - moment()).humanize(); | ||
}, | ||
|
||
@discourseComputed("type", "buffered.email") | ||
disabled(type, email) { | ||
if (type === "email") { | ||
return !email; | ||
} | ||
|
||
return false; | ||
}, | ||
|
||
@discourseComputed("type", "invite.email", "buffered.email") | ||
saveLabel(type, email, bufferedEmail) { | ||
return type === "email" && email !== bufferedEmail | ||
? "user.invited.invite.send_invite_email" | ||
: "user.invited.invite.save_invite"; | ||
}, | ||
|
||
@action | ||
saveInvite() { | ||
this.appEvents.trigger("modal-body:clearFlash"); | ||
|
||
this.save(); | ||
}, | ||
} | ||
); |
Oops, something went wrong.
c047640
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new invite system may have an unexpected behavior that created a security issue with invite links sent since we updated. I have reported it here: https://meta.discourse.org/t/confused-by-save-invite-button-in-2-7-0-beta4/181958/3
It would be great if someone could check this out.