Initial Spark Install

This commit is contained in:
Deon George
2017-11-03 16:26:07 +11:00
commit b1a5807eb3
766 changed files with 128896 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
function kioskAddDiscountForm () {
return {
type: 'amount',
value: null,
duration: 'once',
months: null
};
}
module.exports = {
mixins: [require('./../mixins/discounts')],
/**
* The component's data.
*/
data() {
return {
loadingCurrentDiscount: false,
currentDiscount: null,
discountingUser: null,
form: new SparkForm(kioskAddDiscountForm())
};
},
/**
* The component has been created by Vue.
*/
created() {
var self = this;
Bus.$on('addDiscount', function (user) {
self.form = new SparkForm(kioskAddDiscountForm());
self.setUser(user);
$('#modal-add-discount').modal('show');
});
},
methods: {
/**
* Set the user receiving teh discount.
*/
setUser(user) {
this.discountingUser = user;
this.getCurrentDiscountForUser(user);
},
/**
* Apply the discount to the user.
*/
applyDiscount() {
Spark.post('/spark/kiosk/users/discount/' + this.discountingUser.id, this.form)
.then(() => {
$('#modal-add-discount').modal('hide');
});
},
}
};

View File

@@ -0,0 +1,116 @@
var announcementsCreateForm = function () {
return {
body: '',
action_text: '',
action_url: ''
};
};
module.exports = {
/**
* The component's data.
*/
data() {
return {
announcements: [],
updatingAnnouncement: null,
deletingAnnouncement: null,
createForm: new SparkForm(announcementsCreateForm()),
updateForm: new SparkForm(announcementsCreateForm()),
deleteForm: new SparkForm({})
};
},
/**
* The component has been created by Vue.
*/
created() {
var self = this;
Bus.$on('sparkHashChanged', function (hash, parameters) {
if (hash == 'announcements' && self.announcements.length === 0) {
self.getAnnouncements();
}
});
},
methods: {
/**
* Get all of the announcements.
*/
getAnnouncements() {
axios.get('/spark/kiosk/announcements')
.then(response => {
this.announcements = response.data;
});
},
/**
* Create a new announcement.
*/
create() {
Spark.post('/spark/kiosk/announcements', this.createForm)
.then(() => {
this.createForm = new SparkForm(announcementsCreateForm());
this.getAnnouncements();
});
},
/**
* Edit the given announcement.
*/
editAnnouncement(announcement) {
this.updatingAnnouncement = announcement;
this.updateForm.icon = announcement.icon;
this.updateForm.body = announcement.body;
this.updateForm.action_text = announcement.action_text;
this.updateForm.action_url = announcement.action_url;
$('#modal-update-announcement').modal('show');
},
/**
* Update the specified announcement.
*/
update() {
Spark.put('/spark/kiosk/announcements/' + this.updatingAnnouncement.id, this.updateForm)
.then(() => {
this.getAnnouncements();
$('#modal-update-announcement').modal('hide');
});
},
/**
* Show the approval dialog for deleting an announcement.
*/
approveAnnouncementDelete(announcement) {
this.deletingAnnouncement = announcement;
$('#modal-delete-announcement').modal('show');
},
/**
* Delete the specified announcement.
*/
deleteAnnouncement() {
Spark.delete('/spark/kiosk/announcements/' + this.deletingAnnouncement.id, this.deleteForm)
.then(() => {
this.getAnnouncements();
$('#modal-delete-announcement').modal('hide');
});
}
}
};

View File

@@ -0,0 +1,33 @@
module.exports = {
props: ['user'],
/**
* Load mixins for the component.
*/
mixins: [require('./../mixins/tab-state')],
/**
* Prepare the component.
*/
mounted() {
this.usePushStateForTabs('.spark-settings-tabs');
},
/**
* The component has been created by Vue.
*/
created() {
Bus.$on('sparkHashChanged', function (hash, parameters) {
if (hash == 'users') {
setTimeout(() => {
$('#kiosk-users-search').focus();
}, 150);
}
return true;
});
}
};

View File

