diff --git a/app/assets/javascripts/admin/components/admin-nav-item.js.es6 b/app/assets/javascripts/admin/components/nav-item.js.es6 similarity index 100% rename from app/assets/javascripts/admin/components/admin-nav-item.js.es6 rename to app/assets/javascripts/admin/components/nav-item.js.es6 diff --git a/app/assets/javascripts/admin/templates/admin.hbs b/app/assets/javascripts/admin/templates/admin.hbs index df56d57bf..d8e88712c 100644 --- a/app/assets/javascripts/admin/templates/admin.hbs +++ b/app/assets/javascripts/admin/templates/admin.hbs @@ -4,26 +4,26 @@
diff --git a/app/assets/javascripts/admin/templates/backups.hbs b/app/assets/javascripts/admin/templates/backups.hbs index 510b96530..d274285b2 100644 --- a/app/assets/javascripts/admin/templates/backups.hbs +++ b/app/assets/javascripts/admin/templates/backups.hbs @@ -1,8 +1,8 @@
diff --git a/app/assets/javascripts/admin/templates/components/admin-nav-item.hbs b/app/assets/javascripts/admin/templates/components/nav-item.hbs similarity index 100% rename from app/assets/javascripts/admin/templates/components/admin-nav-item.hbs rename to app/assets/javascripts/admin/templates/components/nav-item.hbs diff --git a/app/assets/javascripts/admin/templates/customize.hbs b/app/assets/javascripts/admin/templates/customize.hbs index 75dcdbab9..1622539bb 100644 --- a/app/assets/javascripts/admin/templates/customize.hbs +++ b/app/assets/javascripts/admin/templates/customize.hbs @@ -1,9 +1,9 @@ {{#admin-nav}} - {{admin-nav-item route='adminCustomize.colors' label='admin.customize.colors.title'}} - {{admin-nav-item route='adminCustomize.css_html' label='admin.customize.css_html.title'}} - {{admin-nav-item route='adminSiteText' label='admin.site_text.title'}} - {{admin-nav-item route='adminUserFields' label='admin.user_fields.title'}} - {{admin-nav-item route='adminEmojis' label='admin.emoji.title'}} + {{nav-item route='adminCustomize.colors' label='admin.customize.colors.title'}} + {{nav-item route='adminCustomize.css_html' label='admin.customize.css_html.title'}} + {{nav-item route='adminSiteText' label='admin.site_text.title'}} + {{nav-item route='adminUserFields' label='admin.user_fields.title'}} + {{nav-item route='adminEmojis' label='admin.emoji.title'}} {{/admin-nav}}
diff --git a/app/assets/javascripts/admin/templates/email.hbs b/app/assets/javascripts/admin/templates/email.hbs index 0ec147207..738865377 100644 --- a/app/assets/javascripts/admin/templates/email.hbs +++ b/app/assets/javascripts/admin/templates/email.hbs @@ -1,9 +1,9 @@ {{#admin-nav}} - {{admin-nav-item route='adminEmail.index' label='admin.email.settings'}} - {{admin-nav-item route='adminEmail.all' label='admin.email.all'}} - {{admin-nav-item route='adminEmail.sent' label='admin.email.sent'}} - {{admin-nav-item route='adminEmail.skipped' label='admin.email.skipped'}} - {{admin-nav-item route='adminEmail.previewDigest' label='admin.email.preview_digest'}} + {{nav-item route='adminEmail.index' label='admin.email.settings'}} + {{nav-item route='adminEmail.all' label='admin.email.all'}} + {{nav-item route='adminEmail.sent' label='admin.email.sent'}} + {{nav-item route='adminEmail.skipped' label='admin.email.skipped'}} + {{nav-item route='adminEmail.previewDigest' label='admin.email.preview_digest'}} {{/admin-nav}}
diff --git a/app/assets/javascripts/admin/templates/flags.hbs b/app/assets/javascripts/admin/templates/flags.hbs index a1ecf4787..b19e8e227 100644 --- a/app/assets/javascripts/admin/templates/flags.hbs +++ b/app/assets/javascripts/admin/templates/flags.hbs @@ -1,6 +1,6 @@ {{#admin-nav}} - {{admin-nav-item route='adminFlags.list' routeParam='active' label='admin.flags.active'}} - {{admin-nav-item route='adminFlags.list' routeParam='old' label='admin.flags.old'}} + {{nav-item route='adminFlags.list' routeParam='active' label='admin.flags.active'}} + {{nav-item route='adminFlags.list' routeParam='old' label='admin.flags.old'}} {{/admin-nav}}
diff --git a/app/assets/javascripts/admin/templates/groups.hbs b/app/assets/javascripts/admin/templates/groups.hbs index 0de3fc5c7..2d767c384 100644 --- a/app/assets/javascripts/admin/templates/groups.hbs +++ b/app/assets/javascripts/admin/templates/groups.hbs @@ -1,6 +1,6 @@ {{#admin-nav}} - {{admin-nav-item route='adminGroupsType' routeParam='custom' label='admin.groups.custom'}} - {{admin-nav-item route='adminGroupsType' routeParam='automatic' label='admin.groups.automatic'}} + {{nav-item route='adminGroupsType' routeParam='custom' label='admin.groups.custom'}} + {{nav-item route='adminGroupsType' routeParam='automatic' label='admin.groups.automatic'}} {{/admin-nav}}
diff --git a/app/assets/javascripts/admin/templates/logs.hbs b/app/assets/javascripts/admin/templates/logs.hbs index 1238e6b5d..5c412949f 100644 --- a/app/assets/javascripts/admin/templates/logs.hbs +++ b/app/assets/javascripts/admin/templates/logs.hbs @@ -1,10 +1,10 @@ {{#admin-nav}} - {{admin-nav-item route='adminLogs.staffActionLogs' label='admin.logs.staff_actions.title'}} - {{admin-nav-item route='adminLogs.screenedEmails' label='admin.logs.screened_emails.title'}} - {{admin-nav-item route='adminLogs.screenedIpAddresses' label='admin.logs.screened_ips.title'}} - {{admin-nav-item route='adminLogs.screenedUrls' label='admin.logs.screened_urls.title'}} + {{nav-item route='adminLogs.staffActionLogs' label='admin.logs.staff_actions.title'}} + {{nav-item route='adminLogs.screenedEmails' label='admin.logs.screened_emails.title'}} + {{nav-item route='adminLogs.screenedIpAddresses' label='admin.logs.screened_ips.title'}} + {{nav-item route='adminLogs.screenedUrls' label='admin.logs.screened_urls.title'}} {{#if currentUser.admin}} - {{admin-nav-item path='/logs' label='admin.logs.logster.title'}} + {{nav-item path='/logs' label='admin.logs.logster.title'}} {{/if}} {{/admin-nav}} diff --git a/app/assets/javascripts/admin/templates/plugins.hbs b/app/assets/javascripts/admin/templates/plugins.hbs index 7150e14cd..96ed1fc7d 100644 --- a/app/assets/javascripts/admin/templates/plugins.hbs +++ b/app/assets/javascripts/admin/templates/plugins.hbs @@ -1,9 +1,9 @@
diff --git a/app/assets/javascripts/admin/templates/users_list.hbs b/app/assets/javascripts/admin/templates/users_list.hbs index 2c4d8d4eb..d37a9ae26 100644 --- a/app/assets/javascripts/admin/templates/users_list.hbs +++ b/app/assets/javascripts/admin/templates/users_list.hbs @@ -1,15 +1,15 @@
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}} +
+
+ +
+ +
+ {{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}} -
- {{text-field value=searchTerm placeholderKey="user.invited.search"}} -
+ {{/if}} {{#if model.invites}} - +
- - - {{#if can_see_invite_details}} - - - - - + {{#if inviteRedeemed}} + + + {{#if can_see_invite_details}} + + + + + + {{/if}} + {{else}} + {{/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)
    {{i18n 'user.invited.user'}}{{i18n 'user.invited.redeemed_at'}}{{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'}}{{i18n 'user.invited.user'}}{{i18n 'user.invited.redeemed_at'}}{{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'}}{{i18n 'user.invited.user'}}