pyc-website
main website for pyc inc.
git clone https://9o.is/git/pyc-website.git
commit 484b95a47ce5ac6b32a83aa487c03a65afe02144 parent 0b8d127557611af42f46cbff48cba2e185aa0462 Author: Jul <jul@9o.is> Date: Sun, 6 Jul 2014 23:52:05 -0400 admin page to accept or reject id verification requests Diffstat:
| M | src/main/scala/inc/pyc/config/Site.scala | | | 38 | +++++++++++++++++++++++++------------- |
| A | src/main/scala/inc/pyc/snippet/AdminSnip.scala | | | 89 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | src/main/webapp/app/App.js | | | 54 | +++++++++++++++++++++++++++++++++++++++++++++++------- |
| M | src/main/webapp/app/controllers/Forms.js | | | 11 | ++++++++--- |
| A | src/main/webapp/app/directives/ngReallyClick.js | | | 19 | +++++++++++++++++++ |
| A | src/main/webapp/settings/admin/verify.html | | | 45 | +++++++++++++++++++++++++++++++++++++++++++++ |
| M | src/main/webapp/settings/purchase_limit.html | | | 4 | ++-- |
7 files changed, 235 insertions(+), 25 deletions(-)
diff --git a/src/main/scala/inc/pyc/config/Site.scala b/src/main/scala/inc/pyc/config/Site.scala @@ -39,7 +39,7 @@ object Site extends Locs { val domain = "pycbitcoin.com" - // locations (for top group) + // Regular links val home = MenuLoc(Menu.i("Bitcoin ATM Services") / "index" >> TopBarGroup) val locations = MenuLoc(Menu.i("ATM Locations") / "locations" >> TopBarGroup >> SiteMapGroup) val whatsBitcoin = MenuLoc(Menu.i("What Is Bitcoin") / "what-is-bitcoin" >> SiteMapGroup) @@ -47,27 +47,38 @@ object Site extends Locs { val blog = MenuLoc(Menu.i("Blog") / "blog" >> TopBarGroup >> RedirectBlog >> SiteMapGroup) val faqs = MenuLoc(Menu.i("FAQs") / "faqs" >> TopBarGroup >> SiteMapGroup) val atmHowTo = MenuLoc(Menu.i("How To Use ATM") / "how-to-use-atm" >> SiteMapGroup) - + + + // Regular Links for Users + val register = MenuLoc(Menu.i("Register") / "register" >> RequireNotLoggedIn >> SiteMapGroup) + val login = MenuLoc(Menu.i("Login") / "login" >> RequireNotLoggedIn >> SiteMapGroup) + val forgotPassword = MenuLoc(Menu.i("Forgot Password") / "forgot-password" >> RequireNotLoggedIn) + + + // requires token links val loginToken = MenuLoc(buildLoginTokenMenu) val logout = MenuLoc(buildLogoutMenu) + val emailResetToken = MenuLoc(buildEmailResetTokenMenu) + + + // Settings Links + val settings = MenuLoc(Menu.i("Settings") / "settings" >> SettingsGroup >> RequireLoggedIn) + val password = MenuLoc(Menu.i("Password") / "settings" / "password" >> SettingsGroup >> RequireLoggedIn) + val purchaseLimit = MenuLoc(Menu.i("Purchase Limit") / "settings" / "purchase_limit" >> SettingsGroup >> RequireLoggedIn) + + + // Settings for Admins + val verifyID = MenuLoc(Menu.i("Verify ID") / "settings" / "admin" / "verify" >> SettingsGroup >> HasRole("admin")) + + + // Profile pages private val profileParamMenu = Menu.param[User]("User", "Profile", User.findByUsername _, _.username.get ) / "user" >> Loc.CalcValue(() => User.currentUser) lazy val profileLoc = profileParamMenu.toLoc - - val settings = MenuLoc(Menu.i("Settings") / "settings" >> SettingsGroup >> RequireLoggedIn) - val password = MenuLoc(Menu.i("Password") / "settings" / "password" >> SettingsGroup >> RequireLoggedIn) - val emailResetToken = MenuLoc(buildEmailResetTokenMenu) - - val purchaseLimit = MenuLoc(Menu.i("Purchase Limit") / "settings" / "purchase_limit" >> SettingsGroup >> RequireLoggedIn) - val account = MenuLoc(Menu.i("Account") / "settings" / "account" >> SettingsGroup >> RequireLoggedIn) - val editProfile = MenuLoc(Menu("EditProfile", "Profile") / "settings" / "profile" >> SettingsGroup >> RequireLoggedIn) - val register = MenuLoc(Menu.i("Register") / "register" >> RequireNotLoggedIn >> SiteMapGroup) - val login = MenuLoc(Menu.i("Login") / "login" >> RequireNotLoggedIn >> SiteMapGroup) - val forgotPassword = MenuLoc(Menu.i("Forgot Password") / "forgot-password" >> RequireNotLoggedIn) private def menus = List( home.menu, @@ -85,6 +96,7 @@ object Site extends Locs { settings.menu, password.menu, purchaseLimit.menu, + verifyID.menu, emailResetToken.menu, Menu.i("Error") / "error" >> Hidden, Menu.i("404") / "404" >> Hidden, diff --git a/src/main/scala/inc/pyc/snippet/AdminSnip.scala b/src/main/scala/inc/pyc/snippet/AdminSnip.scala @@ -0,0 +1,88 @@ +package inc.pyc +package snippet + +import xml._ +import lib._ +import model._ +import model.field.USAPurchaseLimit._ +import config.Site +import net.liftweb._ +import http._ +import common._ +import json._ +import JsonDSL._ +import com.foursquare._ +import rogue.LiftRogue._ +import net.liftmodules.mongoauth.MongoAuth + +trait AdminSnip extends AngularSnippet + +class VerifyId extends AdminSnip { + + def roundTrips: List[RoundTripInfo] = List( + "submit" -> submit _, + "init" -> init _) + + /** Pending Users */ + val pending = User.or( + _.where(_.purchaseLimit eqs Unlimited_Pending), + _.where(_.purchaseLimit eqs D3000_Pending)). + fetch + + + def submit(model: JValue): JValue = + for { + JBool(accepted) <- model \ "accepted" + JString(userId) <- model \ "userId" + user <- pending.filter(_.userIdAsString == userId) + } yield { + import net.liftweb.util.Mailer._ + + val fname = user.fname.get + val limit = user.purchaseLimit.get + val title = "Identity Verification" + + val html: Box[NodeSeq] = + + if(accepted) { + + if(limit == D3000_Pending) user.purchaseLimit(D3000) + else user.purchaseLimit(Unlimited) + user.update + + val subtitle = s"Congratulations $fname, you've been verified!" + val body = subtitle + " Your new bitcoin buy limit is " + user.purchaseLimit.get + HtmlEmail.simpleMessage(title, subtitle, body) + + } else { + + if(limit == D3000_Pending) user.purchaseLimit(D1000) + else user.purchaseLimit(D3000) + user.update + + val JString(reason) = model \ "reason" + val subtitle = s"Sorry $fname, your verification failed!" + val body = s"$subtitle $reason -- Your bitcoin buy limit remains at ${user.purchaseLimit.get}" + HtmlEmail.simpleMessage(title, subtitle, body) + } + + html map (sendMail( + From(MongoAuth.systemFancyEmail), + Subject("%s: %s".format(MongoAuth.siteName.vend, title)), + To(user.fancyEmail), + _ + )) + + NgAlert.success("Updated") + } + + def init(model: JValue): JValue = + ("users" -> JArray(pending.map(user => + ("id" -> user.userIdAsString) ~ + ("email" -> user.email.get) ~ + ("fname" -> user.fname.get) ~ + ("lname" -> user.lname.get) ~ + ("username" -> user.username.get) ~ + ("status" -> user.purchaseLimit.get.toString) + ))) +} +\ No newline at end of file diff --git a/src/main/webapp/app/App.js b/src/main/webapp/app/App.js @@ -1,7 +1,7 @@ var app = angular.module("app", [ 'google-maps', 'ui.bootstrap', 'ui.router', 'ui.mask', 'angularFileUpload', 'angular-google-analytics', 'ngThumb', - 'match', 'disabler', 'ngAlert', 'Forms', 'mgo-angular-wizard']); + 'match', 'disabler', 'ngAlert', 'Forms', 'mgo-angular-wizard', 'ngReallyClick']); var ZIP_CODE_REGEXP = /^(\d{5}(-\d{4})?|[A-Z]\d[A-Z] *\d[A-Z]\d)$/; var PASSWORD_REGEXP = /^(?=.*[^a-zA-Z])\S{8,}$/; @@ -185,10 +185,7 @@ app.controller('PasswordChangeCtrl', ['$scope', '$controller', '$rootScope', fun }]); -app.controller('PurchaseLimitCtrl', ['$scope', '$controller', function($scope, $controller) { - $controller('LoadedFormCtrl', {$scope: $scope}); - //$scope.init('PurchaseLimitCtrl', 'init'); - +app.controller('PurchaseLimitCtrl', ['$scope', function($scope) { $scope.thirdPartyVerified = false; }]); @@ -346,6 +343,49 @@ app.controller('UserSettingsEmailCtrl', ['$scope', '$controller', '$rootScope', }; }]); + +app.controller('VerifyIdCtrl', ['$scope', '$controller', '$rootScope', function($scope, $controller, $rootScope) { + $controller('LoadedFormCtrl', {$scope: $scope, $controller: $controller}); + + $scope.init('VerifyId', 'init', function() { + $scope.currentUser = $scope.model.users[0]; + $scope.response = {}; + }); + + + $scope.accept = function() { + $scope.response.accepted = true; + $scope.save(); + }; + + $scope.reject = function() { + $scope.response.accepted = false; + $scope.save(); + }; + + $scope.setCurrentUser = function(user, index) { + $scope.currentUser = angular.copy(user); + $scope.currentUser.index = index; + }; + + $scope.save = function() { + var success = function(alert) { + $scope.model.users.splice($scope.currentUser.index,1); + $scope.currentUser = {}; + $scope.response = {}; + $rootScope.$broadcast('alertDialog', alert); + }; + + var failure = function(alert) { + $rootScope.$broadcast('alertDialog', alert); + }; + + $scope.response.userId = $scope.currentUser.id; + $scope.submit('VerifyId', 'submit', success, failure, $scope.response); + }; + +}]); + app.controller('GMapCtrl', ['$scope', function($scope) { $scope.lat = 40.778202; $scope.long = -74.122381; @@ -358,5 +398,4 @@ app.controller('GMapCtrl', ['$scope', function($scope) { }, zoom: $scope.zoom }; -}]); - +}]); +\ No newline at end of file diff --git a/src/main/webapp/app/controllers/Forms.js b/src/main/webapp/app/controllers/Forms.js @@ -36,9 +36,10 @@ angular.module("Forms", ['ngAlert']) /** * Sends data to server. (Useful for Liftweb's roundtrip with ngAlert at least) */ - $scope.submit = function(className, funcName, successFunc, failureFunc) { + $scope.submit = function(className, funcName, successFunc, failureFunc, model) { $scope.loading = true; - window[className][funcName]($scope.model).then(function(alert) { + var toSend = ((model) ? model : $scope.model); + window[className][funcName](toSend).then(function(alert) { $scope.$apply(function() { $scope.loading = false; if(alert.msg_type === "success") { @@ -85,11 +86,15 @@ angular.module("Forms", ['ngAlert']) /** * Initiates the form with existing data from the server. */ - $scope.init = function(className, funcName) { + $scope.init = function(className, funcName, callback) { window[className][funcName]().then(function(data) { $scope.$apply(function() { $scope.model = angular.copy(data); $scope.master = angular.copy(data); + + if(typeof(callback) === "function") { + callback(); + } }); }); }; diff --git a/src/main/webapp/app/directives/ngReallyClick.js b/src/main/webapp/app/directives/ngReallyClick.js @@ -0,0 +1,18 @@ +/** + * A generic confirmation for risky actions. Usage: Add attributes: + * ng-really-message="Are you sure"? ng-really-click="takeAction()" function + */ +angular.module('ngReallyClick', []). + directive('ngReallyClick', [ function() { + return { + restrict : 'A', + link : function(scope, element, attrs) { + element.bind('click', function() { + var message = attrs.ngReallyMessage; + if (message && window.confirm(message)) { + scope.$apply(attrs.ngReallyClick); + } + }); + } + }; +} ]); +\ No newline at end of file diff --git a/src/main/webapp/settings/admin/verify.html b/src/main/webapp/settings/admin/verify.html @@ -0,0 +1,44 @@ +<div data-lift="surround?with=settings-wrap&at=content"> + <div data-lift="VerifyId" ng-controller="VerifyIdCtrl" class="row margin-top-10" ng-cloak> + <div class="col-xs-12 col-sm-5"> + <div class="well"> + <ul class="list-unstyled"> + <li ng-repeat="user in model.users"> + <a ng-click="setCurrentUser(user, $index)" href="#"> + {{user.fname}} {{user.lname}} + </a> + </li> + </ul> + </div> + </div> + <div class="col-xs-12 col-sm-7"> + <h2>{{currentUser.fname}} {{currentUser.lname}}</h2> + <h5>{{currentUser.status}}</h5> + + <table class="table"> + <tbody> + <tr> + <td>Id</td> + <td>{{currentUser.id}}</td> + </tr> + <tr> + <td>Email</td> + <td>{{currentUser.email}}</td> + </tr> + <tr> + <td>Username</td> + <td>{{currentUser.username}}</td> + </tr> + </tbody> + </table> + + <div ng-show="currentUser != null" class="padding-top-20"> + <button ng-really-click="accept()" ng-really-message="ACCEPT: Are you sure?" type="button" class="btn btn-success">Accept</button> + <button ng-really-click="reject()" ng-really-message="REJECT: Are you sure?" type="button" class="btn btn-danger" ng-disabled="response.reason == null" disabler ng-model="loading">Reject</button> + <br><br> + <div class="note">Give your reason for rejection.</div> + <textarea ng-model="response.reason" class="form-control" rows="3"></textarea> + </div> + </div> + </div> +</div> +\ No newline at end of file diff --git a/src/main/webapp/settings/purchase_limit.html b/src/main/webapp/settings/purchase_limit.html @@ -48,9 +48,9 @@ <wz-step title="Unlimited"> <div class="jumbotron text-center"> - <p>For Unlimited Spending, you must complete at least 1 purchase of $100 minimum.</p> + <p>For Unlimited Spending, you must complete at least 1 purchase of $100 minimum. <br><small>Then click the button below to verify with a Fraud Prevention Program.</small></p> <button type="submit" wz-next class="btn btn-primary btn-lg margin-20" ng-disabled="!thirdPartyVerified" disabler ng-model="loading"> - 3rd Party Verify + Fraud Prevention Verification </button> </div> </wz-step>