diff --git a/app/assets/javascripts/discourse/app/components/future-date-input.js b/app/assets/javascripts/discourse/app/components/future-date-input.js index f673f73ae5c..3f17a595ec1 100644 --- a/app/assets/javascripts/discourse/app/components/future-date-input.js +++ b/app/assets/javascripts/discourse/app/components/future-date-input.js @@ -1,8 +1,10 @@ -import { and, empty, equal } from "@ember/object/computed"; -import { action } from "@ember/object"; import Component from "@ember/component"; -import { FORMAT } from "select-kit/components/future-date-input-selector"; +import { action } from "@ember/object"; +import { and, empty, equal } from "@ember/object/computed"; +import { CLOSE_STATUS_TYPE } from "discourse/controllers/edit-topic-timer"; +import buildTimeframes from "discourse/lib/timeframes-builder"; import I18n from "I18n"; +import { FORMAT } from "select-kit/components/future-date-input-selector"; export default Component.extend({ selection: null, @@ -20,12 +22,17 @@ export default Component.extend({ this._super(...arguments); if (this.input) { - const datetime = moment(this.input); - this.setProperties({ - selection: "pick_date_and_time", - _date: datetime.format("YYYY-MM-DD"), - _time: datetime.format("HH:mm"), - }); + const dateTime = moment(this.input); + const closestTimeframe = this.findClosestTimeframe(dateTime); + if (closestTimeframe) { + this.set("selection", closestTimeframe.id); + } else { + this.setProperties({ + selection: "pick_date_and_time", + _date: dateTime.format("YYYY-MM-DD"), + _time: dateTime.format("HH:mm"), + }); + } } }, @@ -64,4 +71,31 @@ export default Component.extend({ this.attrs.onChangeInput && this.attrs.onChangeInput(null); } }, + + findClosestTimeframe(dateTime) { + const now = moment(); + + const futureDateInputSelectorOptions = { + now, + day: now.day(), + includeWeekend: this.includeWeekend, + includeMidFuture: this.includeMidFuture || true, + includeFarFuture: this.includeFarFuture, + includeDateTime: this.includeDateTime, + canScheduleNow: this.includeNow || false, + canScheduleToday: 24 - now.hour() > 6, + }; + + return buildTimeframes(futureDateInputSelectorOptions).find((tf) => { + const tfDateTime = tf.when( + moment(), + this.statusType !== CLOSE_STATUS_TYPE ? 8 : 18 + ); + + if (tfDateTime) { + const diff = tfDateTime.diff(dateTime); + return 0 <= diff && diff < 60 * 1000; + } + }); + }, }); diff --git a/app/assets/javascripts/discourse/app/controllers/create-invite.js b/app/assets/javascripts/discourse/app/controllers/create-invite.js index 1a774042373..2a4cb7e8412 100644 --- a/app/assets/javascripts/discourse/app/controllers/create-invite.js +++ b/app/assets/javascripts/discourse/app/controllers/create-invite.js @@ -9,6 +9,7 @@ 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"; export default Controller.extend( ModalFunctionality, @@ -16,13 +17,16 @@ export default Controller.extend( { allGroups: null, + flashText: null, + flashClass: null, + flashLink: false, + invite: null, invites: null, - showAdvanced: false, + editing: false, inviteToTopic: false, limitToEmail: false, - autogenerated: false, isLink: empty("buffered.email"), isEmail: notEmpty("buffered.email"), @@ -33,37 +37,33 @@ export default Controller.extend( }); this.setProperties({ + flashText: null, + flashClass: null, + flashLink: false, invite: null, invites: null, - showAdvanced: false, + editing: false, inviteToTopic: false, limitToEmail: false, - autogenerated: 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() { - if (this.autogenerated) { - this.invite - .destroy() - .then(() => this.invites && this.invites.removeObject(this.invite)); - } + this.appEvents.trigger("modal-body:clearFlash"); }, setInvite(invite) { this.set("invite", invite); }, - setAutogenerated(value) { - if (this.invites && (this.autogenerated || !this.invite.id) && !value) { - this.invites.unshiftObject(this.invite); - } - - this.set("autogenerated", value); - }, - save(opts) { const data = { ...this.buffered.buffer }; @@ -101,29 +101,37 @@ export default Controller.extend( .save(data) .then((result) => { this.rollbackBuffer(); - this.setAutogenerated(opts.autogenerated); + + if ( + this.invites && + !this.invites.any((i) => i.id === this.invite.id) + ) { + this.invites.unshiftObject(this.invite); + } + if (result.warnings) { - this.appEvents.trigger("modal-body:flash", { - text: result.warnings.join(","), - messageClass: "warning", + this.setProperties({ + flashText: result.warnings.join(","), + flashClass: "warning", + flashLink: !this.editing, }); - } else if (!this.autogenerated) { + } else { if (this.isEmail && opts.sendEmail) { this.send("closeModal"); } else { - this.appEvents.trigger("modal-body:flash", { - text: opts.copy - ? I18n.t("user.invited.invite.invite_copied") - : I18n.t("user.invited.invite.invite_saved"), - messageClass: "success", + this.setProperties({ + flashText: I18n.t("user.invited.invite.invite_saved"), + flashClass: "success", + flashLink: !this.editing, }); } } }) .catch((e) => - this.appEvents.trigger("modal-body:flash", { - text: extractError(e), - messageClass: "error", + this.setProperties({ + flashText: extractError(e), + flashClass: "error", + flashLink: false, }) ); }, @@ -155,11 +163,6 @@ export default Controller.extend( return staff || groups.any((g) => g.owner); }, - @discourseComputed("currentUser.staff", "isEmail", "canInviteToGroup") - hasAdvanced(staff, isEmail, canInviteToGroup) { - return staff || isEmail || canInviteToGroup; - }, - @action copied() { this.save({ sendEmail: false, copy: true }); @@ -178,10 +181,5 @@ export default Controller.extend( this.set("buffered.email", result[0].email[0]); }); }, - - @action - toggleAdvanced() { - this.toggleProperty("showAdvanced"); - }, } ); diff --git a/app/assets/javascripts/discourse/app/controllers/share-topic.js b/app/assets/javascripts/discourse/app/controllers/share-topic.js index d1c3657be02..e3cf0ed5c92 100644 --- a/app/assets/javascripts/discourse/app/controllers/share-topic.js +++ b/app/assets/javascripts/discourse/app/controllers/share-topic.js @@ -128,15 +128,11 @@ export default Controller.extend( inviteUsers() { this.set("showNotifyUsers", false); const controller = showModal("create-invite"); - controller.setProperties({ - showAdvanced: true, - inviteToTopic: true, - }); + controller.set("inviteToTopic", true); controller.buffered.setProperties({ topicId: this.topic.id, topicTitle: this.topic.title, }); - controller.save({ autogenerated: true }); }, } ); diff --git a/app/assets/javascripts/discourse/app/controllers/user-invited-show.js b/app/assets/javascripts/discourse/app/controllers/user-invited-show.js index f7f98519a99..50c36460c10 100644 --- a/app/assets/javascripts/discourse/app/controllers/user-invited-show.js +++ b/app/assets/javascripts/discourse/app/controllers/user-invited-show.js @@ -66,7 +66,6 @@ export default Controller.extend({ createInvite() { const controller = showModal("create-invite"); controller.set("invites", this.model.invites); - controller.save({ autogenerated: true }); }, @action @@ -77,7 +76,7 @@ export default Controller.extend({ @action editInvite(invite) { const controller = showModal("create-invite"); - controller.set("showAdvanced", true); + controller.set("editing", true); controller.setInvite(invite); }, diff --git a/app/assets/javascripts/discourse/app/routes/group-index.js b/app/assets/javascripts/discourse/app/routes/group-index.js index 1a9ff92a926..c1286780900 100644 --- a/app/assets/javascripts/discourse/app/routes/group-index.js +++ b/app/assets/javascripts/discourse/app/routes/group-index.js @@ -32,9 +32,7 @@ export default DiscourseRoute.extend({ showInviteModal() { const model = this.modelFor("group"); const controller = showModal("create-invite"); - controller.set("showAdvanced", true); controller.buffered.set("groupIds", [model.id]); - controller.save({ autogenerated: true }); }, @action 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 6754afe8d78..d31844c4846 100644 --- a/app/assets/javascripts/discourse/app/templates/modal/create-invite.hbs +++ b/app/assets/javascripts/discourse/app/templates/modal/create-invite.hbs @@ -1,21 +1,40 @@ -{{#d-modal-body title=(if invite.id "user.invited.invite.edit_title" "user.invited.invite.new_title")}} -
-
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 27d6abfe897..f4e577baabf 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 @@ -4,15 +4,12 @@ import { count, exists, fakeTime, - query, queryAll, } from "discourse/tests/helpers/qunit-helpers"; -import { test } from "qunit"; import I18n from "I18n"; +import { test } from "qunit"; acceptance("Invites - Create & Edit Invite Modal", function (needs) { - let deleted; - needs.user(); needs.pretender((server, helper) => { const inviteData = { @@ -42,30 +39,17 @@ acceptance("Invites - Create & Edit Invite Modal", function (needs) { }); server.delete("/invites", () => { - deleted = true; return helper.response({}); }); }); - needs.hooks.beforeEach(() => { - deleted = false; - }); test("basic functionality", async function (assert) { await visit("/u/eviltrout/invited/pending"); await click(".user-invite-buttons .btn:first-child"); - assert.strictEqual( - query("input.invite-link").value, - "http://example.com/invites/52641ae8878790bc7b79916247cfe6ba", - "shows an invite link when modal is opened" - ); - await click(".modal-footer .show-advanced"); - await assert.ok(exists(".invite-to-groups"), "shows advanced options"); - await assert.ok(exists(".invite-to-topic"), "shows advanced options"); - await assert.ok(exists(".invite-expires-at"), "shows advanced options"); - - await click(".modal-close"); - assert.ok(deleted, "deletes the invite if not saved"); + await assert.ok(exists(".invite-to-groups")); + await assert.ok(exists(".invite-to-topic")); + await assert.ok(exists(".invite-expires-at")); }); test("saving", async function (assert) { @@ -81,31 +65,14 @@ acceptance("Invites - Create & Edit Invite Modal", function (needs) { 1, "adds invite to list after saving" ); - - await click(".modal-close"); - assert.notOk(deleted, "does not delete invite on close"); }); test("copying saves invite", async function (assert) { await visit("/u/eviltrout/invited/pending"); await click(".user-invite-buttons .btn:first-child"); - await click(".invite-link .btn"); - - await click(".modal-close"); - assert.notOk(deleted, "does not delete invite on close"); - }); - - test("copying an email invite without an email shows error message", async function (assert) { - await visit("/u/eviltrout/invited/pending"); - await click(".user-invite-buttons .btn:first-child"); - - await fillIn("#invite-email", "error"); - await click(".invite-link .btn"); - assert.strictEqual( - query("#modal-alert").innerText, - "error isn't a valid email address." - ); + await click(".save-invite"); + assert.ok(exists(".invite-link .btn")); }); }); @@ -159,7 +126,10 @@ acceptance("Invites - Email Invites", function (needs) { groups: [], }; - server.post("/invites", () => helper.response(inviteData)); + server.post("/invites", (request) => { + lastRequest = request; + return helper.response(inviteData); + }); server.put("/invites/1", (request) => { lastRequest = request; @@ -232,7 +202,6 @@ acceptance( await visit("/u/eviltrout/invited/pending"); await click(".user-invite-buttons .btn:first-child"); - await click(".modal-footer .show-advanced"); await click(".future-date-input-selector-header"); const options = Array.from( diff --git a/app/assets/stylesheets/common/base/share_link.scss b/app/assets/stylesheets/common/base/share_link.scss index 14cc9532605..396d2c8a1c4 100644 --- a/app/assets/stylesheets/common/base/share_link.scss +++ b/app/assets/stylesheets/common/base/share_link.scss @@ -182,9 +182,10 @@ } } - .show-advanced { - margin-left: auto; - margin-right: 0; + .invite-custom-message { + label { + margin-left: 1.75em; + } } .input-group { @@ -198,5 +199,16 @@ margin-left: 1.75em; width: calc(100% - 1.75em); } + + .future-date-input-date-picker, + .future-date-input-time-picker { + display: inline-block; + margin: 0em 0em 0em 1.75em; + width: calc(50% - 2em); + + input { + height: 34px; + } + } } } diff --git a/app/assets/stylesheets/desktop/modal.scss b/app/assets/stylesheets/desktop/modal.scss index 3c7368df849..c4a54245d22 100644 --- a/app/assets/stylesheets/desktop/modal.scss +++ b/app/assets/stylesheets/desktop/modal.scss @@ -111,6 +111,11 @@ .create-invite-modal, .create-invite-bulk-modal, .share-topic-modal { + &.modal .modal-body { + margin: 1em; + padding: unset; + } + .modal-inner-container { width: 40em; // scale with user font-size max-width: 100vw; // prevent overflow if user font-size is enourmous diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index bb9066166bf..7fe17831b6d 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1613,7 +1613,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}" @@ -1621,20 +1621,20 @@ en: show_advanced: "Show Advanced Options" hide_advanced: "Hide Advanced Options" - restrict_email: "Restrict to one email address" + restrict_email: "Restrict to email" max_redemptions_allowed: "Max uses" add_to_groups: "Add to groups" - invite_to_topic: "Arrive at this topic" + invite_to_topic: "Arrive at topic" expires_at: "Expire after" custom_message: "Optional personal message" send_invite_email: "Save and Send Email" + send_invite_email_instructions: "Restrict invite to email to send an invite email" save_invite: "Save Invite" invite_saved: "Invite saved." - invite_copied: "Invite link copied." bulk_invite: none: "No invitations to display on this page." diff --git a/config/site_settings.yml b/config/site_settings.yml index a927de4c5fd..a1bad6eab04 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -586,6 +586,7 @@ users: default: true invite_expiry_days: default: 30 + client: true max: 36500 invites_per_page: client: true