1
0
mirror of https://github.com/beestat/app.git synced 2025-05-23 18:04:14 -04:00

New contribute page is ready for testing

This commit is contained in:
Jon Ziebell 2022-10-16 21:40:50 -04:00
parent 8f90bd154b
commit 2879d77e3d
18 changed files with 969 additions and 206 deletions

50
api/stripe.php Normal file
View File

@ -0,0 +1,50 @@
<?php
/**
* High level functionality for interacting with the Stripe API.
*
* @author Jon Ziebell
*/
class stripe extends external_api {
public static $exposed = [
'private' => [],
'public' => []
];
protected static $log_mysql = 'all';
protected static $cache = false;
protected static $cache_for = null;
/**
* Send an API call off to Stripe
*
* @param string $method HTTP Method.
* @param string $endpoint API Endpoint.
* @param array $data API request data.
*
* @throws Exception If Stripe did not return valid JSON.
*
* @return array The Stripe response.
*/
public function stripe_api($method, $endpoint, $data) {
$curl_response = $this->curl([
'url' => $this->setting->get('stripe_base_url') . $endpoint,
'post_fields' => http_build_query($data),
'method' => $method,
'header' => [
'Authorization: Basic ' . base64_encode($this->setting->get('stripe_secret_key') . ':'),
'Content-Type: application/x-www-form-urlencoded'
]
]);
$response = json_decode($curl_response, true);
if ($response === null) {
throw new cora\exception('Invalid JSON', 10900);
}
return $response;
}
}

8
api/stripe_api_cache.php Normal file
View File

@ -0,0 +1,8 @@
<?php
/**
* Cache for these external API calls.
*
* @author Jon Ziebell
*/
class stripe_api_cache extends external_api_cache {}

8
api/stripe_api_log.php Normal file
View File

@ -0,0 +1,8 @@
<?php
/**
* Log for these external API calls.
*
* @author Jon Ziebell
*/
class stripe_api_log extends external_api_log {}

17
api/stripe_event.php Normal file
View File

@ -0,0 +1,17 @@
<?php
/**
* Stripe Event
*
* @author Jon Ziebell
*/
class stripe_event extends cora\crud {
public static $exposed = [
'private' => [
'read_id'
],
'public' => []
];
}

View File

@ -0,0 +1,94 @@
<?php
/**
* A Stripe payment_link.
*
* @author Jon Ziebell
*/
class stripe_payment_link extends cora\crud {
public static $exposed = [
'private' => [
'get',
'open'
],
'public' => []
];
public static $user_locked = false;
/**
* Get a stripe_payment_link for the specified attributes. If none exists,
* create.
*
* @param array $attributes
*
* @return array
*/
public function get($attributes) {
$stripe_payment_link = parent::get([
'amount' => $attributes['amount'],
'currency' => $attributes['currency'],
'interval' => $attributes['interval']
]);
if($stripe_payment_link === null) {
$price = $this->api(
'stripe',
'stripe_api',
[
'method' => 'POST',
'endpoint' => 'prices',
'data' => [
'product' => $this->setting->get('stripe_product_id'),
'unit_amount' => $attributes['amount'],
'currency' => $attributes['currency'],
'recurring[interval]' => $attributes['interval']
]
]
);
$payment_link = $this->api(
'stripe',
'stripe_api',
[
'method' => 'POST',
'endpoint' => 'payment_links',
'data' => [
'line_items[0][price]' => $price['id'],
'line_items[0][quantity]' => '1'
]
]
);
return $this->create([
'amount' => $attributes['amount'],
'currency' => $attributes['currency'],
'interval' => $attributes['interval'],
'url' => $payment_link['url']
]);
} else {
return $stripe_payment_link;
}
}
/**
* Open a Stripe link. This exists because in JS it would be a popup to run
* an API call to get the link, then do window.open after. This lets you
* just do a window.open directly to this endpoint.
*
* @param array $attributes
*/
public function open($attributes) {
$stripe_payment_link = $this->get($attributes);
$user = $this->api('user', 'get', $this->session->get_user_id());
$url = $stripe_payment_link['url'] .
'?prefilled_email=' . $user['email_address'] .
'&client_reference_id=' . $user['user_id'];
header('Location: ' . $url);
die();
}
}

View File