@@ -0,0 +1,294 @@
module.exports = {
props: ['user'],
/**
* The component's data.
*/
data() {
return {
monthlyRecurringRevenue: 0,
yearlyRecurringRevenue: 0,
totalVolume: 0,
genericTrialUsers: 0,
indicators: [],
lastMonthsIndicators: null,
lastYearsIndicators: null,
plans: []
};
},
/**
* The component has been created by Vue.
*/
created() {
var self = this;
Bus.$on('sparkHashChanged', function (hash, parameters) {
if (hash == 'metrics' && self.yearlyRecurringRevenue === 0) {
self.getRevenue();
self.getPlans();
self.getTrialUsers();
self.getPerformanceIndicators();
}
});
},
methods: {
/**
* Get the revenue information for the application.
*/
getRevenue() {
axios.get('/spark/kiosk/performance-indicators/revenue')
.then(response => {
this.yearlyRecurringRevenue = response.data.yearlyRecurringRevenue;
this.monthlyRecurringRevenue = response.data.monthlyRecurringRevenue;
this.totalVolume = response.data.totalVolume;
});
},
/**
* Get the subscriber information for the application.
*/
getPlans() {
axios.get('/spark/kiosk/performance-indicators/plans')
.then(response => {
this.plans = response.data;
});
},
/**
* Get the number of users that are on a generic trial.
*/
getTrialUsers() {
axios.get('/spark/kiosk/performance-indicators/trialing')
.then(response => {
this.genericTrialUsers = parseInt(response.data);
});
},
/**
* Get the performance indicators for the application.
*/
getPerformanceIndicators() {
axios.get('/spark/kiosk/performance-indicators')
.then(response => {
this.indicators = response.data.indicators;
this.lastMonthsIndicators = response.data.last_month;
this.lastYearsIndicators = response.data.last_year;
Vue.nextTick(() => {
this.drawCharts();
});
});
},
/**
* Draw the performance indicator charts.
*/
drawCharts() {
this.drawMonthlyRecurringRevenueChart();
this.drawYearlyRecurringRevenueChart();
this.drawDailyVolumeChart();
this.drawNewUsersChart();
},
/**
* Draw the monthly recurring revenue chart.
*/
drawMonthlyRecurringRevenueChart() {
return this.drawCurrencyChart(
'monthlyRecurringRevenueChart', 30, indicator => indicator.monthly_recurring_revenue
);
},
/**
* Draw the yearly recurring revenue chart.
*/
drawYearlyRecurringRevenueChart() {
return this.drawCurrencyChart(
'yearlyRecurringRevenueChart', 30, indicator => indicator.yearly_recurring_revenue
);
},
/**
* Draw the daily volume chart.
*/
drawDailyVolumeChart() {
return this.drawCurrencyChart(
'dailyVolumeChart', 14, indicator => indicator.daily_volume
);
},
/**
* Draw the daily new users chart.
*/
drawNewUsersChart() {
return this.drawChart(
'newUsersChart', 14, indicator => indicator.new_users
);
},
/**
* Draw a chart with currency formatting on the Y-Axis.
*/
drawCurrencyChart(id, days, dataGatherer) {
return this.drawChart(id, days, dataGatherer, value =>
Vue.filter('currency')(value.value)
);
},
/**
* Draw a chart with the given parameters.
*/
drawChart(id, days, dataGatherer, scaleLabelFormatter) {
var dataset = JSON.parse(JSON.stringify(this.baseChartDataSet));
dataset.data = _.map(_.last(this.indicators, days), dataGatherer);
// Here we will build out the dataset for the chart. This will contain the dates and data
// points for the chart. Each chart on the Kiosk only gets one dataset so we only need
// to add it a single element to this array here. But, charts could have more later.
var data = {
labels: _.last(this.availableChartDates, days),
datasets: [dataset]
};
var options = { responsive: true };
// If a scale label formatter was passed, we will hand that to this chart library to fill
// out the Y-Axis labels. This is particularly useful when we want to format them as a
// currency as we do on all of our revenue charts that we display on the Kiosk here.
if (arguments.length === 4) {
options.scaleLabel = scaleLabelFormatter;
}
var chart = new Chart(document.getElementById(id).getContext('2d'), {
type: 'line',
data: data,
options: options
});
},
/**
* Calculate the percent change between two numbers.
*/
percentChange(current, previous) {
var change = Math.round(((current - previous) / previous) * 100);
return change > 0 ? '+' + change.toFixed(0) : change.toFixed(0);
}
},
computed: {
/**
* Calculate the monthly change in monthly recurring revenue.
*/
monthlyChangeInMonthlyRecurringRevenue() {
if ( ! this.lastMonthsIndicators || ! this.indicators) {
return false;
}
return this.percentChange(
_.last(this.indicators).monthly_recurring_revenue,
this.lastMonthsIndicators.monthly_recurring_revenue
);
},
/**
* Calculate the yearly change in monthly recurring revenue.
*/
yearlyChangeInMonthlyRecurringRevenue() {
if ( ! this.lastYearsIndicators || ! this.indicators) {
return false;
}
return this.percentChange(
_.last(this.indicators).monthly_recurring_revenue,
this.lastYearsIndicators.monthly_recurring_revenue
);
},
/**
* Calculate the monthly change in yearly recurring revenue.
*/
monthlyChangeInYearlyRecurringRevenue() {
if ( ! this.lastMonthsIndicators || ! this.indicators) {
return false;
}
return this.percentChange(
_.last(this.indicators).yearly_recurring_revenue,
this.lastMonthsIndicators.yearly_recurring_revenue
);
},
/**
* Calculate the yearly change in yearly recurring revenue.
*/
yearlyChangeInYearlyRecurringRevenue() {
if ( ! this.lastYearsIndicators || ! this.indicators) {
return false;
}
;
return this.percentChange(
_.last(this.indicators).yearly_recurring_revenue,
this.lastYearsIndicators.yearly_recurring_revenue
);
},
/**
* Get the total number of users trialing.
*/
totalTrialUsers() {
return this.genericTrialUsers + _.reduce(this.plans, (memo, plan) => {
return memo + plan.trialing;
}, 0);
},
/**
* Get the available, formatted chart dates for the current indicators.
*/
availableChartDates() {
return _.map(this.indicators, indicator => {
return moment(indicator.created_at).format('M/D');
});
},
/**
* Get the base chart data set.
*/
baseChartDataSet() {
return {
label: "Dataset",
fillColor: "rgba(151,187,205,0.2)",
strokeColor: "rgba(151,187,205,1)",
pointColor: "rgba(151,187,205,1)",
pointStrokeColor: "#fff",
pointHighlightFill: "#fff",
pointHighlightStroke: "rgba(151,187,205,1)",
};
}
}
};

