diff --git a/app/assets/javascripts/discourse/app/controllers/create-invite.js b/app/assets/javascripts/discourse/app/controllers/create-invite.js index 3237366b041..d24e4525685 100644 --- a/app/assets/javascripts/discourse/app/controllers/create-invite.js +++ b/app/assets/javascripts/discourse/app/controllers/create-invite.js @@ -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"); diff --git a/app/assets/javascripts/discourse/app/templates/components/choose-topic.hbs b/app/assets/javascripts/discourse/app/templates/components/choose-topic.hbs index bae33d6b875..7d384bccb21 100644 --- a/app/assets/javascripts/discourse/app/templates/components/choose-topic.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/choose-topic.hbs @@ -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"}} diff --git a/app/assets/javascripts/discourse/app/templates/components/future-date-input.hbs b/app/assets/javascripts/discourse/app/templates/components/future-date-input.hbs index bb629c45272..2ff6e7de08c 100644 --- a/app/assets/javascripts/discourse/app/templates/components/future-date-input.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/future-date-input.hbs @@ -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 diff --git a/app/assets/javascripts/discourse/app/templates/modal/create-invite.hbs b/app/assets/javascripts/discourse/app/templates/modal/create-invite.hbs index 941531c2631..9063845f08d 100644 --- a/app/assets/javascripts/discourse/app/templates/modal/create-invite.hbs +++ b/app/assets/javascripts/discourse/app/templates/modal/create-invite.hbs @@ -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}} diff --git a/app/assets/javascripts/discourse/tests/acceptance/create-invite-modal-test.js b/app/assets/javascripts/discourse/tests/acceptance/create-invite-modal-test.js index 1aed75b4ad7..80afabea8e9 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/create-invite-modal-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/create-invite-modal-test.js @@ -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( diff --git a/app/assets/stylesheets/common/base/modal.scss b/app/assets/stylesheets/common/base/modal.scss index c6649014b29..7da41a3b239 100644 --- a/app/assets/stylesheets/common/base/modal.scss +++ b/app/assets/stylesheets/common/base/modal.scss @@ -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; diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 983f955b02a..63adc3abb4a 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -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."