diff --git a/public/css/helpers.styl b/public/css/helpers.styl index e827803593..52d245a3fb 100644 --- a/public/css/helpers.styl +++ b/public/css/helpers.styl @@ -29,4 +29,7 @@ // Hide visually and from screenreaders, but maintain layout .invisible - visibility: hidden \ No newline at end of file + visibility: hidden + +.transparent + opacity: 0.5 \ No newline at end of file diff --git a/public/js/controllers/groupsCtrl.js b/public/js/controllers/groupsCtrl.js index 69c09aa938..e3ee2be624 100644 --- a/public/js/controllers/groupsCtrl.js +++ b/public/js/controllers/groupsCtrl.js @@ -2,6 +2,11 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '$http', '$q', 'User', 'Members', '$state', 'Notification', function($scope, $rootScope, Shared, Groups, $http, $q, User, Members, $state, Notification) { + Members.selectMember('9', function(){ + $rootScope.openModal('send-gift',{controller:'MemberModalCtrl'}) + }); + + $scope.isMemberOfPendingQuest = function(userid, group) { if (!group.quest || !group.quest.members) return false; if (group.quest.active) return false; // quest is started, not pending @@ -125,6 +130,18 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' $scope.$close(); }); } + $scope.gift = { + type: 'gems', + gems: {amount:0, fromBalance:true}, + subscription: {}, + message:'' + }; + $scope.sendGift = function(uuid, gift){ + $http.post('/api/v2/members/'+uuid+'/gift', gift).success(function(){ + Notification.text('Gift sent!') + $rootScope.User.sync(); + }) + } } ]) diff --git a/src/controllers/members.js b/src/controllers/members.js index d87f561827..f839d0fbce 100644 --- a/src/controllers/members.js +++ b/src/controllers/members.js @@ -29,33 +29,67 @@ api.getMember = function(req, res, next) { }) } -api.sendPrivateMessage = function(req,res,next){ +var sendMessage = function(user, member, msg){ + var message = groups.chatDefaults(msg, user); + shared.refPush(member.inbox.messages, message); + member.inbox.newMessages++; + member._v++; + member.markModified('inbox.messages'); + + var message = groups.chatDefaults(msg, member); + shared.refPush(user.inbox.messages, _.defaults({sent:true},message)); + user.markModified('inbox.messages'); +} + +api.sendPrivateMessage = function(req, res, next){ async.waterfall([ fetchMember(req.params.uuid), - function(member, cb){ + function(member, cb) { if (~member.inbox.blocks.indexOf(res.locals.user._id) // can't send message if that user blocked me || ~res.locals.user.inbox.blocks.indexOf(member._id) // or if I blocked them || member.inbox.optOut) { // or if they've opted out of messaging - return cb({code:401, err: "Can't send message to this user."}); + return cb({code: 401, err: "Can't send message to this user."}); } - - var message = groups.chatDefaults(req.body.message, res.locals.user); - shared.refPush(member.inbox.messages, message); - member.inbox.newMessages++; - member._v++; - member.markModified('inbox.messages'); - - var message = groups.chatDefaults(req.body.message, member); - shared.refPush(res.locals.user.inbox.messages, _.defaults({sent:true},message)); - res.locals.user.markModified('inbox.messages'); - - member.save(cb); - }, - function(a,b,cb){ - res.locals.user.save(cb); + sendMessage(res.locals.user, member, req.body.message); + async.parallel([ + function (cb2) { member.save(cb2) }, + function (cb2) { res.locals.user.save(cb2) } + ], cb); } ], function(err){ if (err) return sendErr(err, res, next); res.send(200); }) +} + +api.sendGift = function(req, res, next){ + async.waterfall([ + fetchMember(req.params.uuid), + function(member, cb) { + // Gems + switch (req.body.type) { + case "gems": + var amt = req.body.gems.amount / 4, + user = res.locals.user; + if (amt < 1 || user.balance < amt) + return cb({code: 401, err: "Amount must be within 0 and your current number of gems."}); + member.balance += amt; + user.balance -= amt; + var msg = "`Hello "+member.profile.name+", "+user.profile.name+" has sent you "+amt+" gems!` "; + if (req.body.message) msg += req.body.message; + sendMessage(user, member, msg); + return async.parallel([ + function (cb2) { member.save(cb2) }, + function (cb2) { res.locals.user.save(cb2) } + ], cb); + case "subscription": + return cb(); + default: + return cb({code:400, err:"Body must contain a gems:{amount,fromBalance} or subscription:{months} object"}); + } + } + ], function(err) { + if (err) return sendErr(err, res, next); + res.send(200); + }); } \ No newline at end of file diff --git a/src/routes/apiv2.coffee b/src/routes/apiv2.coffee index 00191a52b1..415577d565 100644 --- a/src/routes/apiv2.coffee +++ b/src/routes/apiv2.coffee @@ -602,6 +602,16 @@ module.exports = (swagger, v2) -> ] middleware: [auth.auth] action: user.blockUser + "/members/{uuid}/gift": + spec: + method: 'POST' + description: 'Send a gift to a member' + parameters: [ + path 'uuid', 'The UUID of the member', 'string' + body '', '{gems:{amount:Number, fromBalance:Boolean}, subscription:{months:Number}}', 'object' + ] + middleware: [auth.auth] + action: members.sendGift # --------------------------------- # Hall of Heroes / Patrons diff --git a/views/shared/modals/buy-gems.jade b/views/shared/modals/buy-gems.jade index c907973f7b..c6ade26a00 100644 --- a/views/shared/modals/buy-gems.jade +++ b/views/shared/modals/buy-gems.jade @@ -20,7 +20,6 @@ script(id='modals/buyGems.html', type='text/ng-template') td p small.muted=env.t('paymentMethods') - //.btn.btn-primary(ng-click='showStripe()')=env.t('payWithCard') .btn.btn-primary(ng-click='showStripe()')=env.t('card') a.btn.btn-warning(href='/paypal/checkout?_id={{user._id}}&apiToken={{user.apiToken}}') PayPal diff --git a/views/shared/modals/members.jade b/views/shared/modals/members.jade index 2925d4617d..e94596ffa1 100644 --- a/views/shared/modals/members.jade +++ b/views/shared/modals/members.jade @@ -38,6 +38,8 @@ script(type='text/ng-template', id='modals/member.html') span.glyphicon.glyphicon-ban-circle button.btn.btn-md.btn-default(tooltip=env.t('sendPM'), ng-click="openModal('private-message',{controller:'MemberModalCtrl'})", tooltip-placement='right') span.glyphicon.glyphicon-envelope + button.btn.btn-md.btn-default(tooltip="Send Gift", ng-click="openModal('send-gift',{controller:'MemberModalCtrl'})", tooltip-placement='right') + span.glyphicon.glyphicon-gift button.btn.btn-default(ng-click='$close()')=env.t('close') script(type='text/ng-template', id='modals/private-message.html') @@ -48,3 +50,52 @@ script(type='text/ng-template', id='modals/private-message.html') .modal-footer button.btn.btn-primary(ng-click='sendPrivateMessage(profile._id, _message)')=env.t("send") button.btn.btn-default(ng-click='$close()')=env.t('cancel') + +script(type='text/ng-template', id='modals/send-gift.html') + .modal-header + h4 Send Gift to {{::profile.profile.name}} + .modal-body + .panel.panel-default(class="{{gift.type=='gems' ? 'panel-primary' : 'transparent'}}", ng-click='gift.type="gems"') + .panel-heading + .pull-right + span(ng-show='gift.gems.fromBalance') From {{user.balance*4}} Gems + span(ng-hide='gift.gems.fromBalance') Total: ${{gift.gems.amount/4}} USD + | Gems + .panel-body + .row + .col-md-6 + .form-group + input.form-control(type='number', placeholder='Number of Gems', min='0', max='{{gift.gems.fromBalance ? user.balance*4 : ""}}', ng-model='gift.gems.amount') + .col-md-6 + .btn-group + a.btn.btn-default(ng-class="{active:gift.gems.fromBalance}", ng-click="gift.gems.fromBalance=true") From Balance + a.btn.btn-default(ng-class="{active:!gift.gems.fromBalance}", ng-click="gift.gems.fromBalance=false") Purchase + + .panel.panel-default(class="{{gift.type=='subscription' ? 'panel-primary' : 'transparent'}}", ng-click='gift.type="subscription"') + .panel-heading Subscription + .panel-body + .form-group + .radio + label + input(type="radio", name="subRadio", value="1", ng-model='gift.subscription.months') + | 1 Month ($5) + .radio + label + input(type="radio", name="subRadio", value="3", ng-model='gift.subscription.months') + | 3 Months ($15) + .radio + label + input(type="radio", name="subRadio", value="6", ng-model='gift.subscription.months') + | 6 Months ($30) + .radio + label + input(type="radio", name="subRadio", value="12", ng-model='gift.subscription.months') + | 1 Year ($48) + textarea.form-control(rows='3', ng-model='gift.message', placeholder='Personal message (optional)') + + .modal-footer + - var fromBal = "gift.type=='gems' && gift.gems.fromBalance" + button.btn.btn-primary(ng-show=fromBal, ng-click='sendGift(profile._id, gift)')=env.t("send") + a.btn.btn-primary(ng-hide=fromBal, ng-click='showStripe({uuid:profile._id})')=env.t('card') + a.btn.btn-warning(ng-hide=fromBal, href='/paypal/checkout?uuid={{profile._id}}') PayPal + button.btn.btn-default(ng-click='$close()')=env.t('cancel') \ No newline at end of file