Improved create invite modal (#14151)
* FEATURE: Always show advanced invite options The UI is more simple and more efficient than how it was when the advanced options toggle was introduced. It does not make sense to keep it anymore. * UX: Minor copy edits * UX: Merge expire invite controls There were two controls in the create invite modal. One was a static text that displayed how much time is left until the invite expires. The other one was a datetime selector that set the time the invite expires. This commit merges the two controls in a single one: staff users will continue to see the datetime selector without the static text and regular users will only see the static text because they cannot set when the invite expires. * UX: Remove invite link It should only be visible after the invite was created.
This commit is contained in:
parent
ed2c3ebd71
commit
6ae065f9cd
|
@ -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;
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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");
|
||||
},
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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 });
|
||||
},
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,21 +1,40 @@
|
|||
{{#d-modal-body title=(if invite.id "user.invited.invite.edit_title" "user.invited.invite.new_title")}}
|
||||
<form>
|
||||
<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=invite.link
|
||||
readonly=true
|
||||
}}
|
||||
{{copy-button selector="input.invite-link" copied=(action "copied")}}
|
||||
{{#if flashText}}
|
||||
<div id="modal-alert" role="alert" class="alert alert-{{flashClass}}">
|
||||
{{#if flashLink}}
|
||||
<div class="input-group invite-link">
|
||||
<label for="invite-link">{{flashText}} {{i18n "user.invited.invite.instructions"}}</label>
|
||||
<div class="link-share-container">
|
||||
{{input
|
||||
name="invite-link"
|
||||
class="invite-link"
|
||||
value=invite.link
|
||||
readonly=true
|
||||
}}
|
||||
{{copy-button selector="input.invite-link" copied=(action "copied")}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
{{flashText}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="input-group input-expires-at">
|
||||
<label>{{d-icon "far-clock"}}{{expiresAtLabel}}</label>
|
||||
</div>
|
||||
{{#d-modal-body title=(if editing "user.invited.invite.edit_title" "user.invited.invite.new_title")}}
|
||||
<form>
|
||||
{{#if 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=invite.link
|
||||
readonly=true
|
||||
}}
|
||||
{{copy-button selector="input.invite-link" copied=(action "copied")}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="input-group input-email">
|
||||
<label for="invite-email">{{d-icon "envelope"}}{{i18n "user.invited.invite.restrict_email"}}</label>
|
||||
|
@ -48,66 +67,64 @@
|
|||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if showAdvanced}}
|
||||
{{#if isEmail}}
|
||||
<div class="input-group invite-custom-message">
|
||||
<label for="invite-message">{{d-icon "envelope"}}{{i18n "user.invited.invite.custom_message"}}</label>
|
||||
{{textarea id="invite-message" value=buffered.custom_message}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if isEmail}}
|
||||
<div class="input-group invite-custom-message">
|
||||
<label for="invite-message">{{i18n "user.invited.invite.custom_message"}}</label>
|
||||
{{textarea id="invite-message" value=buffered.custom_message}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if showAdvanced}}
|
||||
{{#if currentUser.staff}}
|
||||
<div class="input-group invite-to-topic">
|
||||
{{choose-topic
|
||||
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 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=buffered.topicTitle
|
||||
readonly=true
|
||||
}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if currentUser.staff}}
|
||||
<div class="input-group invite-to-topic">
|
||||
{{choose-topic
|
||||
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 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=buffered.topicTitle
|
||||
readonly=true
|
||||
}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if showAdvanced}}
|
||||
{{#if canInviteToGroup}}
|
||||
<div class="input-group invite-to-groups">
|
||||
<label>{{d-icon "users"}}{{i18n "user.invited.invite.add_to_groups"}}</label>
|
||||
{{group-chooser
|
||||
content=allGroups
|
||||
value=buffered.groupIds
|
||||
labelProperty="name"
|
||||
onChange=(action (mut buffered.groupIds))
|
||||
}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if canInviteToGroup}}
|
||||
<div class="input-group invite-to-groups">
|
||||
<label>{{d-icon "users"}}{{i18n "user.invited.invite.add_to_groups"}}</label>
|
||||
{{group-chooser
|
||||
content=allGroups
|
||||
value=buffered.groupIds
|
||||
labelProperty="name"
|
||||
onChange=(action (mut buffered.groupIds))
|
||||
}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if showAdvanced}}
|
||||
{{#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
|
||||
clearable=true
|
||||
onChangeInput=(action (mut buffered.expires_at))
|
||||
}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if currentUser.staff}}
|
||||
<div class="input-group invite-expires-at">
|
||||
{{future-date-input
|
||||
displayLabelIcon="far-clock"
|
||||
displayLabel=(i18n "user.invited.invite.expires_at")
|
||||
statusType="close"
|
||||
includeDateTime=true
|
||||
includeMidFuture=true
|
||||
clearable=true
|
||||
input=buffered.expires_at
|
||||
onChangeInput=(action (mut buffered.expires_at))
|
||||
}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="input-group input-expires-at">
|
||||
<label>{{d-icon "far-clock"}}{{expiresAtLabel}}</label>
|
||||
</div>
|
||||
{{/if}}
|
||||
</form>
|
||||
{{/d-modal-body}}
|
||||
|
@ -120,21 +137,12 @@
|
|||
action=(action "saveInvite")
|
||||
}}
|
||||
|
||||
{{#if isEmail}}
|
||||
{{d-button
|
||||
icon="envelope"
|
||||
label=(if invite.emailed "user.invited.reinvite" "user.invited.invite.send_invite_email")
|
||||
class="btn-primary send-invite"
|
||||
action=(action "saveInvite" true)
|
||||
}}
|
||||
{{/if}}
|
||||
|
||||
{{#if hasAdvanced}}
|
||||
{{d-button
|
||||
action=(action "toggleAdvanced")
|
||||
class="btn-default show-advanced"
|
||||
icon="cog"
|
||||
title=(if showAdvanced "user.invited.invite.hide_advanced" "user.invited.invite.show_advanced")
|
||||
}}
|
||||
{{/if}}
|
||||
{{d-button
|
||||
icon="envelope"
|
||||
label=(if invite.emailed "user.invited.reinvite" "user.invited.invite.send_invite_email")
|
||||
class="btn-primary send-invite"
|
||||
action=(action "saveInvite" true)
|
||||
title=(unless isEmail "user.invited.invite.send_invite_email_instructions")
|
||||
disabled=(not isEmail)
|
||||
}}
|
||||
</div>
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -586,6 +586,7 @@ users:
|
|||
default: true
|
||||
invite_expiry_days:
|
||||
default: 30
|
||||
client: true
|
||||
max: 36500
|
||||
invites_per_page:
|
||||
client: true
|
||||
|
|
Loading…
Reference in New Issue