UX: Use icons as bulleted list in invite modal (#13229)

This commit is contained in:
Dan Ungureanu 2021-06-02 16:28:54 +03:00 committed by GitHub
parent d2135b23c4
commit 447d8dfc44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 117 additions and 115 deletions

View File

@ -1,11 +1,11 @@
import Controller from "@ember/controller";
import { action } from "@ember/object";
import { equal } from "@ember/object/computed";
import { empty, notEmpty } 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 { bufferedProperty } from "discourse/mixins/buffered-content";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { getNativeContact } from "discourse/lib/pwa-utils";
import Group from "discourse/models/group";
import Invite from "discourse/models/invite";
import I18n from "I18n";
@ -24,7 +24,8 @@ export default Controller.extend(
limitToEmail: false,
autogenerated: false,
type: "link",
isLink: empty("buffered.email"),
isEmail: notEmpty("buffered.email"),
onShow() {
Group.findAll().then((groups) => {
@ -52,10 +53,7 @@ export default Controller.extend(
},
setInvite(invite) {
this.setProperties({
invite,
type: invite.email ? "email" : "link",
});
this.set("invite", invite);
},
setAutogenerated(value) {
@ -70,7 +68,7 @@ export default Controller.extend(
const data = { ...this.buffered.buffer };
if (data.groupIds !== undefined) {
data.group_ids = data.groupIds;
data.group_ids = data.groupIds.length > 0 ? data.groupIds : "";
delete data.groupIds;
}
@ -80,13 +78,12 @@ export default Controller.extend(
delete data.topicTitle;
}
if (this.type === "link") {
if (this.buffered.get("email")) {
data.email = "";
data.custom_message = "";
if (this.isLink) {
if (this.invite.email) {
data.email = data.custom_message = "";
}
} else if (this.type === "email") {
if (this.buffered.get("max_redemptions_allowed") > 1) {
} else if (this.isEmail) {
if (this.invite.max_redemptions_allowed > 1) {
data.max_redemptions_allowed = 1;
}
@ -106,7 +103,7 @@ export default Controller.extend(
this.rollbackBuffer();
this.setAutogenerated(opts.autogenerated);
if (!this.autogenerated) {
if (this.type === "email" && opts.sendEmail) {
if (this.isEmail && opts.sendEmail) {
this.send("closeModal");
} else {
this.appEvents.trigger("modal-body:flash", {
@ -126,9 +123,6 @@ export default Controller.extend(
);
},
isLink: equal("type", "link"),
isEmail: equal("type", "email"),
@discourseComputed(
"currentUser.staff",
"siteSettings.invite_link_max_redemptions_limit",
@ -156,46 +150,16 @@ export default Controller.extend(
return staff || groups.any((g) => g.owner);
},
@discourseComputed("type", "buffered.email")
disabled(type, email) {
if (type === "email") {
return !email;
}
return false;
},
@discourseComputed("buffered.hasBufferedChanges", "invite.email", "type")
changed(hasBufferedChanges, inviteEmail, type) {
return hasBufferedChanges || (inviteEmail ? "email" : "link") !== type;
},
@discourseComputed("currentUser.staff", "type")
hasAdvanced(staff, type) {
return staff || type === "email";
@discourseComputed("currentUser.staff", "isEmail", "canInviteToGroup")
hasAdvanced(staff, isEmail, canInviteToGroup) {
return staff || isEmail || canInviteToGroup;
},
@action
copied() {
if (this.type === "email" && !this.buffered.get("email")) {
return this.appEvents.trigger("modal-body:flash", {
text: I18n.t("user.invited.invite.blank_email"),
messageClass: "error",
});
}
this.save({ sendEmail: false, copy: true });
},
@action
toggleLimitToEmail() {
const limitToEmail = !this.limitToEmail;
this.setProperties({
limitToEmail,
type: limitToEmail ? "email" : "link",
});
},
@action
saveInvite(sendEmail) {
this.appEvents.trigger("modal-body:clearFlash");

View File

@ -1,4 +1,6 @@
<label for="choose-topic-title">{{i18n labelText}}</label>
<label for="choose-topic-title">
{{#if labelIcon}}{{d-icon labelIcon}}{{/if}}{{i18n labelText}}
</label>
{{text-field value=topicTitle placeholderKey="choose_topic.title.placeholder" id="choose-topic-title"}}

View File

@ -1,6 +1,8 @@
<div class="future-date-input">
<div class="control-group">
<label class={{labelClasses}}>{{displayLabel}}</label>
<label class={{labelClasses}}>
{{#if displayLabelIcon}}{{d-icon displayLabelIcon}}{{/if}}{{displayLabel}}
</label>
{{future-date-input-selector
minimumResultsForSearch=-1
statusType=statusType

View File

@ -13,33 +13,31 @@
</div>
</div>
<p>{{expiresAtLabel}}</p>
<div class="input-group input-expires-at">
<label>{{d-icon "far-clock"}}{{expiresAtLabel}}</label>
</div>
<div class="input-group invite-type">
{{input type="checkbox" id="invite-type" checked=limitToEmail click=(action "toggleLimitToEmail")}}
<label for="invite-type">{{i18n "user.invited.invite.restrict_email"}}</label>
{{#if isEmail}}
<div class="invite-input-with-button">
{{input
id="invite-email"
value=buffered.email
placeholderKey="topic.invite_reply.email_placeholder"
<div class="input-group input-email">
<label for="invite-email">{{d-icon "envelope"}}{{i18n "user.invited.invite.restrict_email"}}</label>
<div class="invite-input-with-button">
{{input
id="invite-email"
value=buffered.email
placeholderKey="topic.invite_reply.email_placeholder"
}}
{{#if capabilities.hasContactPicker}}
{{d-button
icon="address-book"
action=(action "searchContact")
class="btn-primary open-contact-picker"
}}
{{#if capabilities.hasContactPicker}}
{{d-button
icon="address-book"
action=(action "searchContact")
class="btn-primary open-contact-picker"
}}
{{/if}}
</div>
{{/if}}
{{/if}}
</div>
</div>
{{#if isLink}}
<div class="input-group invite-max-redemptions">
<label for="invite-max-redemptions">{{i18n "user.invited.invite.max_redemptions_allowed"}}</label>
<label for="invite-max-redemptions">{{d-icon "users"}}{{i18n "user.invited.invite.max_redemptions_allowed"}}</label>
{{input
id="invite-max-redemptions"
type="number"
@ -50,10 +48,10 @@
</div>
{{/if}}
{{#if isEmail}}
{{#if showAdvanced}}
{{#if showAdvanced}}
{{#if isEmail}}
<div class="input-group invite-custom-message">
<label for="invite-message">{{i18n "user.invited.invite.custom_message"}}</label>
<label for="invite-message">{{d-icon "envelope"}}{{i18n "user.invited.invite.custom_message"}}</label>
{{textarea id="invite-message" value=buffered.custom_message}}
</div>
{{/if}}
@ -66,12 +64,13 @@
selectedTopicId=buffered.topicId
topicTitle=buffered.topicTitle
additionalFilters="status:public"
labelIcon="hand-point-right"
label="user.invited.invite.invite_to_topic"
}}
</div>
{{else if buffered.topicTitle}}
<div class="input-group">
<label for="invite-topic">{{i18n "user.invited.invite.invite_to_topic"}}</label>
<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"
@ -85,7 +84,7 @@
{{#if showAdvanced}}
{{#if canInviteToGroup}}
<div class="input-group invite-to-groups">
<label>{{i18n "user.invited.invite.add_to_groups"}}</label>
<label>{{d-icon "users"}}{{i18n "user.invited.invite.add_to_groups"}}</label>
{{group-chooser
content=allGroups
value=buffered.groupIds
@ -100,6 +99,7 @@
{{#if currentUser.staff}}
<div class="input-group invite-expires-at">
{{future-date-input
displayLabelIcon="far-clock"
displayLabel=(i18n "user.invited.invite.expires_at")
includeDateTime=true
includeMidFuture=true
@ -118,7 +118,6 @@
label="user.invited.invite.save_invite"
class="btn-primary save-invite"
action=(action "saveInvite")
disabled=disabled
}}
{{#if isEmail}}
@ -127,7 +126,6 @@
label=(if invite.emailed "user.invited.reinvite" "user.invited.invite.send_invite_email")
class="btn-primary send-invite"
action=(action "saveInvite" true)
disabled=disabled
}}
{{/if}}

View File

@ -1,7 +1,6 @@
import { click, fillIn, visit } from "@ember/test-helpers";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
import { test } from "qunit";
import I18n from "I18n";
acceptance("Invites - Create & Edit Invite Modal", function (needs) {
let deleted;
@ -23,7 +22,16 @@ acceptance("Invites - Create & Edit Invite Modal", function (needs) {
};
server.post("/invites", () => helper.response(inviteData));
server.put("/invites/1", () => helper.response(inviteData));
server.put("/invites/1", (request) => {
const data = helper.parsePostData(request.requestBody);
if (data.email === "error") {
return helper.response(422, {
errors: ["error isn't a valid email address."],
});
} else {
return helper.response(inviteData);
}
});
server.delete("/invites", () => {
deleted = true;
@ -95,11 +103,11 @@ acceptance("Invites - Create & Edit Invite Modal", function (needs) {
await visit("/u/eviltrout/invited/pending");
await click(".invite-controls .btn:first-child");
await click("#invite-type");
await fillIn("#invite-email", "error");
await click(".invite-link .btn");
assert.equal(
find("#modal-alert").text(),
I18n.t("user.invited.invite.blank_email")
"error isn't a valid email address."
);
});
});
@ -172,11 +180,9 @@ acceptance("Invites - Email Invites", function (needs) {
await visit("/u/eviltrout/invited/pending");
await click(".invite-controls .btn:first-child");
await click("#invite-type");
assert.ok(find("#invite-email").length, "shows email field");
await fillIn("#invite-email", "test@example.com");
assert.ok(find(".save-invite").length, "shows save without email button");
await click(".save-invite");
assert.ok(

View File

@ -832,7 +832,7 @@
.create-invite-modal,
.share-topic-modal {
.input-group {
margin-bottom: 1em;
margin-bottom: 0.5em;
&:last-child {
margin-bottom: 0;
@ -841,24 +841,24 @@
input[type="text"] {
width: 100%;
}
}
.invite-type {
input[type="checkbox"] {
display: inline;
vertical-align: middle;
margin-top: -1px;
textarea#invite-message,
&.invite-to-topic input[type="text"],
.group-chooser,
.user-chooser,
.future-date-input-selector {
margin-left: 25px;
width: calc(100% - 25px);
}
label {
display: inline-block;
&.invite-to-topic input[type="radio"] {
margin-left: 10px;
}
}
.group-chooser,
.user-chooser,
.future-date-input-selector {
width: 100%;
label .d-icon {
color: var(--primary-medium);
margin-right: 10px;
}
}
.input-group input[type="text"],
@ -868,10 +868,6 @@
height: 34px;
}
.input-group .btn {
vertical-align: top;
}
.invite-input-with-button {
display: flex;
@ -880,6 +876,16 @@
}
}
.input-group.input-expires-at,
.input-group.input-email,
.input-group.invite-max-redemptions {
margin-bottom: 0;
input[type="text"] {
width: unset;
}
}
.future-date-input {
.date-picker-wrapper {
input {
@ -900,6 +906,25 @@
}
}
.input-group.input-email {
align-items: baseline;
display: flex;
label {
display: inline;
}
.invite-input-with-button {
display: inline-flex;
flex: 1;
input[type="text"] {
flex: 1;
margin-left: 5px;
}
}
}
.invite-max-redemptions {
label {
display: inline;
@ -910,6 +935,12 @@
}
}
.invite-to-topic {
#choose-topic-title {
margin-bottom: 0;
}
}
.show-advanced {
margin-left: auto;
margin-right: 0;

View File

@ -1534,7 +1534,7 @@ en:
new_title: "Create Invite"
edit_title: "Edit Invite"
instructions: "Share this link to instantly grant access to this site:"
instructions: "Share this link to instantly grant access to this site"
copy_link: "copy link"
expires_in_time: "Expires in %{time}."
expired_at_time: "Expired at %{time}."
@ -1542,21 +1542,20 @@ en:
show_advanced: "Show Advanced Options"
hide_advanced: "Hide Advanced Options"
restrict_email: "Restrict the invite to one email address"
restrict_email: "Restrict to one email address"
max_redemptions_allowed: "Max number of uses:"
max_redemptions_allowed: "Max uses"
add_to_groups: "Add to groups:"
invite_to_topic: "Send to topic on first login:"
expires_at: "Expire after:"
custom_message: "Optional personal message:"
add_to_groups: "Add to groups"
invite_to_topic: "Arrive at this topic"
expires_at: "Expire after"
custom_message: "Optional personal message"
send_invite_email: "Save and Send Email"
save_invite: "Save Invite"
invite_saved: "Invite saved."
invite_copied: "Invite link copied."
blank_email: "Invite link not copied. Email address is required."
bulk_invite:
none: "No invitations to display on this page."