From b11ab4428561f0cf4d50426b1ca30c3568ce4664 Mon Sep 17 00:00:00 2001 From: yackob03 Date: Tue, 5 Nov 2013 14:40:45 -0500 Subject: [PATCH] Add the business plans in. --- data/plans.py | 58 ++++++++++++++++++++++++++++++++------ endpoints/api.py | 16 ++++++++--- static/css/quay.css | 48 +++++++++++++++++++++---------- static/js/app.js | 23 ++++++++++----- static/js/controllers.js | 6 ++-- static/partials/plans.html | 40 ++++++++++++++++++++------ 6 files changed, 145 insertions(+), 46 deletions(-) diff --git a/data/plans.py b/data/plans.py index 95e1ca731..aa17ed4b7 100644 --- a/data/plans.py +++ b/data/plans.py @@ -1,4 +1,5 @@ import json +import itertools USER_PLANS = [ { @@ -27,21 +28,60 @@ USER_PLANS = [ 'price': 2200, 'privateRepos': 20, 'stripeId': 'medium', - 'audience': 'For medium-sized teams', + 'audience': 'For medium teams', + }, + { + 'title': 'Large', + 'price': 5000, + 'privateRepos': 50, + 'stripeId': 'large', + 'audience': 'For larger teams', + }, +] + +BUSINESS_PLANS = [ + { + 'title': 'Open Source', + 'price': 0, + 'privateRepos': 0, + 'stripeId': 'bus-free', + 'audience': 'Committment to FOSS', + }, + { + 'title': 'Skiff', + 'price': 2500, + 'privateRepos': 10, + 'stripeId': 'bus-micro', + 'audience': 'For startups', + }, + { + 'title': 'Yacht', + 'price': 5000, + 'privateRepos': 20, + 'stripeId': 'bus-small', + 'audience': 'For small businesses', + }, + { + 'title': 'Freighter', + 'price': 10000, + 'privateRepos': 50, + 'stripeId': 'bus-medium', + 'audience': 'For normal businesses', + }, + { + 'title': 'Tanker', + 'price': 20000, + 'privateRepos': 125, + 'stripeId': 'bus-large', + 'audience': 'For large businesses', }, ] -def getPlan(id): +def get_plan(id): """ Returns the plan with the given ID or None if none. """ - for plan in USER_PLANS: + for plan in itertools.chain(USER_PLANS, BUSINESS_PLANS): if plan['stripeId'] == id: return plan return None - - -def isPlanActive(stripe_subscription): - """ Returns whether the plan is active. """ - # TODO: this. - return True diff --git a/endpoints/api.py b/endpoints/api.py index 8ed6251a3..b0b400f59 100644 --- a/endpoints/api.py +++ b/endpoints/api.py @@ -16,7 +16,7 @@ import storage from data import model from data.userfiles import UserRequestFiles from data.queue import dockerfile_build_queue -from data.plans import USER_PLANS, getPlan, isPlanActive +from data.plans import USER_PLANS, BUSINESS_PLANS, get_plan from app import app from util.email import send_confirmation_email, send_recovery_email from util.names import parse_repository_name @@ -50,13 +50,19 @@ def api_login_required(f): def handle_dme(ex): return make_response(ex.message, 400) + @app.route('/api/') def welcome(): return make_response('welcome', 200) + @app.route('/api/plans/') def plans_list(): - return jsonify({ 'plans': USER_PLANS }) + return jsonify({ + 'user': USER_PLANS, + 'business': BUSINESS_PLANS, + }) + @app.route('/api/user/', methods=['GET']) def get_logged_in_user(): @@ -112,6 +118,7 @@ def change_user_details(): 'askForPassword': user.password_hash is None, }) + @app.route('/api/user/', methods=['POST']) def create_user_api(): user_data = request.get_json() @@ -251,6 +258,7 @@ def team_view(orgname, t): 'role': role } + @app.route('/api/organization/', methods=['GET']) def get_organization(orgname): user = current_user.db_user() @@ -292,8 +300,8 @@ def get_organization_private_allowed(orgname): if organization.stripe_id: cus = stripe.Customer.retrieve(organization.stripe_id) - if cus.subscription and isPlanActive(cus.subscription): - repos_allowed = getPlan(cus.subscription.plan.id) + if cus.subscription: + repos_allowed = get_plan(cus.subscription.plan.id) return jsonify({ 'privateAllowed': (private_repos < repos_allowed) }) diff --git a/static/css/quay.css b/static/css/quay.css index 5fed775e0..5d5fba061 100644 --- a/static/css/quay.css +++ b/static/css/quay.css @@ -309,24 +309,46 @@ color: #428bca; } +.plans .all-plans .business-feature { + color: #46ac39; +} + .plans-list { text-align: center; margin-bottom: 25px; } +.plans-list .plan-container { + padding: 5px; +} + .plans-list .plan { - width: 245px; vertical-align: top; - display: inline-block; padding: 10px; - margin-right: 10px; border: 1px solid #eee; border-top: 4px solid #94C9F7; - - margin-top: 10px; - font-size: 1.4em; + margin-top: 5px; +} + +.plans-list .plan.small { + border: 1px solid #ddd; + border-top: 4px solid #428bca; + margin-top: 0px; + font-size: 1.6em; +} + +.plans-list .plan.business-plan { + border: 1px solid #eee; + border-top: 4px solid #94F794; +} + +.plans-list .plan.bus-small { + border: 1px solid #ddd; + border-top: 4px solid #47A447; + margin-top: 0px; + font-size: 1.6em; } .plans-list .plan:last-child { @@ -349,7 +371,7 @@ } .plan-price:after { - content: "/ month"; + content: "/ mo"; position: absolute; bottom: 0px; right: 20px; @@ -365,6 +387,10 @@ color: #428bca; } +.plans-list .plan.business-plan .count b { + color: #46ac39; +} + .plans-list .plan .description { font-size: 1em; font-size: 16px; @@ -386,14 +412,6 @@ margin-right: 5px; } - -.plans-list .plan.small { - border: 1px solid #ddd; - border-top: 4px solid #428bca; - margin-top: 0px; - font-size: 1.6em; -} - .plans .plan-faq dd{ margin-bottom: 20px; } diff --git a/static/js/app.js b/static/js/app.js index ca32342a3..411fe2307 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -119,15 +119,19 @@ quayApp = angular.module('quay', ['restangular', 'angularMoment', 'angulartics', var getPlans = Restangular.one('plans'); getPlans.get().then(function(data) { - for(var i = 0; i < data.plans.length; i++) { - planDict[data.plans[i].stripeId] = data.plans[i]; + var i = 0; + for(i = 0; i < data.user.length; i++) { + planDict[data.user[i].stripeId] = data.user[i]; } - plans = data.plans; + for(i = 0; i < data.business.length; i++) { + planDict[data.business[i].stripeId] = data.business[i]; + } + plans = data; callback(plans); }, function() { callback([]); }); }; - planService.getPlanList = function(callback) { + planService.getPlans = function(callback) { planService.verifyLoaded(callback); }; @@ -137,10 +141,15 @@ quayApp = angular.module('quay', ['restangular', 'angularMoment', 'angulartics', }); }; - planService.getMinimumPlan = function(privateCount, callback) { + planService.getMinimumPlan = function(privateCount, isBusiness, callback) { planService.verifyLoaded(function() { - for (var i = 0; i < plans.length; i++) { - var plan = plans[i]; + var planSource = plans.user; + if (isBusiness) { + planSource = plans.business; + } + + for (var i = 0; i < planSource.length; i++) { + var plan = planSource[i]; if (plan.privateRepos >= privateCount) { callback(plan); return; diff --git a/static/js/controllers.js b/static/js/controllers.js index eb51f18f1..c17787df3 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -131,7 +131,7 @@ function SigninCtrl($scope, $location, $timeout, Restangular, KeyService, UserSe function PlansCtrl($scope, UserService, PlanService) { // Load the list of plans. - PlanService.getPlanList(function(plans) { + PlanService.getPlans(function(plans) { $scope.plans = plans; $scope.status = 'ready'; }); @@ -673,8 +673,8 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) { function UserAdminCtrl($scope, $timeout, Restangular, PlanService, UserService, KeyService, $routeParams) { // Load the list of plans. - PlanService.getPlanList(function(plans) { - $scope.plans = plans; + PlanService.getPlans(function(plans) { + $scope.plans = plans.user; }); $scope.$watch(function () { return UserService.currentUser(); }, function (currentUser) { diff --git a/static/partials/plans.html b/static/partials/plans.html index 84885b790..0f34fe018 100644 --- a/static/partials/plans.html +++ b/static/partials/plans.html @@ -7,15 +7,39 @@ All plans include unlimited public repositories and unlimited sharing. All paid plans have a 14-day free trial. -
-
-
{{ plan.title }}
-
${{ plan.price/100 }}
-
{{ plan.privateRepos }} private repositories
-
{{ plan.audience }}
-
SSL secured connections
+
+
+
+
+
{{ plan.title }}
+
${{ plan.price/100 }}
+
{{ plan.privateRepos }} private repositories
+
{{ plan.audience }}
+
SSL secured connections
+ +
+
+
- +
+ Business Plan Pricing +
+ +
+ All business plans include all of the personal plan features, plus: organizations and teams with delegated access to the organization. All business plans have a 14-day free trial. +
+ +
+
+
+
+
{{ plan.title }}
+
${{ plan.price/100 }}
+
{{ plan.privateRepos }} private repositories
+
{{ plan.audience }}
+
SSL secured connections
+ +