diff --git a/app/assets/javascripts/discourse/controllers/invite.js.es6 b/app/assets/javascripts/discourse/controllers/invite.js.es6
index 1f7179111..3995b51f2 100644
--- a/app/assets/javascripts/discourse/controllers/invite.js.es6
+++ b/app/assets/javascripts/discourse/controllers/invite.js.es6
@@ -3,7 +3,7 @@ import ModalFunctionality from 'discourse/mixins/modal-functionality';
import ObjectController from 'discourse/controllers/object';
export default ObjectController.extend(Presence, ModalFunctionality, {
- needs: ['user-invited'],
+ needs: ['user-invited-show'],
// If this isn't defined, it will proxy to the user model on the preferences
// page which is wrong.
@@ -132,14 +132,14 @@ export default ObjectController.extend(Presence, ModalFunctionality, {
if (this.get('disabled')) { return; }
const groupNames = this.get('groupNames'),
- userInvitedController = this.get('controllers.user-invited');
+ userInvitedController = this.get('controllers.user-invited-show');
this.setProperties({ saving: true, error: false });
return this.get('model').createInvite(this.get('emailOrUsername').trim(), groupNames).then(result => {
this.setProperties({ saving: false, finished: true });
if (!this.get('invitingToTopic')) {
- Discourse.Invite.findInvitedBy(Discourse.User.current()).then(invite_model => {
+ Discourse.Invite.findInvitedBy(Discourse.User.current(), userInvitedController.get('filter')).then(invite_model => {
userInvitedController.set('model', invite_model);
userInvitedController.set('totalInvites', invite_model.invites.length);
});
diff --git a/app/assets/javascripts/discourse/controllers/user-invited.js.es6 b/app/assets/javascripts/discourse/controllers/user-invited-show.js.es6
similarity index 88%
rename from app/assets/javascripts/discourse/controllers/user-invited.js.es6
rename to app/assets/javascripts/discourse/controllers/user-invited-show.js.es6
index 7499b2e5a..7e9111a10 100644
--- a/app/assets/javascripts/discourse/controllers/user-invited.js.es6
+++ b/app/assets/javascripts/discourse/controllers/user-invited-show.js.es6
@@ -2,6 +2,7 @@
export default Ember.ObjectController.extend({
user: null,
model: null,
+ filter: null,
totalInvites: null,
canLoadMore: true,
invitesLoading: false,
@@ -20,11 +21,13 @@ export default Ember.ObjectController.extend({
**/
_searchTermChanged: Discourse.debounce(function() {
var self = this;
- Discourse.Invite.findInvitedBy(self.get('user'), this.get('searchTerm')).then(function (invites) {
+ Discourse.Invite.findInvitedBy(self.get('user'), this.get('filter'), this.get('searchTerm')).then(function (invites) {
self.set('model', invites);
});
}, 250).observes('searchTerm'),
+ inviteRedeemed: Em.computed.equal('filter', 'redeemed'),
+
/**
Can the currently logged in user invite users to the site
@@ -82,7 +85,7 @@ export default Ember.ObjectController.extend({
if (self.get('canLoadMore') && !self.get('invitesLoading')) {
self.set('invitesLoading', true);
- Discourse.Invite.findInvitedBy(self.get('user'), self.get('searchTerm'), model.invites.length).then(function(invite_model) {
+ Discourse.Invite.findInvitedBy(self.get('user'), self.get('filter'), self.get('searchTerm'), model.invites.length).then(function(invite_model) {
self.set('invitesLoading', false);
model.invites.pushObjects(invite_model.invites);
if(invite_model.invites.length === 0 || invite_model.invites.length < Discourse.SiteSettings.invites_per_page) {
diff --git a/app/assets/javascripts/discourse/models/invite.js b/app/assets/javascripts/discourse/models/invite.js
index f90f38716..da1cfd161 100644
--- a/app/assets/javascripts/discourse/models/invite.js
+++ b/app/assets/javascripts/discourse/models/invite.js
@@ -37,11 +37,12 @@ Discourse.Invite.reopenClass({
return result;
},
- findInvitedBy: function(user, filter, offset) {
+ findInvitedBy: function(user, filter, search, offset) {
if (!user) { return Em.RSVP.resolve(); }
var data = {};
if (!Em.isNone(filter)) { data.filter = filter; }
+ if (!Em.isNone(search)) { data.search = search; }
data.offset = offset || 0;
return Discourse.ajax("/users/" + user.get('username_lower') + "/invited.json", {data: data}).then(function (result) {
diff --git a/app/assets/javascripts/discourse/routes/app-route-map.js.es6 b/app/assets/javascripts/discourse/routes/app-route-map.js.es6
index c16efc9ed..98e1194df 100644
--- a/app/assets/javascripts/discourse/routes/app-route-map.js.es6
+++ b/app/assets/javascripts/discourse/routes/app-route-map.js.es6
@@ -79,7 +79,9 @@ export default function() {
this.route('card-badge', { path: '/card-badge' });
});
- this.route('invited');
+ this.resource('userInvited', { path: '/invited' }, function() {
+ this.route('show', { path: '/:filter' });
+ });
});
this.route('signup', {path: '/signup'});
diff --git a/app/assets/javascripts/discourse/routes/user-invited-index.js.es6 b/app/assets/javascripts/discourse/routes/user-invited-index.js.es6
new file mode 100644
index 000000000..e11dea722
--- /dev/null
+++ b/app/assets/javascripts/discourse/routes/user-invited-index.js.es6
@@ -0,0 +1,5 @@
+export default Discourse.Route.extend({
+ beforeModel: function() {
+ this.replaceWith('userInvited.show', 'redeemed');
+ }
+});
diff --git a/app/assets/javascripts/discourse/routes/user-invited.js.es6 b/app/assets/javascripts/discourse/routes/user-invited-show.js.es6
similarity index 81%
rename from app/assets/javascripts/discourse/routes/user-invited.js.es6
rename to app/assets/javascripts/discourse/routes/user-invited-show.js.es6
index a36f49af4..a5e391093 100644
--- a/app/assets/javascripts/discourse/routes/user-invited.js.es6
+++ b/app/assets/javascripts/discourse/routes/user-invited-show.js.es6
@@ -2,18 +2,17 @@ import ShowFooter from 'discourse/mixins/show-footer';
import showModal from 'discourse/lib/show-modal';
export default Discourse.Route.extend(ShowFooter, {
- renderTemplate() {
- this.render({ into: 'user' });
- },
- model() {
- return Discourse.Invite.findInvitedBy(this.modelFor('user'));
+ model: function(params) {
+ this.inviteFilter = params.filter;
+ return Discourse.Invite.findInvitedBy(this.modelFor('user'), params.filter);
},
setupController(controller, model) {
controller.setProperties({
model: model,
user: this.controllerFor('user').get('model'),
+ filter: this.inviteFilter,
searchTerm: '',
totalInvites: model.invites.length
});
diff --git a/app/assets/javascripts/discourse/templates/user/invited.hbs b/app/assets/javascripts/discourse/templates/user-invited-show.hbs
similarity index 50%
rename from app/assets/javascripts/discourse/templates/user/invited.hbs
rename to app/assets/javascripts/discourse/templates/user-invited-show.hbs
index c3314fe4b..f591b52d2 100644
--- a/app/assets/javascripts/discourse/templates/user/invited.hbs
+++ b/app/assets/javascripts/discourse/templates/user-invited-show.hbs
@@ -1,32 +1,46 @@
{{#if canInviteToForum}}
-
{{i18n 'user.invited.title'}}
-
-
- {{#if canBulkInvite}}
- {{resumable-upload target="/invites/upload" success="uploadSuccess" error="uploadError" uploadText=uploadText}}
- {{/if}}
-
+ {{#if can_see_invite_details}}
+
+
+
+ {{nav-item route='userInvited.show' routeParam='redeemed' label='user.invited.redeemed_tab'}}
+ {{nav-item route='userInvited.show' routeParam='pending' label='user.invited.pending_tab'}}
+
+
+
+
+ {{d-button action="showInvite" label='user.invited.create' class='btn'}}
+ {{#if canBulkInvite}}
+ {{resumable-upload target="/invites/upload" success="uploadSuccess" error="uploadError" uploadText=uploadText}}
+ {{/if}}
+
+
+ {{/if}}
{{#if showSearch}}
-
+
+
+
{{/if}}
{{#if model.invites}}
-
+
- {{i18n 'user.invited.user'}} |
- {{i18n 'user.invited.redeemed_at'}} |
- {{#if can_see_invite_details}}
- {{i18n 'user.last_seen'}} |
- {{i18n 'user.invited.topics_entered'}} |
- {{i18n 'user.invited.posts_read_count'}} |
- {{i18n 'user.invited.time_read'}} |
- {{i18n 'user.invited.days_visited'}} |
+ {{#if inviteRedeemed}}
+ {{i18n 'user.invited.user'}} |
+ {{i18n 'user.invited.redeemed_at'}} |
+ {{#if can_see_invite_details}}
+ {{i18n 'user.last_seen'}} |
+ {{i18n 'user.invited.topics_entered'}} |
+ {{i18n 'user.invited.posts_read_count'}} |
+ {{i18n 'user.invited.time_read'}} |
+ {{i18n 'user.invited.days_visited'}} |
+ {{/if}}
+ {{else}}
+ {{i18n 'user.invited.user'}} |
{{/if}}
{{#each invite in model.invites}}
@@ -56,13 +70,13 @@
{{#if invite.rescinded}}
{{i18n 'user.invited.rescinded'}}
{{else}}
-
+ {{d-button icon="times" action="rescind" actionParam=invite class="btn" label="user.invited.rescind"}}
{{/if}}
{{#if invite.reinvited}}
{{i18n 'user.invited.reinvited'}}
{{else}}
-
+ {{d-button icon="user-plus" action="reinvite" actionParam=invite class="btn" label="user.invited.reinvite"}}
{{/if}}
{{/if}}
@@ -72,12 +86,13 @@
{{conditional-loading-spinner condition=invitesLoading}}
{{else}}
- {{#if canBulkInvite}}
- {{{i18n 'user.invited.bulk_invite.none'}}}
- {{else}}
- {{i18n 'user.invited.none'}}
- {{/if}}
+
+ {{#if canBulkInvite}}
+ {{{i18n 'user.invited.bulk_invite.none'}}}
+ {{else}}
+ {{i18n 'user.invited.none'}}
+ {{/if}}
+
{{/if}}
-
{{/if}}
diff --git a/app/assets/javascripts/discourse/templates/user/user.hbs b/app/assets/javascripts/discourse/templates/user/user.hbs
index 25a6a7167..f042fa263 100644
--- a/app/assets/javascripts/discourse/templates/user/user.hbs
+++ b/app/assets/javascripts/discourse/templates/user/user.hbs
@@ -55,7 +55,7 @@
{{#link-to 'preferences' class="btn right"}}{{fa-icon "cog"}}{{i18n 'user.preferences'}}{{/link-to}}
{{/if}}
{{#if canInviteToForum}}
- {{#link-to 'user.invited' class="btn right"}}{{fa-icon "user-plus"}}{{i18n 'user.invited.title'}}{{/link-to}}
+ {{#link-to 'userInvited' class="btn right"}}{{fa-icon "user-plus"}}{{i18n 'user.invited.title'}}{{/link-to}}
{{/if}}
diff --git a/app/assets/javascripts/discourse/views/user-invited.js.es6 b/app/assets/javascripts/discourse/views/user-invited-show.js.es6
similarity index 58%
rename from app/assets/javascripts/discourse/views/user-invited.js.es6
rename to app/assets/javascripts/discourse/views/user-invited-show.js.es6
index a94430b7c..25ae5e30d 100644
--- a/app/assets/javascripts/discourse/views/user-invited.js.es6
+++ b/app/assets/javascripts/discourse/views/user-invited-show.js.es6
@@ -2,6 +2,6 @@ import LoadMore from "discourse/mixins/load-more";
export default Ember.View.extend(LoadMore, {
classNames: ['paginated-topics-list'],
- eyelineSelector: '.paginated-topics-list .invite-list tr',
- templateName: 'user/invited'
+ eyelineSelector: '.paginated-topics-list .user-invite-list tr',
+ templateName: 'user-invited-show'
});
diff --git a/app/assets/stylesheets/desktop/user.scss b/app/assets/stylesheets/desktop/user.scss
index 425614e6a..6f83a8978 100644
--- a/app/assets/stylesheets/desktop/user.scss
+++ b/app/assets/stylesheets/desktop/user.scss
@@ -184,6 +184,26 @@
max-height: 45px;
}
}
+
+ .user-invite-list {
+ margin-top: 15px;
+ }
+
+ .user-invite-controls {
+ background-color: dark-light-diff($primary, $secondary, 90%, -75%);
+ padding: 5px 10px 0px 0;
+ height: 35px;
+ }
+
+ .user-invite-search {
+ clear: both;
+ margin: 15px 0px -15px 0px;
+ }
+
+ .user-invite-none {
+ clear: both;
+ padding: 15px;
+ }
}
.about {
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index dc5239d95..8bb3c44dc 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -168,14 +168,15 @@ class UsersController < ApplicationController
def invited
inviter = fetch_user_from_params
offset = params[:offset].to_i || 0
+ filter_by = params[:filter]
- invites = if guardian.can_see_invite_details?(inviter)
- Invite.find_all_invites_from(inviter, offset)
+ invites = if guardian.can_see_invite_details?(inviter) && filter_by == "pending"
+ Invite.find_pending_invites_from(inviter, offset)
else
Invite.find_redeemed_invites_from(inviter, offset)
end
- invites = invites.filter_by(params[:filter])
+ invites = invites.filter_by(params[:search])
render_json_dump invites: serialize_data(invites.to_a, InviteSerializer),
can_see_invite_details: guardian.can_see_invite_details?(inviter)
end
diff --git a/app/models/invite.rb b/app/models/invite.rb
index 51af25166..d4a509ab1 100644
--- a/app/models/invite.rb
+++ b/app/models/invite.rb
@@ -162,8 +162,12 @@ class Invite < ActiveRecord::Base
.references('user_stats')
end
+ def self.find_pending_invites_from(inviter, offset=0)
+ find_all_invites_from(inviter, offset).where('invites.user_id IS NULL').order('invites.created_at DESC')
+ end
+
def self.find_redeemed_invites_from(inviter, offset=0)
- find_all_invites_from(inviter, offset).where('invites.user_id IS NOT NULL')
+ find_all_invites_from(inviter, offset).where('invites.user_id IS NOT NULL').order('invites.redeemed_at DESC')
end
def self.filter_by(email_or_username)
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index f6272d679..e25a7b2b9 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -572,8 +572,10 @@ en:
none: "You haven't invited anyone here yet."
truncated: "Showing the first {{count}} invites."
redeemed: "Redeemed Invites"
+ redeemed_tab: "Redeemed"
redeemed_at: "Redeemed"
pending: "Pending Invites"
+ pending_tab: "Pending"
topics_entered: "Topics Viewed"
posts_read_count: "Posts Read"
expired: "This invite has expired."
diff --git a/config/routes.rb b/config/routes.rb
index 45fb97610..0553fd322 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -268,6 +268,7 @@ Discourse::Application.routes.draw do
get "users/:username/staff-info" => "users#staff_info", constraints: {username: USERNAME_ROUTE_FORMAT}
get "users/:username/invited" => "users#invited", constraints: {username: USERNAME_ROUTE_FORMAT}
+ get "users/:username/invited/:filter" => "users#invited", constraints: {username: USERNAME_ROUTE_FORMAT}
post "users/action/send_activation_email" => "users#send_activation_email"
get "users/:username/activity" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT}
get "users/:username/activity/:filter" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT}
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index 0103bb26f..32544497d 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -911,7 +911,7 @@ describe UsersController do
user: invitee
)
- xhr :get, :invited, username: inviter.username, filter: 'billybob'
+ xhr :get, :invited, username: inviter.username, search: 'billybob'
invites = JSON.parse(response.body)['invites']
expect(invites.size).to eq(1)
@@ -933,7 +933,7 @@ describe UsersController do
user: Fabricate(:user, username: 'jimtom')
)
- xhr :get, :invited, username: inviter.username, filter: 'billybob'
+ xhr :get, :invited, username: inviter.username, search: 'billybob'
invites = JSON.parse(response.body)['invites']
expect(invites.size).to eq(1)
@@ -946,7 +946,7 @@ describe UsersController do
inviter = Fabricate(:user)
Fabricate(:invite, invited_by: inviter)
- xhr :get, :invited, username: inviter.username
+ xhr :get, :invited, username: inviter.username, filter: 'pending'
invites = JSON.parse(response.body)['invites']
expect(invites).to be_empty
@@ -980,7 +980,7 @@ describe UsersController do
with(inviter).returns(true)
end
- xhr :get, :invited, username: inviter.username
+ xhr :get, :invited, username: inviter.username, filter: 'pending'
invites = JSON.parse(response.body)['invites']
expect(invites.size).to eq(1)
@@ -999,7 +999,7 @@ describe UsersController do
with(inviter).returns(false)
end
- xhr :get, :invited, username: inviter.username
+ xhr :get, :invited, username: inviter.username, filter: 'pending'
json = JSON.parse(response.body)['invites']
expect(json).to be_empty
diff --git a/spec/models/invite_spec.rb b/spec/models/invite_spec.rb
index 8c538b709..63ef8e3db 100644
--- a/spec/models/invite_spec.rb
+++ b/spec/models/invite_spec.rb
@@ -387,6 +387,30 @@ describe Invite do
end
end
+ describe '.find_pending_invites_from' do
+ it 'returns pending invites only' do
+ inviter = Fabricate(:user)
+ Fabricate(
+ :invite,
+ invited_by: inviter,
+ user_id: 123,
+ email: 'redeemed@example.com'
+ )
+
+ pending_invite = Fabricate(
+ :invite,
+ invited_by: inviter,
+ user_id: nil,
+ email: 'pending@example.com'
+ )
+
+ invites = Invite.find_pending_invites_from(inviter)
+
+ expect(invites.size).to eq(1)
+ expect(invites.first).to eq pending_invite
+ end
+ end
+
describe '.find_redeemed_invites_from' do
it 'returns redeemed invites only' do
inviter = Fabricate(:user)