diff --git a/package.json b/package.json index 48503271e0..5fe7519805 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "paypal-ipn": "~1.0.1", "paypal-recurring": "git://github.com/jaybryant/paypal-recurring#656b496f43440893c984700191666a5c5c535dca", "pretty-data": "git://github.com/vkiryukhin/pretty-data#master", + "qs": "^2.3.2", "request": "~2.44.0", "s3-upload-stream": "^1.0.6", "stripe": "*", diff --git a/public/js/controllers/groupsCtrl.js b/public/js/controllers/groupsCtrl.js index 335de42aa4..1b3ef7fba2 100644 --- a/public/js/controllers/groupsCtrl.js +++ b/public/js/controllers/groupsCtrl.js @@ -1,7 +1,7 @@ "use strict"; -habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '$http', '$q', 'User', 'Members', '$state', - function($scope, $rootScope, Shared, Groups, $http, $q, User, Members, $state) { +habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '$http', '$q', 'User', 'Members', '$state', 'Notification', + function($scope, $rootScope, Shared, Groups, $http, $q, User, Members, $state, Notification) { $scope.isMemberOfPendingQuest = function(userid, group) { if (!group.quest || !group.quest.members) return false; if (group.quest.active) return false; // quest is started, not pending @@ -63,7 +63,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' } } - // ------ Invites ------ + // ------ Invites ------ $scope.invite = function(group){ Groups.Group.invite({gid: group._id, uuid: group.invitee}, undefined, function(){ @@ -72,6 +72,18 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' group.invitee = ''; }); } + + $scope.emails = [{name:"",email:""},{name:"",email:""}]; + //$scope.inviteLink = function(obj){ + // return window.env.BASE_URL + '?partyInvite=' + encodeURIComponent(JSON.stringify(obj)); + //} + $scope.inviteEmails = function(emails){ + $http.post('/api/v2/user/social/invite-friends', emails).success(function(){ + Notification.text("Invitations sent!"); + $scope.emails = [{name:'',email:''},{name:'',email:''}]; + }); + } + } ]) diff --git a/src/controllers/groups.js b/src/controllers/groups.js index cd7c768fad..9f789f20dd 100644 --- a/src/controllers/groups.js +++ b/src/controllers/groups.js @@ -300,7 +300,8 @@ api.join = function(req, res, next) { group = res.locals.group; if (group.type == 'party' && group._id == (user.invitations && user.invitations.party && user.invitations.party.id)) { - user.invitations.party = undefined; + User.update({_id:user.invitations.party.inviter}, {$inc:{'items.quests.basilist':1}}).exec(); // Reward inviter + user.invitations.party = undefined; // Clear invite user.save(); // invite new user to pending quest if (group.quest.key && !group.quest.active) { @@ -439,10 +440,10 @@ api.invite = function(req, res, next) { function sendInvite (){ if(group.type === 'guild'){ - invite.invitations.guilds.push({id: group._id, name: group.name}); + invite.invitations.guilds.push({id: group._id, name: group.name, inviter:res.locals.user._id}); }else{ //req.body.type in 'guild', 'party' - invite.invitations.party = {id: group._id, name: group.name} + invite.invitations.party = {id: group._id, name: group.name, inviter:res.locals.user._id}; } group.invites.push(invite._id); diff --git a/src/controllers/user.js b/src/controllers/user.js index e2e7ec6b38..6fd278ef39 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -7,13 +7,15 @@ var nconf = require('nconf'); var async = require('async'); var shared = require('habitrpg-shared'); var User = require('./../models/user').model; -var ga = require('./../utils').ga; +var utils = require('./../utils'); +var ga = utils.ga; var Group = require('./../models/group').model; var Challenge = require('./../models/challenge').model; var moment = require('moment'); var logging = require('./../logging'); var acceptablePUTPaths; var api = module.exports; +var qs = require('qs'); // api.purchase // Shared.ops @@ -393,6 +395,40 @@ api.cast = function(req, res, next) { } } +/** + * POST /user/invite-friends + */ +api.inviteFriends = function(req, res, next) { + Group.findOne({type:'party', members:{'$in': [res.locals.user._id]}}).select('_id name').exec(function(err,party){ + if (err) return next(err); + var link = nconf.get('BASE_URL') + '?' + qs.stringify({partyInvite:{id:party._id, inviter:res.locals.user._id, name:party.name}}); + _.each(req.body, function(invite){ + if (invite.email) { + var variables = [ + {name: 'LINK', content: link}, + {name: 'INVITER', content: res.locals.user.profile.name}, + {name: 'INVITEE', content: invite.name} + ]; + // TODO implement "users can only be invited once" + utils.txnEmail(invite, 'invite-friend', variables); + } + }); + res.send(200); + }) +} +api.sessionPartyInvite = function(req,res,next){ + if (req.session.partyInvite) { + var inv = res.locals.user.invitations; + if (!(inv.party && inv.party.id)) { + inv.party = req.session.partyInvite; + Group.update({_id:req.session.partyInvite.id},{$addToSet:{invites:res.locals.user._id}}); + delete req.session.partyInvite; + return res.locals.user.save(next); + } + } + next(); +} + /** * All other user.ops which can easily be mapped to habitrpg-shared/index.coffee, not requiring custom API-wrapping */ diff --git a/src/middleware.js b/src/middleware.js index 4f4bece349..d7a6902ead 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -12,6 +12,7 @@ var shared = require('habitrpg-shared'); var request = require('request'); var os = require('os'); var moment = require('moment'); +var qs = require('qs'); module.exports.apiThrottle = function(app) { if (nconf.get('NODE_ENV') !== 'production') return; @@ -196,7 +197,12 @@ module.exports.locals = function(req, res, next) { tavern: tavern, // for world boss worldDmg: (tavern && tavern.quest && tavern.quest.extra && tavern.quest.extra.worldDmg) || {} - } + }; + + // Put query-string party invitations into session to be handled later + var partyInvite = qs.parse(req.query.partyInvite); + if (partyInvite && partyInvite.id) + req.session.partyInvite = partyInvite; next(); } diff --git a/src/routes/apiv2.coffee b/src/routes/apiv2.coffee index edacbdc243..d2915fe246 100644 --- a/src/routes/apiv2.coffee +++ b/src/routes/apiv2.coffee @@ -320,7 +320,7 @@ module.exports = (swagger, v2) -> parameters:[ body '','The array of batch-operations to perform','object' ] - middleware: [middleware.forceRefresh, auth.auth, i18n.getUserLanguage, cron] + middleware: [middleware.forceRefresh, auth.auth, i18n.getUserLanguage, cron, user.sessionPartyInvite] action: user.batchUpdate # Tags @@ -364,6 +364,15 @@ module.exports = (swagger, v2) -> ] action: user.deleteTag + "/user/social/invite-friends": + spec: + method: 'POST' + description: 'Invite friends via email' + parameters: [ + body 'invites','Array of [{name:"Friend\'s Name", email:"friends@email.com"}] to invite to play in your party','object' + ] + action: user.inviteFriends + # --------------------------------- # Groups # --------------------------------- diff --git a/views/options/settings.jade b/views/options/settings.jade index f8f186c1fd..28e5863e0b 100644 --- a/views/options/settings.jade +++ b/views/options/settings.jade @@ -63,6 +63,7 @@ script(type='text/ng-template', id='partials/options.settings.settings.html') button.btn.btn-default(ng-click='showTour()', popover-placement='right', popover-trigger='mouseenter', popover=env.t('restartTour'))= env.t('showTour') button.btn.btn-default(ng-click='showBailey()', popover-trigger='mouseenter', popover-placement='right', popover=env.t('showBaileyPop'))= env.t('showBailey') button.btn.btn-default(ng-click='openRestoreModal()', popover-trigger='mouseenter', popover-placement='right', popover=env.t('fixValPop'))= env.t('fixVal') + button.btn.btn-default(ng-click="openModal('invite-friends', {controller:'GroupsCtrl'})") Invite Friends button.btn.btn-default(ng-if='user.preferences.disableClasses==true', ng-click='user.ops.changeClass({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('enableClassPop'))= env.t('enableClass') button.btn.btn-default(ng-if='!user.preferences.disableClasses && user.flags.classSelected', ng-click='showClassesTour()', popover-trigger='mouseenter', popover-placement='right', popover=env.t('classTourPop'))= env.t('showClass') diff --git a/views/options/social/group.jade b/views/options/social/group.jade index df056b816b..00ed6a49f5 100644 --- a/views/options/social/group.jade +++ b/views/options/social/group.jade @@ -48,9 +48,11 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter // ------ Members ------- .panel.panel-default .panel-heading - h3.panel-title=env.t('members') + h3.panel-title + =env.t('members') + button.pull-right.btn.btn-primary(ng-click="openModal('invite-friends', {controller:'GroupsCtrl'})", ng-if='::group.type=="party"') Invite Friends .panel-body.modal-fixed-height - div(ng-if='group.type=="party"') + div(ng-if='::group.type=="party"') =env.t('partyList') br select#partyOrder( @@ -86,7 +88,7 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter a.media-body span(ng-click='clickMember(invite._id, true)') | {{invite.profile.name}} - .panel-footer + .panel-footer(ng-if='::group.type!="party"') form.form-inline(ng-submit='invite(group)') //.alert.alert-danger(ng-show='_groupError') {{_groupError}} .form-group diff --git a/views/shared/modals/index.jade b/views/shared/modals/index.jade index 8d5b74e8ab..f8f5433996 100644 --- a/views/shared/modals/index.jade +++ b/views/shared/modals/index.jade @@ -10,3 +10,4 @@ include ./classes include ./quests include ./rebirth include ./limited +include ./invite-friends diff --git a/views/shared/modals/invite-friends.jade b/views/shared/modals/invite-friends.jade new file mode 100644 index 0000000000..87b7e36215 --- /dev/null +++ b/views/shared/modals/invite-friends.jade @@ -0,0 +1,44 @@ +script(type='text/ng-template', id='modals/invite-friends.html') + .modal-header + h4 Invite Friends + .modal-body + p.alert.alert-info Invite friends by User ID here. + + form.form-inline(ng-submit='invite(party)') + //-.alert.alert-danger(ng-show='_groupError') {{_groupError}} + .form-group + input.form-control(type='text', placeholder=env.t('userId'), ng-model='party.invitee') + |  + button.btn.btn-primary(type='submit') Invite Existing User + + hr + + p.alert.alert-info Invite friends by email. If they join via your email, they'll automatically be invited to your party. + + table.table.table-striped + thead + tr + th Name + th Email + tbody + tr(ng-repeat='email in emails') + td + input.form-control(type='text', ng-model='email.name') + td + input.form-control(type='email', ng-model='email.email') + tr + td(colspan=2) + button.btn.btn-xs.pull-right(ng-click='emails = emails.concat([{name:"",email:""}])') + i.glyphicon.glyphicon-plus + tr + td(colspan=2) + button.btn.btn-primary.pull-right(ng-click='inviteEmails(emails)') Invite New User(s) + + //- + hr + p.alert.alert-info Or share this link (copy/paste): + input.form-control(type='text', ng-value='inviteLink({id: party._id, inviter: user._id, name: party.name})') + + .modal-footer + button.btn.btn-default(ng-click='$close()') Close +