FEATURE: Campaigns (#67)

Co-authored-by: Jordan Vidrine <[email protected]>

See https://github.com/discourse/discourse-subscriptions/pull/67 for the full description.
This commit is contained in:
Justin DiRose
2021-06-02 13:15:03 -05:00
committed by GitHub
co-authored by Jordan Vidrine
parent dcec7703f8
commit f596a0f78a
27 changed files with 981 additions and 22 deletions
@@ -0,0 +1,120 @@
import { action } from "@ember/object";
import { ajax } from "discourse/lib/ajax";
import { equal } from "@ember/object/computed";
import { setting } from "discourse/lib/computed";
import Component from "@ember/component";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { inject as service } from "@ember/service";
export default Component.extend({
router: service(),
dismissed: false,
loading: false,
isSidebar: equal(
"siteSettings.discourse_subscriptions_campaign_banner_location",
"Sidebar"
),
subscribers: setting("discourse_subscriptions_campaign_subscribers"),
subscriberGoal: equal(
"siteSettings.discourse_subscriptions_campaign_type",
"Subscribers"
),
currency: setting("discourse_subscriptions_currency"),
goalTarget: setting("discourse_subscriptions_campaign_goal"),
product: setting("discourse_subscriptions_campaign_product"),
showContributors: setting(
"discourse_subscriptions_campaign_show_contributors"
),
classNameBindings: [
"isSidebar:campaign-banner-sidebar",
"shouldShow:campaign-banner",
],
init() {
this._super(...arguments);
this.set("contributors", []);
if (this.showContributors) {
return ajax("/s/contributors", { method: "get" }).then((result) => {
this.setProperties({
contributors: result,
loading: false,
});
});
}
},
didInsertElement() {
this._super(...arguments);
if (this.isSidebar && this.shouldShow) {
document.body.classList.add("subscription-campaign-sidebar");
} else {
document.body.classList.remove("subscription-campaign-sidebar");
}
},
@discourseComputed(
"router.currentRouteName",
"currentUser",
"siteSettings.discourse_subscriptions_campaign_enabled",
"visible"
)
shouldShow(currentRoute, currentUser, enabled, visible) {
// do not show on admin or subscriptions pages
const showOnRoute =
currentRoute !== "discovery.s" &&
!currentRoute.split(".")[0].includes("admin") &&
currentRoute.split(".")[0] !== "s";
return showOnRoute && currentUser && enabled && visible;
},
@observes("dismissed")
_updateBodyClasses() {
if (this.dismissed) {
document.body.classList.remove("subscription-campaign-sidebar");
}
},
@discourseComputed("dismissed")
visible(dismissed) {
const dismissedBannerKey = this.keyValueStore.get(
"dismissed_campaign_banner"
);
const threeMonths = 2628000000 * 3;
const bannerDismissedTime = new Date(dismissedBannerKey);
const now = Date.now();
return (
(!dismissedBannerKey || now - bannerDismissedTime > threeMonths) &&
!dismissed
);
},
@discourseComputed
amountRaised() {
return (
this.siteSettings.discourse_subscriptions_campaign_amount_raised / 100
);
},
@discourseComputed
isGoalMet() {
const currentVolume = this.subscriberGoal
? this.subscribers
: this.amountRaised;
return currentVolume >= this.goalTarget;
},
@action
dismissBanner() {
this.set("dismissed", true);
this.keyValueStore.set({
key: "dismissed_campaign_banner",
value: Date.now(),
});
},
});
@@ -0,0 +1,69 @@
import { action } from "@ember/object";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import Controller from "@ember/controller";
import discourseComputed from "discourse-common/utils/decorators";
import I18n from "I18n";
export default Controller.extend({
loading: false,
@discourseComputed
stripeConfigured() {
return !!this.siteSettings.discourse_subscriptions_public_key;
},
@discourseComputed
campaignEnabled() {
return this.siteSettings.discourse_subscriptions_campaign_enabled;
},
@discourseComputed
campaignProductSet() {
return !!this.siteSettings.discourse_subscriptions_campaign_product;
},
@action
triggerManualRefresh() {
ajax(`/s/admin/refresh`, {
method: "post",
}).then(() => {
bootbox.alert(
I18n.t("discourse_subscriptions.campaign.refresh_page"),
() => {
this.transitionToRoute(
"adminPlugins.discourse-subscriptions.products"
);
}
);
});
},
@action
createOneClickCampaign() {
bootbox.confirm(
I18n.t("discourse_subscriptions.campaign.confirm_creation"),
(result) => {
if (!result) {
return;
}
this.set("loading", true);
ajax(`/s/admin/create-campaign`, {
method: "post",
})
.then(() => {
this.set("loading", false);
bootbox.alert(
I18n.t("discourse_subscriptions.campaign.created"),
() => {
this.send("showSettings");
}
);
})
.catch(popupAjaxError);
}
);
},
});
@@ -14,7 +14,7 @@ export default Controller.extend({
this._super(...arguments);
this.set(
"stripe",
Stripe(Discourse.SiteSettings.discourse_subscriptions_public_key)
Stripe(this.siteSettings.discourse_subscriptions_public_key)
);
const elements = this.get("stripe").elements();
@@ -76,7 +76,7 @@ export default Controller.extend({
plan.type === "recurring"
? "user.billing.subscriptions"
: "user.billing.payments",
Discourse.User.current().username.toLowerCase()
this.currentUser.username.toLowerCase()
);
},
@@ -4,8 +4,6 @@ export default {
map() {
this.route("discourse-subscriptions", function () {
this.route("dashboard");
this.route("products", function () {
this.route("show", { path: "/:product-id" }, function () {
this.route("plans", function () {
@@ -1,3 +1,14 @@
import Route from "@ember/routing/route";
export default Route.extend({});
export default Route.extend({
actions: {
showSettings() {
const controller = this.controllerFor("adminSiteSettings");
this.transitionTo("adminSiteSettingsCategory", "plugins").then(() => {
controller.set("filter", "plugin:discourse-subscriptions campaign");
controller.set("_skipBounce", true);
controller.filterContentNow("plugins");
});
},
},
});
@@ -1,6 +1,6 @@
{{#if model.unconfigured}}
<p>{{i18n 'discourse_subscriptions.admin.unconfigured'}}</p>
<p><a href="https://meta.discourse.org/t/discourse-subscriptions/140818/">Discourse Subscriptions on Meta</a></p>
<p><a href="https://meta.discourse.org/t/discourse-subscriptions/140818/">{{i18n 'discourse_subscriptions.admin.on_meta'}}</a></p>
{{else}}
{{#if model}}
<table class="table discourse-patrons-table">
@@ -1,6 +1,6 @@
{{#if model.unconfigured }}
<p>{{i18n 'discourse_subscriptions.admin.unconfigured'}}</p>
<p><a href="https://meta.discourse.org/t/discourse-subscriptions/140818/">Discourse Subscriptions on Meta</a></p>
<p><a href="https://meta.discourse.org/t/discourse-subscriptions/140818/">{{i18n 'discourse_subscriptions.admin.on_meta'}}</a></p>
{{else}}
<p class="btn-right">
{{#link-to 'adminPlugins.discourse-subscriptions.products.show' 'new' class="btn btn-primary"}}
@@ -1,6 +1,6 @@
{{#if model.unconfigured}}
<p>{{i18n 'discourse_subscriptions.admin.unconfigured'}}</p>
<p><a href="https://meta.discourse.org/t/discourse-subscriptions/140818/">Discourse Subscriptions on Meta</a></p>
<p><a href="https://meta.discourse.org/t/discourse-subscriptions/140818/">{{i18n 'discourse_subscriptions.admin.on_meta'}}</a></p>
{{else}}
{{#load-more selector=".discourse-patrons-table tr" action=(action "loadMore")}}
<table class="table discourse-patrons-table">
@@ -1,15 +1,38 @@
<h2>{{i18n 'discourse_subscriptions.title' site_name=siteSettings.title}}</h2>
<ul class="nav nav-pills">
{{!-- {{nav-item route='adminPlugins.discourse-subscriptions.dashboard' label='discourse_subscriptions.admin.dashboard.title'}} --}}
{{nav-item route='adminPlugins.discourse-subscriptions.products' label='discourse_subscriptions.admin.products.title'}}
{{nav-item route='adminPlugins.discourse-subscriptions.coupons' label='discourse_subscriptions.admin.coupons.title'}}
{{nav-item route='adminPlugins.discourse-subscriptions.subscriptions' label='discourse_subscriptions.admin.subscriptions.title'}}
</ul>
{{#if stripeConfigured}}
<div class="discourse-subscriptions-buttons">
{{#if campaignEnabled}}
{{d-button
label="discourse_subscriptions.campaign.refresh_campaign"
icon="sync-alt"
action=(action 'triggerManualRefresh')
}}
{{else}}
{{#unless campaignProductSet}}
{{d-button
label="discourse_subscriptions.campaign.one_click_campaign"
icon="plus-square"
action=(action 'createOneClickCampaign')
isLoading=loading
}}
{{/unless}}
{{/if}}
</div>
<hr>
<ul class="nav nav-pills">
{{nav-item route='adminPlugins.discourse-subscriptions.products' label='discourse_subscriptions.admin.products.title'}}
{{nav-item route='adminPlugins.discourse-subscriptions.coupons' label='discourse_subscriptions.admin.coupons.title'}}
{{nav-item route='adminPlugins.discourse-subscriptions.subscriptions' label='discourse_subscriptions.admin.subscriptions.title'}}
</ul>
<div id="discourse-subscriptions-admin">
{{outlet}}
</div>
<hr>
<div id="discourse-subscriptions-admin">
{{outlet}}
</div>
{{else}}
<p>{{i18n 'discourse_subscriptions.admin.unconfigured'}}</p>
<p><a href="https://meta.discourse.org/t/discourse-subscriptions/140818/">{{i18n 'discourse_subscriptions.admin.on_meta'}}</a></p>
{{/if}}
@@ -0,0 +1,78 @@
{{#if shouldShow}}
{{d-button
icon="times"
action="dismissBanner"
class="close"
}}
<div class="campaign-banner-info">
<h2 class="campaign-banner-info-header">{{i18n 'discourse_subscriptions.campaign.title'}}</h2>
<p class="campaign-banner-info-description">{{i18n 'discourse_subscriptions.campaign.body'}}</p>
{{#if product}}
{{#link-to "s.show" product disabled=product.subscribed class="btn btn-primary campaign-banner-info-button"}}
{{d-icon "far-heart"}} {{d-icon "heart" class="hover-heart"}} {{i18n 'discourse_subscriptions.campaign.button'}}
{{/link-to}}
{{else}}
{{#link-to "s" class="btn btn-primary campaign-banner-info-button"}}
{{d-icon "far-heart"}} {{d-icon "heart" class="hover-heart"}} {{i18n 'discourse_subscriptions.campaign.button'}}
{{/link-to}}
{{/if}}
</div>
<div class="campaign-banner-progress">
{{#if isGoalMet}}
<p class="campaign-banner-progress-success">{{i18n 'discourse_subscriptions.campaign.goal'}}!</p>
{{#if subscriberGoal}}
<p class="campaign-banner-progress-description">
{{i18n 'discourse_subscriptions.campaign.goal_comparison' current=subscribers goal=goalTarget}}
{{i18n 'discourse_subscriptions.campaign.subscribers'}}
</p>
{{else}}
<p class="campaign-banner-progress-description">
{{html-safe (i18n 'discourse_subscriptions.campaign.goal_comparison' current=(format-currency currency amountRaised) goal=(format-currency currency goalTarget))}}
{{i18n 'discourse_subscriptions.campaign.raised'}}
</p>
{{#if showContributors}}
{{#conditional-loading-spinner condition=loading size="small"}}
<div class="campaign-banner-progress-users">
<p class="campaign-banner-progress-users-title">
<strong>{{i18n 'discourse_subscriptions.campaign.recent_contributors'}}</strong>
</p>
<div class="campaign-banner-progress-users-avatars">
{{#each contributors as |contributor|}}
{{avatar contributor avatarTemplatePath="avatar_template" usernamePath="username" namePath="name" imageSize="small"}}
{{/each}}
</div>
</div>
{{/conditional-loading-spinner}}
{{/if}}
{{/if}}
{{else}}
{{#if subscriberGoal}}
<progress class="campaign-banner-progress-bar" value="{{subscribers}}" max="{{siteSettings.discourse_subscriptions_campaign_goal}}"/>
<p class="campaign-banner-progress-description">
{{i18n 'discourse_subscriptions.campaign.goal_comparison' current=subscribers goal=goalTarget}}
{{i18n 'discourse_subscriptions.campaign.subscribers'}}
</p>
{{else}}
<progress class="campaign-banner-progress-bar" value="{{amountRaised}}" max="{{siteSettings.discourse_subscriptions_campaign_goal}}"/>
<p class="campaign-banner-progress-description">
{{html-safe (i18n 'discourse_subscriptions.campaign.goal_comparison' current=(format-currency currency amountRaised) goal=(format-currency currency goalTarget))}}
{{i18n 'discourse_subscriptions.campaign.raised'}}
</p>
{{/if}}
{{#if showContributors}}
{{#conditional-loading-spinner condition=loading size="small"}}
<div class="campaign-banner-progress-users">
<p class="campaign-banner-progress-users-title">
<strong>{{i18n 'discourse_subscriptions.campaign.recent_contributors'}}</strong>
</p>
<div class="campaign-banner-progress-users-avatars">
{{#each contributors as |contributor|}}
{{avatar contributor avatarTemplatePath="avatar_template" usernamePath="username" namePath="name" imageSize="small"}}
{{/each}}
</div>
</div>
{{/conditional-loading-spinner}}
{{/if}}
{{/if}}
</div>
{{/if}}
@@ -0,0 +1,3 @@
{{#unless (eq siteSettings.discourse_subscriptions_campaign_banner_location "Sidebar")}}
{{campaign-banner}}
{{/unless}}
@@ -0,0 +1,3 @@
{{#if (eq siteSettings.discourse_subscriptions_campaign_banner_location "Sidebar")}}
{{campaign-banner}}
{{/if}}
@@ -44,7 +44,11 @@
label="discourse_subscriptions.plans.payment_button"
}}
{{/if}}
{{else}}
<h2>{{i18n 'discourse_subscriptions.subscribe.already_purchased'}}</h2>
{{#link-to "user.billing.subscriptions" currentUser.username class="btn btn-primary"}}
{{i18n 'discourse_subscriptions.subscribe.go_to_billing'}}
{{/link-to}}
{{/if}}
</div>
</div>