@ -454,6 +454,7 @@ input[type=range]::-moz-range-thumb {
.icon.code_tags:before { content: "\F0174"; }
.icon.cog:before { content: "\F0493"; }
.icon.credit_card_lock:before { content: "\F18E7"; }
.icon.credit_card_settings:before { content: "\F0FF5"; }
.icon.currency_usd:before { content: "\F01C1"; }
.icon.delete:before { content: "\F01B4"; }
.icon.dots_vertical:before { content: "\F01D9"; }
@ -468,6 +469,7 @@ input[type=range]::-moz-range-thumb {
.icon.fast_forward:before { content: "\F0211"; }
.icon.fire:before { content: "\F0238"; }
.icon.floor_plan:before { content: "\F0821"; }
.icon.forum:before { content: "\F028C"; }
.icon.gift:before { content: "\F0E44"; }
.icon.google_play:before { content: "\F02BC"; }
.icon.grid:before { content: "\F02C1"; }
@ -481,6 +483,8 @@ input[type=range]::-moz-range-thumb {
.icon.information:before { content: "\F02FC"; }
.icon.key:before { content: "\F0306"; }
.icon.label:before { content: "\F0315"; }
.icon.label:before { content: "\F0315"; }
.icon.label_off:before { content: "\F0ACB"; }
.icon.layers:before { content: "\F0328"; }
.icon.layers_plus:before { content: "\F0E4D"; }
.icon.link_off:before { content: "\F0338"; }
@ -516,6 +520,7 @@ input[type=range]::-moz-range-thumb {
.icon.numeric_8_box:before { content: "\F03B9"; }
.icon.numeric_9:before { content: "\F0B42"; }
.icon.numeric_9_box:before { content: "\F03BC"; }
.icon.octagram:before { content: "\F06F9"; }
.icon.open_in_new:before { content: "\F03CC"; }
.icon.patreon:before { content: "\F0882"; }
.icon.pause:before { content: "\F03E4"; }
@ -529,6 +534,7 @@ input[type=range]::-moz-range-thumb {
.icon.restart:before { content: "\F0709"; }
.icon.restart_off:before { content: "\F0D95"; }
.icon.snowflake:before { content: "\F0717"; }
.icon.sticker_emoji:before { content: "\F0785"; }
.icon.swap_horizontal:before { content: "\F04E1"; }
.icon.thermometer:before { content: "\F050F"; }
.icon.thermostat:before { content: "\F0393"; }
@ -558,8 +564,6 @@ input[type=range]::-moz-range-thumb {
.icon.wifi_strength_1_alert:before { content: "\F0920"; }
.icon.wifi_strength_4:before { content: "\F0928"; }
.icon.zigbee:before { content: "\F0D41"; }
.icon.label:before { content: "\F0315"; }
.icon.label_off:before { content: "\F0ACB"; }
.icon.f16:before { font-size: 16px; }
.icon.f24:before { font-size: 24px; }

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -83,7 +83,9 @@ beestat.setting = function(argument_1, opt_value, opt_callback) {
'visualize.three_d.show_labels': false,
'visualize.three_d.auto_rotate': false,
'date_format': 'M/D/YYYY'
'date_format': 'M/D/YYYY',
'units.currency': 'usd'
};
// Figure out what we're trying to do.

View File

@ -6,13 +6,32 @@ beestat.user = {};
* @return {boolean} true if yes, false if no.
*/
beestat.user.patreon_is_active = function() {
var user = beestat.user.get();
const user = beestat.user.get();
return (
user.patreon_status !== null &&
user.patreon_status.patron_status === 'active_patron'
);
};
/**
* Determine whether or not the current user is an active Stripe giver.
*
* @return {boolean} true if yes, false if no.
*/
beestat.user.stripe_is_active = function() {
const stripe_events = Object.values(beestat.cache.stripe_event);
for (let i = 0; i < stripe_events.length; i++) {
if (
stripe_events[i].type === 'invoice.paid' &&
moment.unix(stripe_events[i].data.period_end).isAfter(moment()) === false
) {
return true;
}
}
return false;
};
/**
* Is the user connected to Patreon.
*
@ -25,15 +44,13 @@ beestat.user.patreon_is_connected = function() {
/**
* Whether or not the current user gets access to early release features.
*
* @return {boolean} Early access or not.
* @return {boolean}
*/
beestat.user.has_early_access = function() {
var user = beestat.user.get();
const user = beestat.user.get();
return user.user_id === 1 ||
(
user.patreon_status !== null &&
user.patreon_status.patron_status === 'active_patron'
);
beestat.user.stripe_is_active() === true ||
beestat.user.patreon_is_active() === true;
};
/**
@ -42,6 +59,6 @@ beestat.user.has_early_access = function() {
* @return {object} The current user.
*/
beestat.user.get = function() {
var user_id = Object.keys(beestat.cache.user)[0];
const user_id = Object.keys(beestat.cache.user)[0];
return beestat.cache.user[user_id];
};

View File

@ -4,27 +4,15 @@
beestat.component.card.contribute = function() {
beestat.component.card.apply(this, arguments);
this.state_.currency_multiplier = beestat.setting('units.currency') === 'usd' ? 1 : 1.5;
this.state_.contribute_type = 'recurring';
this.state_.contribute_frequency = 'yearly';
this.state_.contribute_amount = 2;
this.state_.contribute_amount_other = 5;
this.state_.contribute_interval = 'year';
this.state_.contribute_amount = 2 * this.state_.currency_multiplier;
this.state_.contribute_amount_other = 5 * this.state_.currency_multiplier;
this.state_.contribute_amount_other_enabled = false;
};
beestat.extend(beestat.component.card.contribute, beestat.component.card);
beestat.component.card.contribute.payment_links = {
'monthly': {
'1': 'https://buy.stripe.com/test_6oEeYk3dG2mmbhC28f',
'2': 'https://buy.stripe.com/test_dR6eYk29C6CC99ubIR',
'3': 'https://buy.stripe.com/test_eVadUg29C5yyadybIQ'
},
'yearly': {
'12': 'https://buy.stripe.com/test_9AQ2by29CaSSgBW3cg',
'24': 'https://buy.stripe.com/test_eVa4jGdSk2mmgBW5kq',
'36': 'https://buy.stripe.com/test_4gwg2o7tWe5485q6ot'
}
};
/**
* Decorate.
*
@ -33,190 +21,258 @@ beestat.component.card.contribute.payment_links = {
beestat.component.card.contribute.prototype.decorate_contents_ = function(parent) {
const self = this;
const p1 = document.createElement('p');
p1.innerText = 'Beestat is completely free to use and does not run ads or sell your data. If you find this service useful, please consider contributing. Your gift directly supports operating costs and project development.';
parent.appendChild(p1);
if (beestat.user.has_early_access() === true) {
const p1 = document.createElement('p');
p1.innerText = 'Thank you so much for your support! Your contribution goes a long way towards keeping beestat running and enabling me to create a rich and useful application. My hope is that you are able to use beestat to save money and improve the efficiency of your home.';
parent.appendChild(p1);
const p2 = document.createElement('p');
p2.innerText = 'I would like to give...';
parent.appendChild(p2);
const tile_group = new beestat.component.tile_group();
// Amount
const amount_container = document.createElement('div');
Object.assign(amount_container.style, {
'display': 'flex',
'flex-wrap': 'wrap',
'align-items': 'center',
'grid-gap': `${beestat.style.size.gutter}px`
});
parent.appendChild(amount_container);
const tile_group_amount = new beestat.component.tile_group();
const amounts = [
1,
2,
3
];
amounts.forEach(function(amount) {
const tile_amount = new beestat.component.tile()
.set_background_color(beestat.style.color.bluegray.light)
.set_background_hover_color(beestat.style.color.green.base)
const discord_tile = new beestat.component.tile()
.set_background_color(beestat.style.color.purple.base)
.set_background_hover_color(beestat.style.color.purple.light)
.set_text_color('#fff')
.set_text('$' + amount + ' / month');
tile_group_amount.add_tile(tile_amount);
.set_icon('forum')
.set_text('Join the Private Discord');
discord_tile.addEventListener('click', function() {
window.open('https://discord.gg/GzWbhD6tSB');
});
tile_group.add_tile(discord_tile);
if (
amount === self.state_.contribute_amount &&
self.state_.contribute_amount_other_enabled === false
) {
tile_amount
.set_background_color(beestat.style.color.green.base);
} else {
tile_amount
.set_background_color(beestat.style.color.bluegray.light)
.set_background_hover_color(beestat.style.color.green.base);
const community_tile = new beestat.component.tile()
.set_background_color(beestat.style.color.purple.base)
.set_background_hover_color(beestat.style.color.purple.light)
.set_text_color('#fff')
.set_icon('forum')
.set_text('Join the beestat Community');
community_tile.addEventListener('click', function() {
window.open('https://community.beestat.io');
});
tile_group.add_tile(community_tile);
tile_amount.addEventListener('click', function() {
self.state_.contribute_amount_other_enabled = false;
self.state_.contribute_amount = amount;
self.rerender();
});
}
});
tile_group.render(parent);
const tile_amount_other = new beestat.component.tile()
.set_text_color('#fff')
.set_text('Other Amount');
if (this.state_.contribute_amount_other_enabled === true) {
tile_amount_other
.set_background_color(beestat.style.color.green.base);
const p2 = document.createElement('p');
p2.innerText = 'Looking for a way to contribute more but don\'t want to change your recurring support amount?';
parent.appendChild(p2);
const one_time_gift_tile = new beestat.component.tile()
.set_background_color(beestat.style.color.green.base)
.set_background_hover_color(beestat.style.color.green.light)
.set_text_color('#fff')
.set_icon('gift')
.set_text('Make a One-Time Gift');
one_time_gift_tile.addEventListener('click', function() {
window.open('https://donate.stripe.com/7sIcPp4Gt6APais144');
});
one_time_gift_tile.render(parent);
} else {
tile_amount_other
.set_background_color(beestat.style.color.bluegray.light)
.set_background_hover_color(beestat.style.color.green.base);
}
const p3 = document.createElement('p');
p3.innerText = 'Beestat is completely free to use and does not run ads or sell your data. If you find this service useful, please consider contributing. Your gift directly supports operating costs and project development.';
parent.appendChild(p3);
tile_amount_other.addEventListener('click', function() {
self.state_.contribute_amount_other_enabled = true;
self.rerender();
});
tile_group_amount.add_tile(tile_amount_other);
const p4 = document.createElement('p');
p4.innerText = 'I would like to give...';
parent.appendChild(p4);
tile_group_amount.render($(amount_container));
// Amount
const amount_container = document.createElement('div');
Object.assign(amount_container.style, {
'display': 'flex',
'flex-wrap': 'wrap',
'align-items': 'center',
'grid-gap': `${beestat.style.size.gutter}px`
});
parent.appendChild(amount_container);
if (this.state_.contribute_amount_other_enabled === true) {
const other_amount_input = new beestat.component.input.text()
.set_width(100)
.set_icon('currency_usd')
.set_value(this.state_.contribute_amount_other)
.set_maxlength(6)
.set_requirements({
'required': true,
'type': 'number',
'min_value': 1,
'max_value': 100
})
.set_transform({
'type': 'round',
'decimals': 2
});
other_amount_input.addEventListener('blur', function() {
if (this.meets_requirements() === true) {
self.state_.contribute_amount_other = this.get_value();
self.rerender();
const tile_group_amount = new beestat.component.tile_group();
const amounts = [
1 * this.state_.currency_multiplier,
2 * this.state_.currency_multiplier,
3 * this.state_.currency_multiplier
];
amounts.forEach(function(amount) {
const tile_amount = new beestat.component.tile()
.set_background_color(beestat.style.color.bluegray.light)
.set_background_hover_color(beestat.style.color.green.base)
.set_text_color('#fff')
.set_text('$' + amount + ' / month');
tile_group_amount.add_tile(tile_amount);
if (
amount === self.state_.contribute_amount &&
self.state_.contribute_amount_other_enabled === false
) {
tile_amount
.set_background_color(beestat.style.color.green.base);
} else {
tile_amount
.set_background_color(beestat.style.color.bluegray.light)
.set_background_hover_color(beestat.style.color.green.base);
tile_amount.addEventListener('click', function() {
self.state_.contribute_amount_other_enabled = false;
self.state_.contribute_amount = amount;
self.rerender();
});
}
});
other_amount_input.render($(amount_container));
}
// Frequency
const tile_group_frequency = new beestat.component.tile_group();
tile_group_frequency.style({
'margin-top': `${beestat.style.size.gutter}px`
});
const frequencies = [
'yearly',
'monthly'
];
frequencies.forEach(function(frequency) {
const tile_frequency = new beestat.component.tile()
.set_background_color(beestat.style.color.bluegray.light)
.set_background_hover_color(beestat.style.color.lightblue.base)
const tile_amount_other = new beestat.component.tile()
.set_text_color('#fff')
.set_text(frequency.charAt(0).toUpperCase() + frequency.slice(1));
tile_group_frequency.add_tile(tile_frequency);
if (frequency === self.state_.contribute_frequency) {
tile_frequency
.set_background_color(beestat.style.color.lightblue.base);
.set_text('Other Amount');
if (this.state_.contribute_amount_other_enabled === true) {
tile_amount_other
.set_background_color(beestat.style.color.green.base);
} else {
tile_frequency
tile_amount_other
.set_background_color(beestat.style.color.bluegray.light)
.set_background_hover_color(beestat.style.color.lightblue.base);
tile_frequency.addEventListener('click', function() {
self.state_.contribute_frequency = frequency;
self.rerender();
});
.set_background_hover_color(beestat.style.color.green.base);
}
});
tile_group_frequency.render(parent);
tile_amount_other.addEventListener('click', function() {
self.state_.contribute_amount_other_enabled = true;
self.rerender();
});
tile_group_amount.add_tile(tile_amount_other);
// Review
const review_container = document.createElement('div');
Object.assign(review_container.style, {
'padding': `${beestat.style.size.gutter}px`,
'background': beestat.style.color.bluegray.dark,
'margin-top': `${beestat.style.size.gutter}px`,
'margin-bottom': `${beestat.style.size.gutter}px`
});
tile_group_amount.render($(amount_container));
let contribute_amount;
if (this.state_.contribute_amount_other_enabled === true) {
contribute_amount = this.state_.contribute_amount_other;
} else {
contribute_amount = this.state_.contribute_amount;
if (this.state_.contribute_amount_other_enabled === true) {
const other_amount_input = new beestat.component.input.text()
.set_width(100)
.set_icon('currency_usd')
.set_value(Number(this.state_.contribute_amount_other).toFixed(2))
.set_maxlength(6)
.set_requirements({
'required': true,
'type': 'decimal',
'min_value': 1
})
.set_transform({
'type': 'round',
'decimals': 2
});
other_amount_input.addEventListener('blur', function() {
if (this.meets_requirements() === true) {
self.state_.contribute_amount_other = this.get_value();
self.rerender();
}
});
other_amount_input.render($(amount_container));
}
// Frequency
const tile_group_frequency = new beestat.component.tile_group();
tile_group_frequency.style({
'margin-top': `${beestat.style.size.gutter}px`
});
const intervals = [
'year',
'month'
];
intervals.forEach(function(frequency) {
const tile_frequency = new beestat.component.tile()
.set_background_color(beestat.style.color.bluegray.light)
.set_background_hover_color(beestat.style.color.lightblue.base)
.set_text_color('#fff')
.set_text('Pay ' + frequency.charAt(0).toUpperCase() + frequency.slice(1) + 'ly');
tile_group_frequency.add_tile(tile_frequency);
if (frequency === self.state_.contribute_interval) {
tile_frequency
.set_background_color(beestat.style.color.lightblue.base);
} else {
tile_frequency
.set_background_color(beestat.style.color.bluegray.light)
.set_background_hover_color(beestat.style.color.lightblue.base);
tile_frequency.addEventListener('click', function() {
self.state_.contribute_interval = frequency;
self.rerender();
});
}
});
tile_group_frequency.render(parent);
// Review
const review_container = document.createElement('div');
Object.assign(review_container.style, {
'padding': `${beestat.style.size.gutter}px`,
'background': beestat.style.color.bluegray.dark,
'margin-top': `${beestat.style.size.gutter}px`,
'margin-bottom': `${beestat.style.size.gutter}px`
});
let contribute_amount;
if (this.state_.contribute_amount_other_enabled === true) {
contribute_amount = this.state_.contribute_amount_other;
} else {
contribute_amount = this.state_.contribute_amount;
}
if (this.state_.contribute_interval === 'year') {
contribute_amount *= 12;
}
contribute_amount = Math.round(contribute_amount * 100) / 100;
const contribute_amount_formatted = new Intl.NumberFormat('en-US', {
'style': 'currency',
'currency': beestat.setting('units.currency')
}).format(contribute_amount);
const contribute_interval = this.state_.contribute_interval;
review_container.innerText = 'Give ' + contribute_amount_formatted + ' / ' + contribute_interval + ' until I cancel.';
parent.appendChild(review_container);
// Buttons
const button_container = document.createElement('div');
Object.assign(button_container.style, {
'text-align': 'right'
});
parent.appendChild($(button_container));
const continue_tile = new beestat.component.tile()
.set_background_color(beestat.style.color.green.base)
.set_background_hover_color(beestat.style.color.green.light)
.set_text_color('#fff')
.set_size('large')
.set_icon('credit_card_lock')
.set_text('Continue to Payment');
continue_tile.addEventListener('click', function() {
self.state_.stripe_connecting = true;
this
.set_background_color(beestat.style.color.gray.base)
.set_background_hover_color()
.removeEventListener('click');
window.open('https://dev.app.beestat.io/api/?resource=stripe_payment_link&method=open&arguments={"attributes":{"amount":' + (contribute_amount * 100) + ',"currency":"' + beestat.setting('units.currency') + '","interval":"' + contribute_interval + '"}}&api_key=' + beestat.api.api_key);
setTimeout(function() {
self.rerender();
}, 5000);
});
continue_tile.render($(button_container));
if (this.state_.stripe_connecting === true) {
const api_call = new beestat.api()
.add_call('stripe_event', 'read_id')
.set_callback(function(response) {
beestat.cache.set('stripe_event', response);
});
window.setTimeout(function() {
api_call.send();
self.rerender();
}, 5000);
}
}
if (this.state_.contribute_frequency === 'yearly') {
contribute_amount *= 12;
}
contribute_amount = Math.round(contribute_amount * 100) / 100;
const contribute_amount_formatted = new Intl.NumberFormat('en-US', {
'style': 'currency',
'currency': 'USD'
}).format(contribute_amount);
const contribute_frequency = this.state_.contribute_frequency.replace('ly', '');
review_container.innerText = 'Give ' + contribute_amount_formatted + ' / ' + contribute_frequency + ' until I cancel.';
parent.appendChild(review_container);
// Buttons
const button_container = document.createElement('div');
Object.assign(button_container.style, {
'text-align': 'right'
});
parent.appendChild($(button_container));
const continue_tile = new beestat.component.tile()
.set_background_color(beestat.style.color.green.base)
.set_background_hover_color(beestat.style.color.green.light)
.set_text_color('#fff')
.set_size('large')
.set_icon('credit_card_lock')
.set_text('Continue to Payment');
continue_tile.addEventListener('click', function() {
window.open(
beestat.component.card.contribute.payment_links[self.state_.contribute_frequency][contribute_amount] +
'?prefilled_email=' + beestat.user.get().email_address +
'&client_reference_id=' + beestat.user.get().user_id
);
});
continue_tile.render($(button_container));
};
/**
@ -234,19 +290,21 @@ beestat.component.card.contribute.prototype.get_title_ = function() {
* @param {rocket.Elements} parent
*/
beestat.component.card.contribute.prototype.decorate_top_right_ = function(parent) {
const menu = (new beestat.component.menu()).render(parent);
if (beestat.user.has_early_access() === false) {
const menu = (new beestat.component.menu()).render(parent);
menu.add_menu_item(new beestat.component.menu_item()
.set_text('One-Time Gift')
.set_icon('gift')
.set_callback(function() {
window.open('https://donate.stripe.com/test_bIY2by6pS1ii99u8wG');
}));
menu.add_menu_item(new beestat.component.menu_item()
.set_text('One-Time Gift')
.set_icon('gift')
.set_callback(function() {
window.open('https://donate.stripe.com/7sIcPp4Gt6APais144');
}));
menu.add_menu_item(new beestat.component.menu_item()
.set_text('Help')
.set_icon('help_circle')
.set_callback(function() {
window.open('https://doc.beestat.io/TODO');
}));
menu.add_menu_item(new beestat.component.menu_item()
.set_text('Support on Patreon')
.set_icon('patreon')
.set_callback(function() {
window.open('https://patreon.com/beestat');
}));
}
};

View File

@ -0,0 +1,58 @@
/**
* Contribute benefits.
*/
beestat.component.card.contribute_benefits = function() {
beestat.component.card.apply(this, arguments);
};
beestat.extend(beestat.component.card.contribute_benefits, beestat.component.card);
/**
* Decorate.
*
* @param {rocket.Elements} parent
*/
beestat.component.card.contribute_benefits.prototype.decorate_contents_ = function(parent) {
const p = document.createElement('p');
p.innerText = 'In addition to satisfaction of supporting a great project, you\'ll get:';
parent.appendChild(p);
const benefit_container = document.createElement('div');
Object.assign(benefit_container.style, {
'background': beestat.style.color.bluegray.dark,
'padding': `${beestat.style.size.gutter}px`
});
parent.appendChild(benefit_container);
const benefits = [
'Early access to new features',
'Private Discord membership',
'More frequent data syncing'
];
benefits.forEach(function(benefit) {
new beestat.component.tile()
.set_shadow(false)
.set_text_color(beestat.style.color.yellow.base)
.set_icon('octagram')
.set_text(benefit)
.style({
'margin-bottom': `${beestat.style.size.gutter}px`
})
.render($(benefit_container));
});
new beestat.component.tile()
.set_shadow(false)
.set_text_color(beestat.style.color.red.base)
.set_icon('heart')
.set_text('My unending gratitude')
.render($(benefit_container));
};
/**
* Get the title of the card.
*
* @return {string} The title.
*/
beestat.component.card.contribute_benefits.prototype.get_title_ = function() {
return 'Benefits';
};

View File

@ -0,0 +1,237 @@
/**
* Contribute benefits.
*/
beestat.component.card.contribute_status = function() {
const self = this;
beestat.dispatcher.addEventListener(
[
'cache.user',
'cache.stripe_event'
],
function() {
self.rerender();
}
);
beestat.component.card.apply(this, arguments);
};
beestat.extend(beestat.component.card.contribute_status, beestat.component.card);
/**
* Decorate.
*
* @param {rocket.Elements} parent
*/
beestat.component.card.contribute_status.prototype.decorate_contents_ = function(parent) {
const p = document.createElement('p');
p.innerText = 'Use this area to view, update, or cancel your support of beestat.';
parent.appendChild(p);
this.decorate_direct_(parent);
this.decorate_patreon_(parent);
};
/**
* Decorate direct giving.
*
* @param {rocket.Elements} parent
*/
beestat.component.card.contribute_status.prototype.decorate_direct_ = function(parent) {
(new beestat.component.title('Direct Giving')).render(parent);
const container = document.createElement('div');
Object.assign(container.style, {
'background': beestat.style.color.bluegray.dark,
'padding': `${beestat.style.size.gutter}px`,
'display': 'flex',
'flex-wrap': 'wrap',
'grid-gap': `${beestat.style.size.gutter}px`,
'align-items': 'center',
'margin-bottom': `${beestat.style.size.gutter}px`
});
parent.appendChild(container);
const status_container = document.createElement('div');
Object.assign(status_container.style, {
'flex-grow': '1'
});
container.appendChild(status_container);
const status_tile = new beestat.component.tile()
.set_shadow(false);
const button_container = document.createElement('div');
container.appendChild(button_container);
if (beestat.user.stripe_is_active() === true) {
status_tile
.set_icon('check')
.set_text_color(beestat.style.color.green.base)
.set_text('Supporter');
} else {
status_tile
.set_icon('close')
.set_text_color(beestat.style.color.gray.base)
.set_text('Not a Supporter');
}
status_tile.render($(status_container));
const manage_tile = new beestat.component.tile()
.set_text('Manage Support')
.set_icon('credit_card_settings')
.set_background_color(beestat.style.color.red.base)
.set_background_hover_color(beestat.style.color.red.light)
.set_text_color('#fff')
.addEventListener('click', function() {
window.open(
window.environment === 'dev'
? 'https://billing.stripe.com/p/login/test_14k8zD2vwb8g6ZO8ww'
: 'https://billing.stripe.com/p/login/7sI5kEetRfHP6g8fYY'
);
});
manage_tile.render($(button_container));
};
/**
* Decorate Patreon giving.
*
* @param {rocket.Elements} parent
*/
beestat.component.card.contribute_status.prototype.decorate_patreon_ = function(parent) {
const self = this;
(new beestat.component.title('Patreon')).render(parent);
const container = document.createElement('div');
Object.assign(container.style, {
'background': beestat.style.color.bluegray.dark,
'padding': `${beestat.style.size.gutter}px`,
'display': 'flex',
'flex-wrap': 'wrap',
'grid-gap': `${beestat.style.size.gutter}px`,
'align-items': 'center'
});
parent.appendChild(container);
const status_container = document.createElement('div');
Object.assign(status_container.style, {
'flex-grow': '1'
});
container.appendChild(status_container);
const status_tile = new beestat.component.tile()
.set_shadow(false);
const button_container = document.createElement('div');
container.appendChild(button_container);
if (beestat.user.patreon_is_connected() === true) {
self.state_.patreon_connecting = false;
const user = beestat.user.get();
if (user.patreon_status.patron_status === 'active_patron') {
status_tile
.set_icon('check')
.set_text_color(beestat.style.color.green.base)
.set_text('Supporter');
} else {
status_tile
.set_icon('close')
.set_text_color(beestat.style.color.gray.base)
.set_text('Not a Supporter');
}
status_tile.render($(status_container));
const tile_group = new beestat.component.tile_group();
const unlink_tile = new beestat.component.tile()
.set_text('Unlink')
.set_icon('link_off')
.set_shadow(false)
.set_text_color(beestat.style.color.gray.base)
.set_text_hover_color(beestat.style.color.red.base)
.addEventListener('click', function() {
this.removeEventListener('click');
new beestat.api()
.add_call(
'user',
'unlink_patreon_account',
{},
'unlink_patreon_account'
)
.add_call('user', 'read_id', {}, 'user')
.set_callback(function(response) {
beestat.cache.set('user', response.user);
})
.send();
});
tile_group.add_tile(unlink_tile);
const manage_tile = new beestat.component.tile()
.set_text('Manage Support')
.set_icon('patreon')
.set_background_color(beestat.style.color.red.base)
.set_background_hover_color(beestat.style.color.red.light)
.set_text_color('#fff')
.addEventListener('click', function() {
window.open('https://patreon.com/beestat');
});
tile_group.add_tile(manage_tile);
tile_group.render($(button_container));
} else {
status_tile
.set_icon('cloud_question')
.set_text_color(beestat.style.color.gray.base)
.set_text('Account Not Connected')
.render($(status_container));
if (this.state_.patreon_connecting === true) {
const connecting_button = new beestat.component.tile()
.set_text('Cancel Connection')
.set_icon('close')
.set_background_color(beestat.style.color.red.base)
.set_background_hover_color(beestat.style.color.red.light)
.set_text_color('#fff')
.addEventListener('click', function() {
self.state_.patreon_connecting = false;
self.rerender();
});
connecting_button.render($(button_container));
const api_call = new beestat.api()
.add_call('user', 'read_id')
.set_callback(function(response) {
beestat.cache.set('user', response);
});
window.setTimeout(function() {
api_call.send();
}, 5000);
} else {
const link_button = new beestat.component.tile()
.set_text('Connect Account')
.set_icon('patreon')
.set_background_color(beestat.style.color.red.base)
.set_background_hover_color(beestat.style.color.red.light)
.set_text_color('#fff')
.addEventListener('click', function() {
window.open('../api/?resource=patreon&method=authorize&arguments={}&api_key=ER9Dz8t05qUdui0cvfWi5GiVVyHP6OB8KPuSisP2');
self.state_.patreon_connecting = true;
self.rerender();
});
link_button.render($(button_container));
}
}
};
/**
* Get the title of the card.
*
* @return {string} The title.
*/
beestat.component.card.contribute_status.prototype.get_title_ = function() {
return 'Contribution Status';
};

View File

@ -0,0 +1,130 @@
/**
* Contribute benefits.
*/
beestat.component.card.merchandise = function() {
beestat.component.card.apply(this, arguments);
};
beestat.extend(beestat.component.card.merchandise, beestat.component.card);
/**
* Decorate.
*
* @param {rocket.Elements} parent
*/
beestat.component.card.merchandise.prototype.decorate_contents_ = function(parent) {
const p = document.createElement('p');
p.innerText = 'Slap a sticker on your furnace and another on a water bottle to support your favorite thermostat analytics platform! Stickers are high quality, made in the USA, and shipped within 1 business day.';
parent.appendChild(p);
const flex_container = document.createElement('div');
Object.assign(flex_container.style, {
'display': 'flex',
'grid-gap': `${beestat.style.size.gutter}px`,
'align-items': 'center',
'flex-wrap': 'wrap'
});
parent.appendChild(flex_container);
const sticker_logo_text_container = document.createElement('div');
Object.assign(sticker_logo_text_container.style, {
'background': beestat.style.color.gray.dark,
'padding': `${beestat.style.size.gutter}px`,
'border-radius': `${beestat.style.size.border_radius}px`,
'text-align': 'right',
'color': beestat.style.color.gray.dark
});
const sticker_logo_text_img = document.createElement('img');
sticker_logo_text_img.setAttribute('src', 'img/merchandise/sticker_logo_text.png');
sticker_logo_text_img.style.height = '48px';
sticker_logo_text_img.style.marginTop = '8px';
sticker_logo_text_img.style.marginBottom = '8px';
sticker_logo_text_container.appendChild(sticker_logo_text_img);
flex_container.appendChild(sticker_logo_text_container);
const sticker_logo_container = document.createElement('div');
Object.assign(sticker_logo_container.style, {
'background': beestat.style.color.gray.dark,
'padding': `${beestat.style.size.gutter}px`,
'border-radius': `${beestat.style.size.border_radius}px`,
'text-align': 'right',
'color': beestat.style.color.gray.dark
});
const sticker_logo_img = document.createElement('img');
sticker_logo_img.setAttribute('src', 'img/merchandise/sticker_logo.png');
sticker_logo_img.style.height = '64px';
sticker_logo_container.appendChild(sticker_logo_img);
flex_container.appendChild(sticker_logo_container);
// Buy button
const tile_container = document.createElement('div');
Object.assign(tile_container.style, {
'flex-grow': '1',
'text-align': 'center'
});
const pay_data = {
'usd': {
'amount': 5,
'payment_link': {
'dev': 'https://buy.stripe.com/test_5kA5nK3dG5yy2L6aF8',
'live': 'https://buy.stripe.com/14k5mXa0N9N13U47sx'
}
},
'cad': {
'amount': 7,
'payment_link': {
'dev': 'https://buy.stripe.com/test_3csg2og0sgdc71m14w',
'live': 'https://buy.stripe.com/aEUg1B1uh6APbmweV0'
}
},
'aud': {
'amount': 7,
'payment_link': {
'dev': 'https://buy.stripe.com/test_aEU2bycOg4uugBW14v',
'live': 'https://buy.stripe.com/6oE6r15Kx2kzgGQbIQ'
}
},
'gbp': {
'amount': 5,
'payment_link': {
'dev': 'https://buy.stripe.com/test_eVaeYkg0s1ii2L66oR',
'live': 'https://buy.stripe.com/28og1B2yl4sH76g6ov'
}
}
};
const amount_formatted = new Intl.NumberFormat('en-US', {
'style': 'currency',
'currency': beestat.setting('units.currency')
}).format(pay_data[beestat.setting('units.currency')].amount);
const pay_tile = new beestat.component.tile()
.set_background_color(beestat.style.color.lightblue.base)
.set_background_hover_color(beestat.style.color.lightblue.light)
.set_text_color('#fff')
.set_size('large')
.set_icon('sticker_emoji')
.set_text([
'Buy Sticker 2-Pack',
amount_formatted
])
.render($(tile_container));
pay_tile.addEventListener('click', function() {
window.open(
pay_data[beestat.setting('units.currency')].payment_link[window.environment] +
'?prefilled_email=' + beestat.user.get().email_address +
'&client_reference_id=' + beestat.user.get().user_id
);
});
flex_container.appendChild(tile_container);
};
/**
* Get the title of the card.
*
* @return {string} The title.
*/
beestat.component.card.merchandise.prototype.get_title_ = function() {
return 'Merch!';
};

View File

@ -80,6 +80,39 @@ beestat.component.card.settings.prototype.decorate_contents_ = function(parent)
distance_radio_group.render(parent);
// Currency
parent.appendChild(
$.createElement('p')
.innerText('Currency')
);
const currency_select = new beestat.component.input.select()
.add_option({
'label': 'USD',
'value': 'usd'
})
.add_option({
'label': 'CAD',
'value': 'cad'
})
.add_option({
'label': 'AUD',
'value': 'aud'
})
.add_option({
'label': 'GBP',
'value': 'gpb'
});
currency_select.render(parent);
currency_select.set_value(beestat.setting('units.currency'));
currency_select.addEventListener('change', function() {
beestat.setting({
'units.currency': currency_select.get_value()
});
});
/**
* Thermosat Summary
*/

View File

@ -89,6 +89,9 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
echo '<script src="/js/component/card/visualize_intro.js"></script>' . PHP_EOL;
echo '<script src="/js/component/card/visualize_affiliate.js"></script>' . PHP_EOL;
echo '<script src="/js/component/card/contribute.js"></script>' . PHP_EOL;
echo '<script src="/js/component/card/contribute_benefits.js"></script>' . PHP_EOL;
echo '<script src="/js/component/card/contribute_status.js"></script>' . PHP_EOL;
echo '<script src="/js/component/card/merchandise.js"></script>' . PHP_EOL;
echo '<script src="/js/component/card/visualize_video.js"></script>' . PHP_EOL;
echo '<script src="/js/component/chart.js"></script>' . PHP_EOL;
echo '<script src="/js/component/chart/runtime_thermostat_summary.js"></script>' . PHP_EOL;

View File

@ -34,11 +34,27 @@ beestat.layer.contribute.prototype.decorate_ = function(parent) {
cards.push([
{
'card': new beestat.component.card.contribute(),
'size': 9
'size': 8
},
{
'card': new beestat.component.card.contribute(),
'size': 3
'card': new beestat.component.card.contribute_benefits(),
'size': 4
}
]);
// History
cards.push([
{
'card': new beestat.component.card.contribute_status(),
'size': 12
}
]);
// Merchandise
cards.push([
{
'card': new beestat.component.card.merchandise(),
'size': 12
}
]);

View File

@ -127,6 +127,13 @@ beestat.layer.load.prototype.decorate_ = function(parent) {
'runtime_thermostat_summary'
);
api.add_call(
'stripe_event',
'read_id',
{},
'stripe_event'
);
api.set_callback(function(response) {
beestat.cache.set('user', response.user);
@ -145,6 +152,7 @@ beestat.layer.load.prototype.decorate_ = function(parent) {
beestat.cache.set('floor_plan', response.floor_plan);
beestat.cache.set('announcement', response.announcement);
beestat.cache.set('runtime_thermostat_summary', response.runtime_thermostat_summary);
beestat.cache.set('stripe_event', response.stripe_event);
// Set the active thermostat_id if this is your first time visiting.
if (beestat.setting('thermostat_id') === undefined) {
@ -203,6 +211,26 @@ beestat.layer.load.prototype.decorate_ = function(parent) {
);
}
// Currency (USD is default)
const currency_map = {
'CAN': 'cad',
'AUS': 'aud',
'GBR': 'gbp'
};
if (
beestat.setting('units.currency') === undefined &&
thermostat.address_id !== null &&
beestat.address.is_valid(thermostat.address_id) === true
) {
const address = beestat.cache.address[thermostat.address_id];
if (currency_map[address.normalized.components.country_iso_3] !== undefined) {
beestat.setting(
'units.currency',
currency_map[address.normalized.components.country_iso_3]
);
}
}
// Rename series if there are multiple stages.
if (beestat.thermostat.get_stages(thermostat.thermostat_id, 'heat') > 1) {
beestat.series.compressor_heat_1.name = 'Heat 1';