View File

@@ -0,0 +1,148 @@
module.exports = {
props: ['user', 'plans'],
/**
* The component's data.
*/
data() {
return {
loading: false,
profile: null,
revenue: 0
};
},
/**
* The component has been created by Vue.
*/
created() {
var self = this;
this.$parent.$on('showUserProfile', function(id) {
self.getUserProfile(id);
});
},
/**
* Prepare the component.
*/
mounted() {
Mousetrap.bind('esc', e => this.showSearch());
},
methods: {
/**
* Get the profile user.
*/
getUserProfile(id) {
this.loading = true;
axios.get('/spark/kiosk/users/' + id + '/profile')
.then(response => {
this.profile = response.data.user;
this.revenue = response.data.revenue;
this.loading = false;
});
},
/**
* Impersonate the given user.
*/
impersonate(user) {
window.location = '/spark/kiosk/users/impersonate/' + user.id;
},
/**
* Show the discount modal for the given user.
*/
addDiscount(user) {
Bus.$emit('addDiscount', user);
},
/**
* Get the plan the user is actively subscribed to.
*/
activePlan(billable) {
if (this.activeSubscription(billable)) {
var activeSubscription = this.activeSubscription(billable);
return _.find(this.plans, (plan) => {
return plan.id == activeSubscription.provider_plan;
});
}
},
/**
* Get the active, valid subscription for the user.
*/
activeSubscription(billable) {
var subscription = this.subscription(billable);
if ( ! subscription ||
(subscription.ends_at &&
moment.utc().isAfter(moment.utc(subscription.ends_at)))) {
return;
}
return subscription;
},
/**
* Get the active subscription instance.
*/
subscription(billable) {
if ( ! billable) {
return;
}
const subscription = _.find(
billable.subscriptions,
subscription => subscription.name == 'default'
);
if (typeof subscription !== 'undefined') {
return subscription;
}
},
/**
* Get the customer URL on the billing provider's website.
*/
customerUrlOnBillingProvider(billable) {
if (! billable) {
return;
}
if (this.spark.usesStripe) {
return 'https://dashboard.stripe.com/customers/' + billable.stripe_id;
} else {
var domain = Spark.env == 'production' ? '' : 'sandbox.';
return 'https://' + domain + 'braintreegateway.com/merchants/' +
Spark.braintreeMerchantId +
'/customers/' +
billable.braintree_id;
}
},
/**
* Show the search results and hide the user profile.
*/
showSearch() {
this.$parent.$emit('showSearch');
this.profile = null;
}
}
};

123
spark/resources/assets/js/kiosk/users.js vendored Normal file
View File

@@ -0,0 +1,123 @@
module.exports = {
props: ['user'],
/**
* The component's data.
*/
data() {
return {
plans: [],
searchForm: new SparkForm({
query: ''
}),
searching: false,
noSearchResults: false,
searchResults: [],
showingUserProfile: false
};
},
/**
* The component has been created by Vue.
*/
created() {
var self = this;
this.getPlans();
this.$on('showSearch', function(){
self.navigateToSearch();
});
Bus.$on('sparkHashChanged', function (hash, parameters) {
if (hash != 'users') {
return true;
}
if (parameters && parameters.length > 0) {
self.loadProfile({ id: parameters[0] });
} else {
self.showSearch();
}
return true;
});
},
methods: {
/**
* Get all of the available subscription plans.
*/
getPlans() {
axios.get('/spark/plans')
.then(response => {
this.plans = response.data;
});
},
/**
* Perform a search for the given query.
*/
search() {
this.searching = true;
this.noSearchResults = false;
axios.post('/spark/kiosk/users/search', this.searchForm)
.then(response => {
this.searchResults = response.data;
this.noSearchResults = this.searchResults.length === 0;
this.searching = false;
});
},
/**
* Show the search results and update the browser history.
*/
navigateToSearch() {
history.pushState(null, null, '#/users');
this.showSearch();
},
/**
* Show the search results.
*/
showSearch() {
this.showingUserProfile = false;
Vue.nextTick(function () {
$('#kiosk-users-search').focus();
});
},
/**
* Show the user profile for the given user.
*/
showUserProfile(user) {
history.pushState(null, null, '#/users/' + user.id);
this.loadProfile(user);
},
/**
* Load the user profile for the given user.
*/
loadProfile(user) {
this.$emit('showUserProfile', user.id);
this.showingUserProfile = true;
}
}
};