mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-01 13:54:35 +03:00
6339770a67
no issue - passed correct action for opening stripe connect modal through to portal settings modal - updated `<GhTaskButton>` to accept a `@unlinkedTask=true/false` property - ember-concurrency will throw warnings about unsafe task cancellation if the initiator of a task is destroyed due to the actions of a task. Eg. the stripe connect button being replaced with the plan checkboxes because stripe connect details are added to settings - to avoid warnings ember-concurrency expects the task initiation to be marked as "unlinked" so that the task is allowed to continue even though the initiator is destroyed - updated `<GhSiteIframe>` to force a refresh when the `@guid` property changes - we want the portal preview to fully reload so that it can fetch server data and see that stripe is connected - updated portal settings modal to initiate a refresh when switching from "connect to stripe" to the plans checkboxes that happens automatically after a successful stripe connection
310 lines
10 KiB
JavaScript
310 lines
10 KiB
JavaScript
import $ from 'jquery';
|
|
import ModalComponent from 'ghost-admin/components/modal-base';
|
|
import copyTextToClipboard from 'ghost-admin/utils/copy-text-to-clipboard';
|
|
import {action, computed} from '@ember/object';
|
|
import {htmlSafe} from '@ember/template';
|
|
import {inject as service} from '@ember/service';
|
|
import {task, timeout} from 'ember-concurrency';
|
|
const ICON_EXTENSIONS = ['gif', 'jpg', 'jpeg', 'png', 'svg'];
|
|
|
|
export default ModalComponent.extend({
|
|
config: service(),
|
|
membersUtils: service(),
|
|
settings: service(),
|
|
store: service(),
|
|
|
|
page: 'signup',
|
|
iconExtensions: null,
|
|
isShowModalLink: true,
|
|
customIcon: null,
|
|
showLinksPage: false,
|
|
showLeaveSettingsModal: false,
|
|
freeSignupRedirect: undefined,
|
|
paidSignupRedirect: undefined,
|
|
prices: null,
|
|
isPreloading: true,
|
|
portalPreviewGuid: 'modal-portal-settings',
|
|
|
|
confirm() {},
|
|
|
|
filteredPrices: computed('prices', 'settings.{portalPlans.[],membersMonthlyPriceId,membersYearlyPriceId}', function () {
|
|
const monthlyPriceId = this.settings.get('membersMonthlyPriceId');
|
|
const yearlyPriceId = this.settings.get('membersYearlyPriceId');
|
|
const portalPlans = this.settings.get('portalPlans');
|
|
const prices = this.prices || [];
|
|
return prices.filter((d) => {
|
|
return [monthlyPriceId, yearlyPriceId].includes(d.id);
|
|
}).filter((d) => {
|
|
return d.amount !== 0 && d.type === 'recurring';
|
|
}).map((price) => {
|
|
return {
|
|
...price,
|
|
checked: !!portalPlans.find(d => d === price.id)
|
|
};
|
|
});
|
|
}),
|
|
|
|
hasPaidPriceChecked: computed('prices', 'settings.portalPlans.[]', function () {
|
|
const portalPlans = this.settings.get('portalPlans');
|
|
const prices = this.prices || [];
|
|
return prices.filter((d) => {
|
|
return d.amount !== 0 && d.type === 'recurring';
|
|
}).some((price) => {
|
|
return !!portalPlans.find(d => d === price.id);
|
|
});
|
|
}),
|
|
|
|
backgroundStyle: computed('settings.accentColor', function () {
|
|
let color = this.settings.get('accentColor') || '#ffffff';
|
|
return htmlSafe(`background-color: ${color}`);
|
|
}),
|
|
|
|
showModalLinkOrAttribute: computed('isShowModalLink', function () {
|
|
if (this.isShowModalLink) {
|
|
return `#/portal`;
|
|
}
|
|
return `data-portal`;
|
|
}),
|
|
|
|
portalPreviewUrl: computed('page', 'membersUtils.{isFreeChecked,isMonthlyChecked,isYearlyChecked}', 'settings.{portalName,portalButton,portalButtonIcon,portalButtonSignupText,portalButtonStyle,accentColor,portalPlans.[]}', function () {
|
|
const options = this.getProperties(['page']);
|
|
return this.membersUtils.getPortalPreviewUrl(options);
|
|
}),
|
|
|
|
showIconSetting: computed('selectedButtonStyle', function () {
|
|
const selectedButtonStyle = this.get('selectedButtonStyle.name') || '';
|
|
return selectedButtonStyle.includes('icon');
|
|
}),
|
|
|
|
showButtonTextSetting: computed('selectedButtonStyle', function () {
|
|
const selectedButtonStyle = this.get('selectedButtonStyle.name') || '';
|
|
return selectedButtonStyle.includes('text');
|
|
}),
|
|
|
|
selectedButtonStyle: computed('settings.portalButtonStyle', function () {
|
|
return this.buttonStyleOptions.find((buttonStyle) => {
|
|
return (buttonStyle.name === this.settings.get('portalButtonStyle'));
|
|
});
|
|
}),
|
|
|
|
init() {
|
|
this._super(...arguments);
|
|
this.buttonStyleOptions = [
|
|
{name: 'icon-and-text', label: 'Icon and text'},
|
|
{name: 'icon-only', label: 'Icon only'},
|
|
{name: 'text-only', label: 'Text only'}
|
|
];
|
|
this.iconExtensions = ICON_EXTENSIONS;
|
|
},
|
|
|
|
didInsertElement() {
|
|
this._super(...arguments);
|
|
this.settings.get('errors').clear();
|
|
},
|
|
|
|
actions: {
|
|
toggleFreePlan(isChecked) {
|
|
this.updateAllowedPlan('free', isChecked);
|
|
},
|
|
toggleMonthlyPlan(isChecked) {
|
|
this.updateAllowedPlan('monthly', isChecked);
|
|
},
|
|
toggleYearlyPlan(isChecked) {
|
|
this.updateAllowedPlan('yearly', isChecked);
|
|
},
|
|
togglePlan(priceId, event) {
|
|
this.updateAllowedPlan(priceId, event.target.checked);
|
|
},
|
|
togglePortalButton(showButton) {
|
|
this.settings.set('portalButton', showButton);
|
|
},
|
|
|
|
togglePortalName(showSignupName) {
|
|
this.settings.set('portalName', showSignupName);
|
|
},
|
|
|
|
setPaidSignupRedirect(url) {
|
|
this.set('paidSignupRedirect', url);
|
|
},
|
|
|
|
setFreeSignupRedirect(url) {
|
|
this.set('freeSignupRedirect', url);
|
|
},
|
|
|
|
confirm() {
|
|
return this.saveTask.perform();
|
|
},
|
|
|
|
isPlanSelected(plan) {
|
|
const allowedPlans = this.settings.get('portalPlans');
|
|
return allowedPlans.includes(plan);
|
|
},
|
|
|
|
switchPreviewPage(page) {
|
|
if (page === 'links') {
|
|
this.set('showLinksPage', true);
|
|
this.set('page', '');
|
|
} else {
|
|
this.set('showLinksPage', false);
|
|
this.set('page', page);
|
|
}
|
|
},
|
|
|
|
switchToSignupPage() {
|
|
if (this.showLinksPage) {
|
|
this.set('showLinksPage', false);
|
|
this.set('page', 'signup');
|
|
}
|
|
},
|
|
|
|
setButtonStyle(buttonStyle) {
|
|
this.settings.set('portalButtonStyle', buttonStyle.name);
|
|
},
|
|
setSignupButtonText(event) {
|
|
this.settings.set('portalButtonSignupText', event.target.value);
|
|
},
|
|
/**
|
|
* Fired after an image upload completes
|
|
* @param {string} property - Property name to be set on `this.settings`
|
|
* @param {UploadResult[]} results - Array of UploadResult objects
|
|
* @return {string} The URL that was set on `this.settings.property`
|
|
*/
|
|
imageUploaded(property, results) {
|
|
if (results[0]) {
|
|
this.set('customIcon', results[0].url);
|
|
this.settings.set('portalButtonIcon', results[0].url);
|
|
}
|
|
},
|
|
/**
|
|
* Opens a file selection dialog - Triggered by "Upload Image" buttons,
|
|
* searches for the hidden file input within the .gh-setting element
|
|
* containing the clicked button then simulates a click
|
|
* @param {MouseEvent} event - MouseEvent fired by the button click
|
|
*/
|
|
triggerFileDialog(event) {
|
|
// simulate click to open file dialog
|
|
// using jQuery because IE11 doesn't support MouseEvent
|
|
$(event.target)
|
|
.closest('.gh-setting-action')
|
|
.find('input[type="file"]')
|
|
.click();
|
|
},
|
|
|
|
deleteCustomIcon() {
|
|
this.set('customIcon', null);
|
|
this.settings.set('portalButtonIcon', this.membersUtils.defaultIconKeys[0]);
|
|
},
|
|
|
|
selectDefaultIcon(icon) {
|
|
this.settings.set('portalButtonIcon', icon);
|
|
},
|
|
|
|
closeLeaveSettingsModal() {
|
|
this.set('showLeaveSettingsModal', false);
|
|
},
|
|
|
|
openStripeSettings() {
|
|
this.isWaitingForStripeConnection = true;
|
|
this.model.openStripeSettings();
|
|
},
|
|
|
|
leaveSettings() {
|
|
this.closeModal();
|
|
},
|
|
|
|
validateFreeSignupRedirect() {
|
|
return this._validateSignupRedirect(this.get('freeSignupRedirect'), 'membersFreeSignupRedirect');
|
|
},
|
|
|
|
validatePaidSignupRedirect() {
|
|
return this._validateSignupRedirect(this.get('paidSignupRedirect'), 'membersPaidSignupRedirect');
|
|
}
|
|
},
|
|
|
|
updateAllowedPlan(plan, isChecked) {
|
|
const portalPlans = this.settings.get('portalPlans') || [];
|
|
const allowedPlans = [...portalPlans];
|
|
|
|
if (!isChecked) {
|
|
this.settings.set('portalPlans', allowedPlans.filter(p => p !== plan));
|
|
} else {
|
|
allowedPlans.push(plan);
|
|
this.settings.set('portalPlans', allowedPlans);
|
|
}
|
|
},
|
|
|
|
_validateSignupRedirect(url, type) {
|
|
let errMessage = `Please enter a valid URL`;
|
|
this.settings.get('errors').remove(type);
|
|
this.settings.get('hasValidated').removeObject(type);
|
|
|
|
if (url === null) {
|
|
this.settings.get('errors').add(type, errMessage);
|
|
this.settings.get('hasValidated').pushObject(type);
|
|
return false;
|
|
}
|
|
|
|
if (url === undefined) {
|
|
// Not initialised
|
|
return;
|
|
}
|
|
|
|
if (url.href.startsWith(this.siteUrl)) {
|
|
const path = url.href.replace(this.siteUrl, '');
|
|
this.settings.set(type, path);
|
|
} else {
|
|
this.settings.set(type, url.href);
|
|
}
|
|
},
|
|
|
|
finishPreloading: action(async function () {
|
|
if (this.model.preloadTask?.isRunning) {
|
|
await this.model.preloadTask;
|
|
}
|
|
|
|
const portalButtonIcon = this.settings.get('portalButtonIcon') || '';
|
|
if (portalButtonIcon && !this.membersUtils.defaultIconKeys.includes(portalButtonIcon)) {
|
|
this.set('customIcon', this.settings.get('portalButtonIcon'));
|
|
}
|
|
|
|
this.getAvailablePrices.perform();
|
|
this.siteUrl = this.config.get('blogUrl');
|
|
|
|
this.set('isPreloading', false);
|
|
}),
|
|
|
|
refreshAfterStripeConnected: action(async function () {
|
|
if (this.isWaitingForStripeConnection) {
|
|
await this.finishPreloading();
|
|
this.notifyPropertyChange('page'); // force preview url to recompute
|
|
this.set('portalPreviewGuid', Date.now().valueOf()); // force preview re-render
|
|
this.isWaitingForStripeConnection = false;
|
|
}
|
|
}),
|
|
|
|
copyLinkOrAttribute: task(function* () {
|
|
copyTextToClipboard(this.showModalLinkOrAttribute);
|
|
yield timeout(this.isTesting ? 50 : 3000);
|
|
}),
|
|
|
|
saveTask: task(function* () {
|
|
this.send('validateFreeSignupRedirect');
|
|
this.send('validatePaidSignupRedirect');
|
|
if (this.settings.get('errors').length !== 0) {
|
|
return;
|
|
}
|
|
yield this.settings.save();
|
|
this.closeModal();
|
|
}).drop(),
|
|
|
|
getAvailablePrices: task(function* () {
|
|
const products = yield this.store.query('product', {include: 'stripe_prices'});
|
|
const product = products.firstObject;
|
|
const prices = product.get('stripePrices');
|
|
const activePrices = prices.filter((d) => {
|
|
return !!d.active;
|
|
});
|
|
this.set('prices', activePrices);
|
|
}).drop()
|
|
});
|