mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-23 07:38:45 -05:00
Invite Users step
This commit is contained in:
parent
35b767f6af
commit
ef84981e38
19 changed files with 373 additions and 62 deletions
|
@ -0,0 +1,16 @@
|
|||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
classNames: ['invite-list-user'],
|
||||
|
||||
@computed('user.role')
|
||||
roleName(role) {
|
||||
return this.get('roles').findProperty('id', role).label;
|
||||
},
|
||||
|
||||
actions: {
|
||||
removeUser(user) {
|
||||
this.sendAction('removeUser', user);
|
||||
}
|
||||
}
|
||||
});
|
62
app/assets/javascripts/wizard/components/invite-list.js.es6
Normal file
62
app/assets/javascripts/wizard/components/invite-list.js.es6
Normal file
|
@ -0,0 +1,62 @@
|
|||
export default Ember.Component.extend({
|
||||
classNames: ['invite-list'],
|
||||
users: null,
|
||||
inviteEmail: '',
|
||||
inviteRole: '',
|
||||
invalid: false,
|
||||
|
||||
init() {
|
||||
this._super();
|
||||
this.set('users', []);
|
||||
|
||||
this.set('roles', [
|
||||
{id: 'moderator', label: I18n.t('wizard.invites.roles.moderator') },
|
||||
{id: 'regular', label: I18n.t('wizard.invites.roles.regular') },
|
||||
]);
|
||||
|
||||
this.updateField();
|
||||
},
|
||||
|
||||
keyPress(e) {
|
||||
if (e.keyCode === 13) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.send('addUser');
|
||||
}
|
||||
},
|
||||
|
||||
updateField() {
|
||||
this.set('field.value', JSON.stringify(this.get('users')));
|
||||
},
|
||||
|
||||
actions: {
|
||||
addUser() {
|
||||
const user = {
|
||||
email: this.get('inviteEmail') || '',
|
||||
role: this.get('inviteRole')
|
||||
};
|
||||
|
||||
if (!/(.+)@(.+){2,}\.(.+){2,}/.test(user.email)) {
|
||||
return this.set('invalid', true);
|
||||
}
|
||||
|
||||
const users = this.get('users');
|
||||
if (users.findProperty('email', user.email)) {
|
||||
return this.set('invalid', true);
|
||||
}
|
||||
|
||||
this.set('invalid', false);
|
||||
|
||||
users.pushObject(user);
|
||||
this.updateField();
|
||||
|
||||
this.set('inviteEmail', '');
|
||||
Ember.run.scheduleOnce('afterRender', () => this.$('.invite-email').focus());
|
||||
},
|
||||
|
||||
removeUser(user) {
|
||||
this.get('users').removeObject(user);
|
||||
this.updateField();
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,6 @@
|
|||
<span class='email'>{{user.email}}</span>
|
||||
<span class='role'>{{roleName}}</span>
|
||||
|
||||
<button class="wizard-btn small danger remove-user" {{action "removeUser" user}}>
|
||||
{{fa-icon "times"}}
|
||||
</button>
|
|
@ -0,0 +1,20 @@
|
|||
|
||||
{{#if users}}
|
||||
<div class="users-list">
|
||||
{{#each users as |user|}}
|
||||
{{invite-list-user user=user roles=roles removeUser="removeUser"}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="new-user">
|
||||
<div class="text-field {{if invalid 'invalid'}}">
|
||||
{{input class="invite-email" value=inviteEmail placeholder="user@example.com"}}
|
||||
</div>
|
||||
|
||||
{{combo-box value=inviteRole content=roles nameProperty="label" width="200px"}}
|
||||
|
||||
<button class="wizard-btn small add-user" {{action "addUser"}}>
|
||||
{{fa-icon "plus"}}{{i18n "wizard.invites.add_user"}}
|
||||
</button>
|
||||
</div>
|
|
@ -1 +1,6 @@
|
|||
{{combo-box class=fieldClass value=field.value content=field.choices nameProperty="label" width="400px"}}
|
||||
{{combo-box elementId=field.id
|
||||
class=fieldClass
|
||||
value=field.value
|
||||
content=field.choices
|
||||
nameProperty="label"
|
||||
width="400px"}}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{{input value=field.value class=fieldClass placeholder=field.placeholder}}
|
||||
{{input elementId=field.id value=field.value class=fieldClass placeholder=field.placeholder}}
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
<label>
|
||||
<label for={{field.id}}>
|
||||
<span class='label-value'>{{field.label}}</span>
|
||||
|
||||
{{#if field.description}}
|
||||
<div class='field-description'>{{{field.description}}}</div>
|
||||
{{/if}}
|
||||
|
||||
<div class='input-area'>
|
||||
{{component inputComponentName field=field step=step fieldClass=fieldClass}}
|
||||
</div>
|
||||
|
||||
{{#if field.errorDescription}}
|
||||
<div class='field-error-description'>{{field.errorDescription}}</div>
|
||||
{{/if}}
|
||||
|
||||
</label>
|
||||
|
||||
<div class='input-area'>
|
||||
{{component inputComponentName field=field step=step fieldClass=fieldClass}}
|
||||
</div>
|
||||
|
||||
{{#if field.errorDescription}}
|
||||
<div class='field-error-description'>{{field.errorDescription}}</div>
|
||||
{{/if}}
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
{{/if}}
|
||||
|
||||
{{#if showNextButton}}
|
||||
<button class='wizard-btn next' {{action "nextStep"}} disabled={{saving}}>
|
||||
<button class='wizard-btn next primary' {{action "nextStep"}} disabled={{saving}}>
|
||||
{{i18n "wizard.next"}}
|
||||
{{fa-icon "chevron-right"}}
|
||||
</button>
|
||||
|
|
|
@ -1,4 +1,15 @@
|
|||
module("Acceptance: wizard");
|
||||
import startApp from 'wizard/test/helpers/start-app';
|
||||
|
||||
var wizard;
|
||||
module("Acceptance: wizard", {
|
||||
beforeEach() {
|
||||
wizard = startApp();
|
||||
},
|
||||
|
||||
teardown() {
|
||||
Ember.run(wizard, 'destroy');
|
||||
}
|
||||
});
|
||||
|
||||
test("Wizard starts", assert => {
|
||||
visit("/");
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import { componentTest } from 'wizard/test/helpers/component-test';
|
||||
|
||||
moduleForComponent('invite-list', { integration: true });
|
||||
|
||||
componentTest('can add users', {
|
||||
template: `{{invite-list field=field}}`,
|
||||
|
||||
setup() {
|
||||
this.set('field', {});
|
||||
},
|
||||
|
||||
test(assert) {
|
||||
assert.ok(this.$('.users-list .invite-list-user').length === 0, 'no users at first');
|
||||
assert.ok(this.$('.new-user .invalid').length === 0, 'not invalid at first');
|
||||
|
||||
const firstVal = JSON.parse(this.get('field.value'));
|
||||
assert.equal(firstVal.length, 0, 'empty JSON at first');
|
||||
|
||||
click('.add-user');
|
||||
andThen(() => {
|
||||
assert.ok(this.$('.users-list .invite-list-user').length === 0, "doesn't add a blank user");
|
||||
assert.ok(this.$('.new-user .invalid').length === 1);
|
||||
});
|
||||
|
||||
fillIn('.invite-email', 'eviltrout@example.com');
|
||||
click('.add-user');
|
||||
|
||||
andThen(() => {
|
||||
assert.ok(this.$('.users-list .invite-list-user').length === 1, 'adds the user');
|
||||
assert.ok(this.$('.new-user .invalid').length === 0);
|
||||
|
||||
const val = JSON.parse(this.get('field.value'));
|
||||
assert.equal(val.length, 1);
|
||||
assert.equal(val[0].email, 'eviltrout@example.com', 'adds the email to the JSON');
|
||||
assert.ok(val[0].role.length, 'adds the role to the JSON');
|
||||
});
|
||||
|
||||
fillIn('.invite-email', 'eviltrout@example.com');
|
||||
click('.add-user');
|
||||
|
||||
andThen(() => {
|
||||
assert.ok(this.$('.users-list .invite-list-user').length === 1, "can't add the same user twice");
|
||||
assert.ok(this.$('.new-user .invalid').length === 1);
|
||||
});
|
||||
|
||||
fillIn('.invite-email', 'not-an-email');
|
||||
click('.add-user');
|
||||
|
||||
andThen(() => {
|
||||
assert.ok(this.$('.users-list .invite-list-user').length === 1, "won't add an invalid email");
|
||||
assert.ok(this.$('.new-user .invalid').length === 1);
|
||||
});
|
||||
|
||||
click('.invite-list .invite-list-user:eq(0) .remove-user');
|
||||
andThen(() => {
|
||||
assert.ok(this.$('.users-list .invite-list-user').length === 0, 'removed the user');
|
||||
});
|
||||
}
|
||||
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
import initializer from 'wizard/initializers/load-helpers';
|
||||
|
||||
export function componentTest(name, opts) {
|
||||
opts = opts || {};
|
||||
|
||||
test(name, function(assert) {
|
||||
initializer.initialize();
|
||||
|
||||
if (opts.setup) {
|
||||
opts.setup.call(this);
|
||||
}
|
||||
|
||||
andThen(() => this.render(opts.template));
|
||||
andThen(() => opts.test.call(this, assert));
|
||||
});
|
||||
}
|
19
app/assets/javascripts/wizard/test/helpers/start-app.js.es6
Normal file
19
app/assets/javascripts/wizard/test/helpers/start-app.js.es6
Normal file
|
@ -0,0 +1,19 @@
|
|||
import Wizard from 'wizard/wizard';
|
||||
import initializer from 'wizard/initializers/load-helpers';
|
||||
|
||||
let app;
|
||||
let started = false;
|
||||
|
||||
export default function() {
|
||||
Ember.run(() => app = Wizard.create({ rootElement: '#ember-testing' }));
|
||||
|
||||
if (!started) {
|
||||
initializer.initialize();
|
||||
app.start();
|
||||
started = true;
|
||||
}
|
||||
app.setupForTesting();
|
||||
app.injectTestHelpers();
|
||||
return app;
|
||||
}
|
||||
|
|
@ -12,8 +12,10 @@
|
|||
//= require wizard-application
|
||||
//= require wizard-vendor
|
||||
//= require helpers/assertions
|
||||
//= require_tree ./helpers
|
||||
//= require_tree ./acceptance
|
||||
//= require_tree ./models
|
||||
//= require_tree ./components
|
||||
//= require locales/en
|
||||
//= require fake_xml_http_request
|
||||
//= require route-recognizer
|
||||
|
@ -42,13 +44,10 @@ QUnit.testDone(function() {
|
|||
server.shutdown();
|
||||
});
|
||||
|
||||
var wizard = require('wizard/wizard').default.create({
|
||||
rootElement: '#ember-testing'
|
||||
});
|
||||
require('wizard/test/helpers/start-app').default();
|
||||
|
||||
wizard.setupForTesting();
|
||||
wizard.injectTestHelpers();
|
||||
wizard.start();
|
||||
var buildResolver = require('discourse-common/resolver').buildResolver;
|
||||
window.setResolver(buildResolver('wizard').create());
|
||||
|
||||
Object.keys(requirejs.entries).forEach(function(entry) {
|
||||
if ((/\-test/).test(entry)) {
|
||||
|
|
|
@ -84,6 +84,11 @@ body.wizard {
|
|||
color: #333;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, .4);
|
||||
|
||||
&.small {
|
||||
padding: 0.25em 0.5em;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
@ -110,6 +115,42 @@ body.wizard {
|
|||
}
|
||||
}
|
||||
|
||||
.wizard-btn.primary {
|
||||
background-color: #6699ff;
|
||||
color: white;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, .6);
|
||||
|
||||
&:hover {
|
||||
background-color: #80B3FF;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: #4D80E6;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: #000167;
|
||||
}
|
||||
}
|
||||
|
||||
.wizard-btn.danger {
|
||||
background-color: #E60000;
|
||||
color: white;
|
||||
|
||||
&:hover {
|
||||
background-color: #CC0000;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: #B30000;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: #990000;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.wizard-btn-upload {
|
||||
clear: both;
|
||||
display: inline-block;
|
||||
|
@ -125,22 +166,6 @@ body.wizard {
|
|||
align-items: center;
|
||||
|
||||
.wizard-btn.next {
|
||||
background-color: #6699ff;
|
||||
color: white;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, .6);
|
||||
|
||||
&:hover {
|
||||
background-color: #80B3FF;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: #4D80E6;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: #000167;
|
||||
}
|
||||
|
||||
min-width: 70px;
|
||||
|
||||
i.fa-chevron-right {
|
||||
|
@ -240,29 +265,29 @@ body.wizard {
|
|||
}
|
||||
}
|
||||
|
||||
&.text-field {
|
||||
input {
|
||||
width: 100%;
|
||||
font-size: 1.2em;
|
||||
padding: 6px;
|
||||
border: 1px solid #ccc;
|
||||
transition: border-color .5s;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&.invalid {
|
||||
input {
|
||||
padding: 3px;
|
||||
border: 4px solid red;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
.text-field {
|
||||
input {
|
||||
width: 100%;
|
||||
font-size: 1.2em;
|
||||
padding: 6px;
|
||||
border: 1px solid #ccc;
|
||||
transition: border-color .5s;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&.invalid {
|
||||
input {
|
||||
padding: 3px;
|
||||
border: 4px solid red;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.radio-field-choice {
|
||||
margin-bottom: 1.5em;
|
||||
|
||||
|
@ -280,3 +305,42 @@ body.wizard {
|
|||
color: #777;
|
||||
}
|
||||
}
|
||||
|
||||
.invite-list {
|
||||
.users-list {
|
||||
margin-bottom: 1em;
|
||||
|
||||
.invite-list-user {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1em;
|
||||
|
||||
.email {
|
||||
width: 330px;
|
||||
}
|
||||
|
||||
.role {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.new-user {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1em;
|
||||
|
||||
.invite-email {
|
||||
width: 350px;
|
||||
}
|
||||
}
|
||||
|
||||
button.add-user {
|
||||
.fa {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3234,3 +3234,11 @@ en:
|
|||
upload: "Upload"
|
||||
uploading: "Uploading..."
|
||||
|
||||
invites:
|
||||
add_user: "add"
|
||||
roles:
|
||||
admin: "Admin"
|
||||
moderator: "Moderator"
|
||||
regular: "Regular User"
|
||||
|
||||
|
||||
|
|
|
@ -3311,6 +3311,10 @@ en:
|
|||
label: "Large Icon"
|
||||
description: "Icon used for Apple touch devices. Recommended size is 144px by 144px."
|
||||
|
||||
invites:
|
||||
title: "Invite Staff"
|
||||
description: "We recommend you invite some staff members to help you get things started."
|
||||
|
||||
finished:
|
||||
title: "Your Discourse Forum is Ready!"
|
||||
description: |
|
||||
|
|
|
@ -129,6 +129,18 @@ class Wizard
|
|||
end
|
||||
end
|
||||
|
||||
@wizard.append_step('invites') do |step|
|
||||
step.add_field(id: 'invite_list', type: 'component')
|
||||
|
||||
step.on_update do |updater|
|
||||
users = JSON.parse(updater.fields[:invite_list])
|
||||
|
||||
users.each do |u|
|
||||
Invite.create_invite_by_email(u['email'], @wizard.user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
DiscourseEvent.trigger(:build_wizard, @wizard)
|
||||
|
||||
@wizard.append_step('finished')
|
||||
|
|
|
@ -141,7 +141,6 @@ describe Wizard::StepUpdater do
|
|||
end
|
||||
|
||||
context "logos step" do
|
||||
|
||||
it "updates the fields correctly" do
|
||||
updater = wizard.create_updater('logos',
|
||||
logo_url: '/uploads/logo.png',
|
||||
|
@ -158,5 +157,22 @@ describe Wizard::StepUpdater do
|
|||
end
|
||||
end
|
||||
|
||||
context "invites step" do
|
||||
|
||||
let(:invites) {
|
||||
return [{ email: 'regular@example.com', role: 'regular'},
|
||||
{ email: 'moderator@example.com', role: 'moderator'}]
|
||||
}
|
||||
|
||||
it "updates the fields correctly" do
|
||||
updater = wizard.create_updater('invites', invite_list: invites.to_json)
|
||||
updater.update
|
||||
|
||||
expect(updater).to be_success
|
||||
|
||||
expect(Invite.where(email: 'regular@example.com')).to be_present
|
||||
expect(Invite.where(email: 'moderator@example.com')).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -18,12 +18,6 @@ describe ExtraLocalesController do
|
|||
get :show, bundle: '-invalid..character!!'
|
||||
expect(response).to_not be_success
|
||||
end
|
||||
|
||||
it "works with a valid bundle" do
|
||||
get :show, bundle: 'admin'
|
||||
expect(response).to be_success
|
||||
expect(response.body).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue