Added members feature to labs

no issue

- Added new members settings/toggle to labs
This commit is contained in:
Rish 2019-02-26 10:29:57 +07:00 committed by Rishabh Garg
parent 779a6aba18
commit 57f9663b6e
10 changed files with 302 additions and 13 deletions

View File

@ -1,5 +1,6 @@
import Component from '@ember/component';
import {computed} from '@ember/object';
import {computed, defineProperty} from '@ember/object';
import {readOnly} from '@ember/object/computed';
import {inject as service} from '@ember/service';
const FeatureFlagComponent = Component.extend({
@ -7,14 +8,21 @@ const FeatureFlagComponent = Component.extend({
tagName: 'label',
classNames: 'checkbox',
attributeBindings: ['for'],
_flagValue: null,
attributeBindings: ['for', 'disabled'],
disabled: computed('_disabled', function () {
if (this.get('_disabled')) {
return true;
}
return false;
}),
value: computed('_flagValue', {
get() {
return this.get('_flagValue');
},
set(key, value) {
if (this.get('flag') === 'members' && value === true) {
this.set(`feature.subscribers`, false);
}
return this.set(`feature.${this.get('flag')}`, value);
}
}),
@ -30,12 +38,14 @@ const FeatureFlagComponent = Component.extend({
init() {
this._super(...arguments);
this.set('_flagValue', this.get(`feature.${this.get('flag')}`));
defineProperty(this, '_flagValue', readOnly(`feature.${this.get('flag')}`), function () {
return this.get(`feature.${this.get('flag')}`);
});
}
});
FeatureFlagComponent.reopenClass({
positionalParams: ['flag']
positionalParams: ['flag', '_disabled']
});
export default FeatureFlagComponent;

View File

@ -8,6 +8,7 @@ import {
isRequestEntityTooLargeError,
isUnsupportedMediaTypeError
} from 'ghost-admin/services/ajax';
import {computed} from '@ember/object';
import {isBlank} from '@ember/utils';
import {isArray as isEmberArray} from '@ember/array';
import {run} from '@ember/runloop';
@ -45,6 +46,7 @@ export default Controller.extend({
importErrors: null,
importSuccessful: false,
showDeleteAllModal: false,
showMemberConfig: false,
submitting: false,
uploadButtonText: 'Import',
@ -53,7 +55,6 @@ export default Controller.extend({
jsonMimeType: null,
yamlExtension: null,
yamlMimeType: null,
init() {
this._super(...arguments);
this.importMimeType = IMPORT_MIME_TYPES;
@ -63,6 +64,23 @@ export default Controller.extend({
this.yamlMimeType = YAML_MIME_TYPE;
},
subscriptionSettings: computed('settings.membersSubscriptionSettings', function () {
let subscriptionSettings = this.parseSubscriptionSettings(this.get('settings.membersSubscriptionSettings'));
let stripeProcessor = subscriptionSettings.paymentProcessors.find((proc) => {
return (proc.adapter === 'stripe');
});
let monthlyPlan = stripeProcessor.config.plans.find(plan => plan.interval === 'month');
let yearlyPlan = stripeProcessor.config.plans.find(plan => plan.interval === 'year');
monthlyPlan.dollarAmount = (monthlyPlan.amount / 100);
yearlyPlan.dollarAmount = (yearlyPlan.amount / 100);
stripeProcessor.config.plans = {
monthly: monthlyPlan,
yearly: yearlyPlan
};
subscriptionSettings.stripeConfig = stripeProcessor.config;
return subscriptionSettings;
}),
actions: {
onUpload(file) {
let formData = new FormData();
@ -143,6 +161,10 @@ export default Controller.extend({
this.toggleProperty('showDeleteAllModal');
},
toggleMemberConfig() {
this.toggleProperty('showMemberConfig');
},
/**
* Opens a file selection dialog - Triggered by "Upload x" buttons,
* searches for the hidden file input within the .gh-setting element
@ -156,6 +178,66 @@ export default Controller.extend({
.closest('.gh-setting-action')
.find('input[type="file"]')
.click();
},
setSubscriptionSettings(key, event) {
let subscriptionSettings = this.parseSubscriptionSettings(this.get('settings.membersSubscriptionSettings'));
let stripeProcessor = subscriptionSettings.paymentProcessors.find((proc) => {
return (proc.adapter === 'stripe');
});
let stripeConfig = stripeProcessor.config;
stripeConfig.product = {
name: this.get('settings').get('title')
};
if (key === 'isPaid') {
subscriptionSettings.isPaid = event;
}
if (key === 'secret_token' || key === 'public_token') {
stripeConfig[key] = event.target.value;
}
if (key === 'month' || key === 'year') {
stripeConfig.plans = stripeConfig.plans.map((plan) => {
if (key === plan.interval) {
plan.amount = event.target.value * 100;
}
return plan;
});
}
this.set('settings.membersSubscriptionSettings', JSON.stringify(subscriptionSettings));
}
},
parseSubscriptionSettings(settingsString) {
try {
return JSON.parse(settingsString);
} catch (e) {
return {
isPaid: false,
paymentProcessors: [{
adapter: 'stripe',
config: {
secret_token: '',
public_token: '',
product: {
name: this.get('settings').get('title')
},
plans: [
{
name: 'Monthly',
currency: 'usd',
interval: 'month',
amount: ''
},
{
name: 'Yearly',
currency: 'usd',
interval: 'year',
amount: ''
}
]
}
}]
};
}
},
@ -213,6 +295,14 @@ export default Controller.extend({
}
}).drop(),
saveSettings: task(function* () {
try {
return yield this.get('settings').save();
} catch (error) {
throw error;
}
}).drop(),
redirectUploadResult: task(function* (success) {
this.set('redirectSuccess', success);
this.set('redirectFailure', !success);

View File

@ -29,5 +29,6 @@ export default Model.extend(ValidationEngine, {
defaultValue() {
return {isActive: true};
}
})
}),
membersSubscriptionSettings: attr('string')
});

View File

@ -52,6 +52,7 @@ export default Service.extend({
publicAPI: feature('publicAPI'),
subscribers: feature('subscribers'),
members: feature('members'),
nightShift: feature('nightShift', {user: true, onChange: '_setAdminTheme'}),
_user: null,

View File

@ -27,7 +27,7 @@ export default Service.extend(_ProxyMixin, ValidationEngine, {
_loadSettings() {
if (!this._loadingPromise) {
this._loadingPromise = this.get('store')
.queryRecord('setting', {type: 'blog,theme,private'})
.queryRecord('setting', {type: 'blog,theme,private,members'})
.then((settings) => {
this._loadingPromise = null;
return settings;

View File

@ -55,6 +55,7 @@
@import "layouts/apps.css";
@import "layouts/packages.css";
@import "layouts/subscribers.css";
@import "layouts/labs.css";
:root {

View File

@ -55,6 +55,7 @@
@import "layouts/apps.css";
@import "layouts/packages.css";
@import "layouts/subscribers.css";
@import "layouts/labs.css";
/* ---------------------------✈️----------------------------- */

View File

@ -0,0 +1,70 @@
.gh-labs-price-label input {
padding-right: 96px;
}
.gh-labs-price-label input::-webkit-outer-spin-button, .gh-labs-price-label input::-webkit-inner-spin-button {
/* display: none; <- Crashes Chrome on hover */
-webkit-appearance: none;
margin: 0;
}
.gh-labs-price-label input[type=number] {
-moz-appearance: textfield;
/* Firefox */
}
.gh-labs-price-label::after {
position: absolute;
top: 8px;
right: 12px;
color: var(--midlightgrey);
}
.gh-labs-monthly-price::after {
content: "USD/month";
}
.gh-labs-yearly-price::after {
content: "USD/year";
}
.gh-labs-toggle-wrapper {
padding-top: 6px;
padding-bottom: 6px;
border-radius: 5px;
}
.gh-btn-labs-toggle {
border: none;
display: flex;
align-items: center;
color: var(--blue);
}
.gh-btn-labs-toggle svg {
width: 10px;
height: 10px;
margin-right: 5px;
}
.gh-btn-labs-toggle svg path {
stroke: var(--blue);
}
.gh-labs-members-radio {
cursor: pointer;
margin: 0 8px;
}
.gh-labs-members-radio.active {
background: color-mod(var(--blue) alpha(6%));
border-color: var(--blue);
}
.gh-labs-disabled .gh-setting-content, .gh-labs-disabled .gh-setting-action {
opacity: 0.25;
}
.gh-labs-disabled .for-checkbox label {
cursor: default;
}

View File

@ -1,3 +1,3 @@
<input type="checkbox" checked={{value}} id={{for}} name={{name}} onclick={{action (mut value) value="target.checked"}}>
<input type="checkbox" checked={{value}} disabled={{disabled}} id={{for}} name={{name}} onclick={{action (mut value) value="target.checked"}}>
<span class="input-toggle-component"></span>
{{{yield}}}

View File

@ -102,15 +102,130 @@
<div class="for-checkbox">{{gh-feature-flag "nightShift"}}</div>
</div>
</div>
<div class="gh-setting">
<div class="gh-setting {{if feature.members "gh-labs-disabled"}}">
<div class="gh-setting-content">
<div class="gh-setting-title">Subscribers</div>
<div class="gh-setting-desc">Collect email addresses from your readers, more info in <a href="https://docs.ghost.org/faq/enable-subscribers-feature/">the docs</a></div>
<div class="gh-setting-desc">Collect email addresses from your readers, more info in <a
href="https://docs.ghost.org/faq/enable-subscribers-feature/">the docs</a></div>
</div>
<div class="gh-setting-action">
<div class="for-checkbox">{{gh-feature-flag "subscribers"}}</div>
{{#if feature.members}}
<div class="for-checkbox">{{gh-feature-flag "subscribers" "disabled"}}</div>
{{else}}
<div class="for-checkbox">{{gh-feature-flag "subscribers" }}</div>
{{/if}}
</div>
</div>
{{#if config.enableDeveloperExperiments}}
<div class="gh-setting">
<div class="gh-setting-content">
<div class="gh-setting-title">Members</div>
<div class="gh-setting-desc">Enable free or paid member registration. Restrict content by using <span class="dib blue ba br2 b--blue pa1 pt0 pb0 tag-token--internal">#members</span> hashtag on posts</div>
{{#liquid-if feature.labs.members class="nr20"}}
<button type="button" class="gh-btn gh-btn-labs-toggle" {{action "toggleMemberConfig" ""}}>
{{#if showMemberConfig}}
{{svg-jar "arrow-down-small"}}
{{else}}
{{svg-jar "arrow-right-small"}}
{{/if}}
Configure
</button>
{{#liquid-if showMemberConfig}}
<div class="flex nl2 nr2 mt5">
<div class="gh-publishmenu-radio {{if (eq subscriptionSettings.isPaid false) "active"}} flex-auto w-50 ba br4 b--whitegrey pa5 gh-labs-members-radio"
{{action "setSubscriptionSettings" "isPaid" false on="click"}}>
<div class="gh-publishmenu-radio-button"></div>
<div class="gh-publishmenu-radio-content">
<div class="gh-publishmenu-radio-label f3"><span class="fw6 f7 darkgrey">Free</span></div>
<div class="gh-publishmenu-radio-desc">Access to members-only posts require free user registration</div>
</div>
</div>
<div class="gh-publishmenu-radio {{if (eq subscriptionSettings.isPaid true) "active"}} flex-auto w-50 ba br4 b--whitegrey pa5 ml2 gh-labs-members-radio">
<div class="gh-publishmenu-radio-button" {{action "setSubscriptionSettings" "isPaid" true on="click"}}></div>
<div class="gh-publishmenu-radio-content">
<div {{action "setSubscriptionSettings" "isPaid" true on="click"}}>
<div class="gh-publishmenu-radio-label"><span class="fw6 f7 darkgrey">Paid</span></div>
<div class="gh-publishmenu-radio-desc">Set up paid subscriptions using Stripe</div>
</div>
</div>
</div>
</div>
{{#liquid-if (eq subscriptionSettings.isPaid true)}}
<div class="ba br4 b--whitegrey pa5 mt5 bg-whitegrey-l2">
<div class="flex flex-column">
<div class="flex">
<div class="w-50 mr2">
<label class="fw6">Stripe publishable API key</label>
{{gh-text-input
value=(readonly subscriptionSettings.stripeConfig.public_token)
input=(action "setSubscriptionSettings" "public_token")
class="mt1"
placeholder="pk_..."
}}
</div>
<div class="w-50 ml2">
<label class="fw6">Stripe secret API key</label>
{{gh-text-input
value=(readonly subscriptionSettings.stripeConfig.secret_token)
input=(action "setSubscriptionSettings" "secret_token")
class="mt1"
placeholder="sk_..."
}}
</div>
</div>
<a href="https://stripe.com/docs/keys" target="_blank" class="mt1 self-end fw3">How to find Stripe API keys</a>
</div>
<div class="mt4 flex nb5">
<div class="w-50 mr2">
{{#gh-form-group}}
<label class="fw6">Monthly price</label>
<div class="mt1 relative gh-labs-price-label gh-labs-monthly-price">
{{gh-text-input
value=(readonly subscriptionSettings.stripeConfig.plans.monthly.dollarAmount)
type="number"
input=(action "setSubscriptionSettings" "month")
}}
</div>
{{/gh-form-group}}
</div>
<div class="w-50 ml2">
{{#gh-form-group class="description-container"}}
<label class="fw6">Yearly price</label>
<div class="mt1 relative gh-labs-price-label gh-labs-yearly-price">
{{gh-text-input
value=(readonly subscriptionSettings.stripeConfig.plans.yearly.dollarAmount)
type="number"
input=(action "setSubscriptionSettings" "year")
}}
</div>
{{/gh-form-group}}
</div>
</div>
</div>
{{/liquid-if}}
<div class="mb5 mt5">
{{gh-task-button "Save"
task=saveSettings
successText="Saved"
runningText="Saving"
class="gh-btn gh-btn-blue gh-btn-icon"
}}
</div>
{{/liquid-if}}
{{/liquid-if}}
</div>
<div class="gh-setting-action">
<div class="for-checkbox">{{gh-feature-flag "members"}}</div>
</div>
</div>
{{/if}}
<div class="gh-setting">
{{#gh-uploader
extensions=jsonExtension