Initial Spark Install
This commit is contained in:
65
spark/resources/assets/js/kiosk/add-discount.js
vendored
Normal file
65
spark/resources/assets/js/kiosk/add-discount.js
vendored
Normal 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');
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
116
spark/resources/assets/js/kiosk/announcements.js
vendored
Normal file
116
spark/resources/assets/js/kiosk/announcements.js
vendored
Normal 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');
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
33
spark/resources/assets/js/kiosk/kiosk.js
vendored
Normal file
33
spark/resources/assets/js/kiosk/kiosk.js
vendored
Normal 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;
|
||||
});
|
||||
}
|
||||
};
|
294
spark/resources/assets/js/kiosk/metrics.js
vendored
Normal file
294
spark/resources/assets/js/kiosk/metrics.js
vendored
Normal 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)",
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
148
spark/resources/assets/js/kiosk/profile.js
vendored
Normal file
148
spark/resources/assets/js/kiosk/profile.js
vendored
Normal 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
123
spark/resources/assets/js/kiosk/users.js
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
};
|
Reference in New Issue
Block a user