- Add some more analytics events
- Enable business features for personal users on business plans - Fix a bug in the credit card image view
This commit is contained in:
parent
8bfc0ac48d
commit
c20e7dbcf7
10 changed files with 241 additions and 121 deletions
|
@ -1601,9 +1601,31 @@ def subscribe(user, plan, token, require_business_plan):
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/user/invoices', methods=['GET'])
|
||||||
|
@api_login_required
|
||||||
|
def user_invoices_api():
|
||||||
|
user = current_user.db_user()
|
||||||
|
if not user.stripe_id:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
return get_invoices(user.stripe_id)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/organization/<orgname>/invoices', methods=['GET'])
|
@app.route('/api/organization/<orgname>/invoices', methods=['GET'])
|
||||||
@api_login_required
|
@api_login_required
|
||||||
def org_invoices_api(orgname):
|
def org_invoices_api(orgname):
|
||||||
|
permission = AdministerOrganizationPermission(orgname)
|
||||||
|
if permission.can():
|
||||||
|
organization = model.get_organization(orgname)
|
||||||
|
if not organization.stripe_id:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
return get_invoices(organization.stripe_id)
|
||||||
|
|
||||||
|
abort(403)
|
||||||
|
|
||||||
|
|
||||||
|
def get_invoices(customer_id):
|
||||||
def invoice_view(i):
|
def invoice_view(i):
|
||||||
return {
|
return {
|
||||||
'id': i.id,
|
'id': i.id,
|
||||||
|
@ -1619,18 +1641,10 @@ def org_invoices_api(orgname):
|
||||||
'plan': i.lines.data[0].plan.id if i.lines.data[0].plan else None
|
'plan': i.lines.data[0].plan.id if i.lines.data[0].plan else None
|
||||||
}
|
}
|
||||||
|
|
||||||
permission = AdministerOrganizationPermission(orgname)
|
invoices = stripe.Invoice.all(customer=customer_id, count=12)
|
||||||
if permission.can():
|
return jsonify({
|
||||||
organization = model.get_organization(orgname)
|
'invoices': [invoice_view(i) for i in invoices.data]
|
||||||
if not organization.stripe_id:
|
})
|
||||||
abort(404)
|
|
||||||
|
|
||||||
invoices = stripe.Invoice.all(customer=organization.stripe_id, count=12)
|
|
||||||
return jsonify({
|
|
||||||
'invoices': [invoice_view(i) for i in invoices.data]
|
|
||||||
})
|
|
||||||
|
|
||||||
abort(403)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/organization/<orgname>/plan', methods=['PUT'])
|
@app.route('/api/organization/<orgname>/plan', methods=['PUT'])
|
||||||
|
@ -1815,6 +1829,17 @@ def org_logs_api(orgname):
|
||||||
abort(403)
|
abort(403)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/user/logs', methods=['GET'])
|
||||||
|
@api_login_required
|
||||||
|
def user_logs_api():
|
||||||
|
performer_name = request.args.get('performer', None)
|
||||||
|
start_time = request.args.get('starttime', None)
|
||||||
|
end_time = request.args.get('endtime', None)
|
||||||
|
|
||||||
|
return get_logs(current_user.db_user().username, start_time, end_time,
|
||||||
|
performer_name=performer_name)
|
||||||
|
|
||||||
|
|
||||||
def get_logs(namespace, start_time, end_time, performer_name=None,
|
def get_logs(namespace, start_time, end_time, performer_name=None,
|
||||||
repository=None):
|
repository=None):
|
||||||
performer = None
|
performer = None
|
||||||
|
|
|
@ -4,7 +4,7 @@ import stripe
|
||||||
|
|
||||||
from flask import (abort, redirect, request, url_for, render_template,
|
from flask import (abort, redirect, request, url_for, render_template,
|
||||||
make_response, Response)
|
make_response, Response)
|
||||||
from flask.ext.login import login_user, UserMixin
|
from flask.ext.login import login_user, UserMixin, current_user
|
||||||
from flask.ext.principal import identity_changed
|
from flask.ext.principal import identity_changed
|
||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
|
|
||||||
|
@ -134,21 +134,34 @@ def privacy():
|
||||||
|
|
||||||
@app.route('/receipt', methods=['GET'])
|
@app.route('/receipt', methods=['GET'])
|
||||||
def receipt():
|
def receipt():
|
||||||
|
if not current_user.is_authenticated():
|
||||||
|
abort(401)
|
||||||
|
return
|
||||||
|
|
||||||
id = request.args.get('id')
|
id = request.args.get('id')
|
||||||
if id:
|
if id:
|
||||||
invoice = stripe.Invoice.retrieve(id)
|
invoice = stripe.Invoice.retrieve(id)
|
||||||
if invoice:
|
if invoice:
|
||||||
org = model.get_user_or_org_by_customer_id(invoice.customer)
|
user_or_org = model.get_user_or_org_by_customer_id(invoice.customer)
|
||||||
if org and org.organization:
|
|
||||||
admin_org = AdministerOrganizationPermission(org.username)
|
if user_or_org:
|
||||||
if admin_org.can():
|
if user_or_org.organization:
|
||||||
file_data = renderInvoiceToPdf(invoice, org)
|
admin_org = AdministerOrganizationPermission(user_or_org.username)
|
||||||
return Response(file_data,
|
if not admin_org.can():
|
||||||
mimetype="application/pdf",
|
abort(404)
|
||||||
headers={"Content-Disposition":
|
return
|
||||||
"attachment;filename=receipt.pdf"})
|
else:
|
||||||
|
if not user_or_org.username == current_user.db_user().username:
|
||||||
|
abort(404)
|
||||||
|
return
|
||||||
|
|
||||||
|
file_data = renderInvoiceToPdf(invoice, user_or_org)
|
||||||
|
return Response(file_data,
|
||||||
|
mimetype="application/pdf",
|
||||||
|
headers={"Content-Disposition": "attachment;filename=receipt.pdf"})
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
|
|
||||||
def common_login(db_user):
|
def common_login(db_user):
|
||||||
if login_user(_LoginWrappedDBUser(db_user.username, db_user)):
|
if login_user(_LoginWrappedDBUser(db_user.username, db_user)):
|
||||||
logger.debug('Successfully signed in as: %s' % db_user.username)
|
logger.debug('Successfully signed in as: %s' % db_user.username)
|
||||||
|
|
|
@ -1919,28 +1919,28 @@ p.editable:hover i {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.org-admin .invoice-title {
|
.billing-invoices-element .invoice-title {
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.org-admin .invoice-status .success {
|
.billing-invoices-element .invoice-status .success {
|
||||||
color: green;
|
color: green;
|
||||||
}
|
}
|
||||||
|
|
||||||
.org-admin .invoice-status .pending {
|
.billing-invoices-element .invoice-status .pending {
|
||||||
color: steelblue;
|
color: steelblue;
|
||||||
}
|
}
|
||||||
|
|
||||||
.org-admin .invoice-status .danger {
|
.billing-invoices-element .invoice-status .danger {
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
.org-admin .invoice-amount:before {
|
.billing-invoices-element .invoice-amount:before {
|
||||||
content: '$';
|
content: '$';
|
||||||
}
|
}
|
||||||
|
|
||||||
.org-admin .invoice-details {
|
.billing-invoices-element .invoice-details {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
@ -1949,21 +1949,21 @@ p.editable:hover i {
|
||||||
border-left: 2px solid #eee !important;
|
border-left: 2px solid #eee !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.org-admin .invoice-details td {
|
.billing-invoices-element .invoice-details td {
|
||||||
border: 0px solid transparent !important;
|
border: 0px solid transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.org-admin .invoice-details dl {
|
.billing-invoices-element .invoice-details dl {
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.org-admin .invoice-details dd {
|
.billing-invoices-element .invoice-details dd {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.org-admin .invoice-title:hover {
|
.billing-invoices-element .invoice-title:hover {
|
||||||
color: steelblue;
|
color: steelblue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
53
static/directives/billing-invoices.html
Normal file
53
static/directives/billing-invoices.html
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
<div class="billing-invoices-element">
|
||||||
|
<div ng-show="loading">
|
||||||
|
<div class="quay-spinner"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ng-show="!loading && !invoices">
|
||||||
|
No invoices have been created
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ng-show="!loading && invoices">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<th>Billing Date/Time</th>
|
||||||
|
<th>Amount Due</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th></th>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody class="invoice" ng-repeat="invoice in invoices">
|
||||||
|
<tr class="invoice-title">
|
||||||
|
<td ng-click="toggleInvoice(invoice.id)"><span class="invoice-datetime">{{ invoice.date * 1000 | date:'medium' }}</span></td>
|
||||||
|
<td ng-click="toggleInvoice(invoice.id)"><span class="invoice-amount">{{ invoice.amount_due / 100 }}</span></td>
|
||||||
|
<td>
|
||||||
|
<span class="invoice-status">
|
||||||
|
<span class="success" ng-show="invoice.paid">Paid - Thank you!</span>
|
||||||
|
<span class="danger" ng-show="!invoice.paid && invoice.attempted && invoice.closed">Payment failed</span>
|
||||||
|
<span class="danger" ng-show="!invoice.paid && invoice.attempted && !invoice.closed">Payment failed - Will retry soon</span>
|
||||||
|
<span class="pending" ng-show="!invoice.paid && !invoice.attempted">Payment pending</span>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a ng-show="invoice.paid" href="/receipt?id={{ invoice.id }}" download="receipt.pdf" target="_new">
|
||||||
|
<i class="fa fa-download" title="Download Receipt" bs-tooltip="tooltip.title"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr ng-class="invoiceExpanded[invoice.id] ? 'in' : 'out'" class="invoice-details panel-collapse collapse">
|
||||||
|
<td colspan="3">
|
||||||
|
<dl class="dl-normal">
|
||||||
|
<dt>Billing Period</dt>
|
||||||
|
<dd>
|
||||||
|
<span>{{ invoice.period_start * 1000 | date:'mediumDate' }}</span> -
|
||||||
|
<span>{{ invoice.period_end * 1000 | date:'mediumDate' }}</span>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
|
@ -7,7 +7,7 @@
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div class="quay-spinner" ng-show="!currentCard || changingCard"></div>
|
<div class="quay-spinner" ng-show="!currentCard || changingCard"></div>
|
||||||
<div class="current-card" ng-show="currentCard && !changingCard">
|
<div class="current-card" ng-show="currentCard && !changingCard">
|
||||||
<img src="{{ '/static/img/creditcards/' + getCreditImage(currentCard) }}" ng-show="currentCard.last4">
|
<img ng-src="{{ '/static/img/creditcards/' + getCreditImage(currentCard) }}" ng-show="currentCard.last4">
|
||||||
<span class="no-card-outline" ng-show="!currentCard.last4"></span>
|
<span class="no-card-outline" ng-show="!currentCard.last4"></span>
|
||||||
|
|
||||||
<span class="last4" ng-show="currentCard.last4">****-****-****-<b>{{ currentCard.last4 }}</b></span>
|
<span class="last4" ng-show="currentCard.last4">****-****-****-<b>{{ currentCard.last4 }}</b></span>
|
||||||
|
|
|
@ -969,6 +969,59 @@ quayApp.filter('visibleLogFilter', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
quayApp.directive('billingInvoices', function () {
|
||||||
|
var directiveDefinitionObject = {
|
||||||
|
priority: 0,
|
||||||
|
templateUrl: '/static/directives/billing-invoices.html',
|
||||||
|
replace: false,
|
||||||
|
transclude: false,
|
||||||
|
restrict: 'C',
|
||||||
|
scope: {
|
||||||
|
'organization': '=organization',
|
||||||
|
'user': '=user',
|
||||||
|
'visible': '=visible'
|
||||||
|
},
|
||||||
|
controller: function($scope, $element, $sce, Restangular) {
|
||||||
|
$scope.loading = false;
|
||||||
|
$scope.invoiceExpanded = {};
|
||||||
|
|
||||||
|
$scope.toggleInvoice = function(id) {
|
||||||
|
$scope.invoiceExpanded[id] = !$scope.invoiceExpanded[id];
|
||||||
|
};
|
||||||
|
|
||||||
|
var update = function() {
|
||||||
|
var hasValidUser = !!$scope.user;
|
||||||
|
var hasValidOrg = !!$scope.organization;
|
||||||
|
var isValid = hasValidUser || hasValidOrg;
|
||||||
|
|
||||||
|
if (!$scope.visible || !isValid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.loading = true;
|
||||||
|
|
||||||
|
var url = getRestUrl('user/invoices');
|
||||||
|
if ($scope.organization) {
|
||||||
|
url = getRestUrl('organization', $scope.organization.name, 'invoices');
|
||||||
|
}
|
||||||
|
|
||||||
|
var getInvoices = Restangular.one(url);
|
||||||
|
getInvoices.get().then(function(resp) {
|
||||||
|
$scope.invoices = resp.invoices;
|
||||||
|
$scope.loading = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.$watch('organization', update);
|
||||||
|
$scope.$watch('user', update);
|
||||||
|
$scope.$watch('visible', update);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return directiveDefinitionObject;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
quayApp.directive('logsView', function () {
|
quayApp.directive('logsView', function () {
|
||||||
var directiveDefinitionObject = {
|
var directiveDefinitionObject = {
|
||||||
priority: 0,
|
priority: 0,
|
||||||
|
@ -2112,15 +2165,27 @@ quayApp.run(['$location', '$rootScope', 'Restangular', 'UserService', 'PlanServi
|
||||||
// Check if we need to redirect based on a previously chosen plan.
|
// Check if we need to redirect based on a previously chosen plan.
|
||||||
PlanService.handleNotedPlan();
|
PlanService.handleNotedPlan();
|
||||||
|
|
||||||
var changeTab = function(activeTab) {
|
var changeTab = function(activeTab, opt_timeout) {
|
||||||
|
var checkCount = 0;
|
||||||
|
|
||||||
$timeout(function() {
|
$timeout(function() {
|
||||||
|
if (checkCount > 5) { return; }
|
||||||
|
checkCount++;
|
||||||
|
|
||||||
$('a[data-toggle="tab"]').each(function(index) {
|
$('a[data-toggle="tab"]').each(function(index) {
|
||||||
var tabName = this.getAttribute('data-target').substr(1);
|
var tabName = this.getAttribute('data-target').substr(1);
|
||||||
if (tabName == activeTab) {
|
if (tabName != activeTab) {
|
||||||
this.click();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.clientWidth == 0) {
|
||||||
|
changeTab(activeTab, 500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.click();
|
||||||
});
|
});
|
||||||
});
|
}, opt_timeout);
|
||||||
};
|
};
|
||||||
|
|
||||||
var resetDefaultTab = function() {
|
var resetDefaultTab = function() {
|
||||||
|
|
|
@ -602,8 +602,22 @@ function UserAdminCtrl($scope, $timeout, $location, Restangular, PlanService, Us
|
||||||
|
|
||||||
$('.form-change-pw').popover();
|
$('.form-change-pw').popover();
|
||||||
|
|
||||||
|
$scope.logsShown = 0;
|
||||||
|
$scope.invoicesShown = 0;
|
||||||
|
|
||||||
|
$scope.loadLogs = function() {
|
||||||
|
if (!$scope.hasPaidBusinessPlan) { return; }
|
||||||
|
$scope.logsShown++;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.loadInvoices = function() {
|
||||||
|
if (!$scope.hasPaidBusinessPlan) { return; }
|
||||||
|
$scope.invoicesShown++;
|
||||||
|
};
|
||||||
|
|
||||||
$scope.planChanged = function(plan) {
|
$scope.planChanged = function(plan) {
|
||||||
$scope.hasPaidPlan = plan && plan.price > 0;
|
$scope.hasPaidPlan = plan && plan.price > 0;
|
||||||
|
$scope.hasPaidBusinessPlan = PlanService.isOrgCompatible(plan) && plan.price > 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.showConvertForm = function() {
|
$scope.showConvertForm = function() {
|
||||||
|
@ -981,11 +995,6 @@ function NewRepoCtrl($scope, $location, $http, $timeout, UserService, Restangula
|
||||||
function OrgViewCtrl($rootScope, $scope, Restangular, ApiService, $routeParams) {
|
function OrgViewCtrl($rootScope, $scope, Restangular, ApiService, $routeParams) {
|
||||||
var orgname = $routeParams.orgname;
|
var orgname = $routeParams.orgname;
|
||||||
|
|
||||||
$('.info-icon').popover({
|
|
||||||
'trigger': 'hover',
|
|
||||||
'html': true
|
|
||||||
});
|
|
||||||
|
|
||||||
$scope.TEAM_PATTERN = TEAM_PATTERN;
|
$scope.TEAM_PATTERN = TEAM_PATTERN;
|
||||||
$rootScope.title = 'Loading...';
|
$rootScope.title = 'Loading...';
|
||||||
|
|
||||||
|
@ -1053,6 +1062,11 @@ function OrgViewCtrl($rootScope, $scope, Restangular, ApiService, $routeParams)
|
||||||
$scope.organization = org;
|
$scope.organization = org;
|
||||||
$rootScope.title = orgname;
|
$rootScope.title = orgname;
|
||||||
$rootScope.description = 'Viewing organization ' + orgname;
|
$rootScope.description = 'Viewing organization ' + orgname;
|
||||||
|
|
||||||
|
$('.info-icon').popover({
|
||||||
|
'trigger': 'hover',
|
||||||
|
'html': true
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1078,31 +1092,20 @@ function OrgAdminCtrl($rootScope, $scope, Restangular, $routeParams, UserService
|
||||||
$scope.membersFound = null;
|
$scope.membersFound = null;
|
||||||
$scope.invoiceLoading = true;
|
$scope.invoiceLoading = true;
|
||||||
$scope.logsShown = 0;
|
$scope.logsShown = 0;
|
||||||
|
$scope.invoicesShown = 0;
|
||||||
|
|
||||||
$scope.loadLogs = function() {
|
$scope.loadLogs = function() {
|
||||||
$scope.logsShown++;
|
$scope.logsShown++;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.loadInvoices = function() {
|
||||||
|
$scope.invoicesShown++;
|
||||||
|
};
|
||||||
|
|
||||||
$scope.planChanged = function(plan) {
|
$scope.planChanged = function(plan) {
|
||||||
$scope.hasPaidPlan = plan && plan.price > 0;
|
$scope.hasPaidPlan = plan && plan.price > 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.loadInvoices = function() {
|
|
||||||
if ($scope.invoices) { return; }
|
|
||||||
$scope.invoiceLoading = true;
|
|
||||||
|
|
||||||
var getInvoices = Restangular.one(getRestUrl('organization', orgname, 'invoices'));
|
|
||||||
getInvoices.get().then(function(resp) {
|
|
||||||
$scope.invoiceExpanded = {};
|
|
||||||
$scope.invoices = resp.invoices;
|
|
||||||
$scope.invoiceLoading = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.toggleInvoice = function(id) {
|
|
||||||
$scope.invoiceExpanded[id] = !$scope.invoiceExpanded[id];
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.loadMembers = function() {
|
$scope.loadMembers = function() {
|
||||||
if ($scope.membersFound) { return; }
|
if ($scope.membersFound) { return; }
|
||||||
$scope.membersLoading = true;
|
$scope.membersLoading = true;
|
||||||
|
|
|
@ -72,7 +72,8 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="button-bar">
|
<div class="button-bar">
|
||||||
<button class="btn btn-large btn-success" type="submit" ng-disabled="newOrgForm.$invalid || !currentPlan">
|
<button class="btn btn-large btn-success" type="submit" ng-disabled="newOrgForm.$invalid || !currentPlan"
|
||||||
|
analytics-on analytics-event="create_organization">
|
||||||
Create Organization
|
Create Organization
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -40,60 +40,7 @@
|
||||||
|
|
||||||
<!-- Billing History tab -->
|
<!-- Billing History tab -->
|
||||||
<div id="billing" class="tab-pane">
|
<div id="billing" class="tab-pane">
|
||||||
<div ng-show="invoiceLoading">
|
<div class="billing-invoices" organization="organization" visible="invoicesShown"></div>
|
||||||
<div class="quay-spinner"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div ng-show="!invoiceLoading && !invoices">
|
|
||||||
No invoices have been created
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div ng-show="!invoiceLoading && invoices">
|
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<th>Billing Date/Time</th>
|
|
||||||
<th>Amount Due</th>
|
|
||||||
<th>Status</th>
|
|
||||||
<th></th>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody class="invoice" ng-repeat="invoice in invoices">
|
|
||||||
<tr class="invoice-title">
|
|
||||||
<td ng-click="toggleInvoice(invoice.id)"><span class="invoice-datetime">{{ invoice.date * 1000 | date:'medium' }}</span></td>
|
|
||||||
<td ng-click="toggleInvoice(invoice.id)"><span class="invoice-amount">{{ invoice.amount_due / 100 }}</span></td>
|
|
||||||
<td>
|
|
||||||
<span class="invoice-status">
|
|
||||||
<span class="success" ng-show="invoice.paid">Paid - Thank you!</span>
|
|
||||||
<span class="danger" ng-show="!invoice.paid && invoice.attempted && invoice.closed">Payment failed</span>
|
|
||||||
<span class="danger" ng-show="!invoice.paid && invoice.attempted && !invoice.closed">Payment failed - Will retry soon</span>
|
|
||||||
<span class="pending" ng-show="!invoice.paid && !invoice.attempted">Payment pending</span>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a ng-show="invoice.paid" href="/receipt?id={{ invoice.id }}" download="receipt.pdf" target="_new">
|
|
||||||
<i class="fa fa-download" title="Download Receipt" bs-tooltip="tooltip.title"></i>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr ng-class="invoiceExpanded[invoice.id] ? 'in' : 'out'" class="invoice-details panel-collapse collapse">
|
|
||||||
<td colspan="3">
|
|
||||||
<dl class="dl-normal">
|
|
||||||
<dt>Billing Period</dt>
|
|
||||||
<dd>
|
|
||||||
<span>{{ invoice.period_start * 1000 | date:'mediumDate' }}</span> -
|
|
||||||
<span>{{ invoice.period_end * 1000 | date:'mediumDate' }}</span>
|
|
||||||
</dd>
|
|
||||||
<dt>Plan</dt>
|
|
||||||
<dd>
|
|
||||||
<span>{{ invoice.plan ? plan_map[invoice.plan].title : '(N/A)' }}</span>
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Members tab -->
|
<!-- Members tab -->
|
||||||
|
|
|
@ -27,16 +27,23 @@
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<ul class="nav nav-pills nav-stacked">
|
<ul class="nav nav-pills nav-stacked">
|
||||||
<li class="active"><a href="javascript:void(0)" data-toggle="tab" data-target="#plan">Plan and Usage</a></li>
|
<li class="active"><a href="javascript:void(0)" data-toggle="tab" data-target="#plan">Plan and Usage</a></li>
|
||||||
<li ng-show="hasPaidPlan"><a href="javascript:void(0)" data-toggle="tab" data-target="#billing">Billing Options</a></li>
|
<li ng-show="hasPaidPlan"><a href="javascript:void(0)" data-toggle="tab" data-target="#billingoptions">Billing Options</a></li>
|
||||||
|
<li ng-show="hasPaidBusinessPlan"><a href="javascript:void(0)" data-toggle="tab" data-target="#billing" ng-click="loadInvoices()">Billing History</a></li>
|
||||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#robots">Robot Accounts</a></li>
|
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#robots">Robot Accounts</a></li>
|
||||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#password">Set Password</a></li>
|
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#password">Set Password</a></li>
|
||||||
|
<li ng-show="hasPaidBusinessPlan"><a href="javascript:void(0)" data-toggle="tab" data-target="#logs" ng-click="loadLogs()">Usage Logs</a></li>
|
||||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#migrate" id="migrateTab">Convert to Organization</a></li>
|
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#migrate" id="migrateTab">Convert to Organization</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Content -->
|
<!-- Content -->
|
||||||
<div class="col-md-10">
|
<div class="col-md-10">
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
|
<!-- Logs tab -->
|
||||||
|
<div id="logs" class="tab-pane">
|
||||||
|
<div class="logs-view" user="user" visible="logsShown"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Plans tab -->
|
<!-- Plans tab -->
|
||||||
<div id="plan" class="tab-pane active">
|
<div id="plan" class="tab-pane active">
|
||||||
<div class="plan-manager" user="user.username" ready-for-plan="readyForPlan()" plan-changed="planChanged(plan)"></div>
|
<div class="plan-manager" user="user.username" ready-for-plan="readyForPlan()" plan-changed="planChanged(plan)"></div>
|
||||||
|
@ -69,10 +76,15 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Billing options tab -->
|
<!-- Billing options tab -->
|
||||||
<div id="billing" class="tab-pane">
|
<div id="billingoptions" class="tab-pane">
|
||||||
<div class="billing-options" user="user"></div>
|
<div class="billing-options" user="user"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Billing History tab -->
|
||||||
|
<div id="billing" class="tab-pane">
|
||||||
|
<div class="billing-invoices" user="user" visible="invoicesShown"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Convert to organization tab -->
|
<!-- Convert to organization tab -->
|
||||||
<div id="migrate" class="tab-pane">
|
<div id="migrate" class="tab-pane">
|
||||||
<!-- Step 0 -->
|
<!-- Step 0 -->
|
||||||
|
@ -86,11 +98,11 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel-body" ng-show="user.organizations.length == 0">
|
<div class="panel-body" ng-show="user.organizations.length == 0">
|
||||||
<div class="alert alert-danger">
|
<div class="alert alert-warning">
|
||||||
Converting a user account into an organization <b>cannot be undone</b>.<br> Here be many fire-breathing dragons!
|
Note: Converting a user account into an organization <b>cannot be undone</b>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="btn btn-danger" ng-click="showConvertForm()">Start conversion process</button>
|
<button class="btn btn-primary" ng-click="showConvertForm()">Start conversion process</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -113,7 +125,7 @@
|
||||||
ng-model="org.adminUser" required autofocus>
|
ng-model="org.adminUser" required autofocus>
|
||||||
<input id="adminPassword" name="adminPassword" type="password" class="form-control" placeholder="Admin Password"
|
<input id="adminPassword" name="adminPassword" type="password" class="form-control" placeholder="Admin Password"
|
||||||
ng-model="org.adminPassword" required>
|
ng-model="org.adminPassword" required>
|
||||||
<span class="description">The username and password for an <b>existing account</b> that will become administrator of the organization</span>
|
<span class="description">The username and password for the account that will become administrator of the organization</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Plans Table -->
|
<!-- Plans Table -->
|
||||||
|
@ -123,7 +135,8 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="button-bar">
|
<div class="button-bar">
|
||||||
<button class="btn btn-large btn-danger" type="submit" ng-disabled="convertForm.$invalid || !org.plan">
|
<button class="btn btn-large btn-danger" type="submit" ng-disabled="convertForm.$invalid || !org.plan"
|
||||||
|
analytics-on analytics-event="convert_to_organization">
|
||||||
Convert To Organization
|
Convert To Organization
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
Reference in a new issue