mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-24 03:14:03 +03:00
Added portal default plan setting (#19238)
fixes PROD-61 This adds a new default plan setting. It defaults to yearly, which is the current default selected interval in Portal. Behind the new portal improvements feature flag, the default plan can be changed. It will also change automatically if the available intervals are changed. This PR also wires up passing the new setting to the Portal preview.
This commit is contained in:
parent
a38aef7522
commit
3f6ea04c43
@ -176,6 +176,10 @@
|
||||
"key": "portal_plans",
|
||||
"value": "[\"monthly\",\"yearly\",\"free\"]"
|
||||
},
|
||||
{
|
||||
"key": "portal_default_plan",
|
||||
"value": "yearly"
|
||||
},
|
||||
{
|
||||
"key": "portal_products",
|
||||
"value": "[]"
|
||||
|
@ -14,9 +14,9 @@ const SignupOptions: React.FC<{
|
||||
setError: (key: string, error: string | undefined) => void
|
||||
}> = ({localSettings, updateSetting, localTiers, updateTier, errors, setError}) => {
|
||||
const {config} = useGlobalData();
|
||||
|
||||
const [membersSignupAccess, portalName, portalSignupTermsHtml, portalSignupCheckboxRequired, portalPlansJson] = getSettingValues(
|
||||
localSettings, ['members_signup_access', 'portal_name', 'portal_signup_terms_html', 'portal_signup_checkbox_required', 'portal_plans']
|
||||
const hasPortalImprovements = useFeatureFlag('portalImprovements');
|
||||
const [membersSignupAccess, portalName, portalSignupTermsHtml, portalSignupCheckboxRequired, portalPlansJson, portalDefaultPlan] = getSettingValues(
|
||||
localSettings, ['members_signup_access', 'portal_name', 'portal_signup_terms_html', 'portal_signup_checkbox_required', 'portal_plans', 'portal_default_plan']
|
||||
);
|
||||
const portalPlans = JSON.parse(portalPlansJson?.toString() || '[]') as string[];
|
||||
|
||||
@ -50,6 +50,20 @@ const SignupOptions: React.FC<{
|
||||
}
|
||||
|
||||
updateSetting('portal_plans', JSON.stringify(portalPlans));
|
||||
|
||||
// Check default plan is included
|
||||
if (hasPortalImprovements) {
|
||||
if (portalDefaultPlan === 'yearly') {
|
||||
if (!portalPlans.includes('yearly') && portalPlans.includes('monthly')) {
|
||||
updateSetting('portal_default_plan', 'monthly');
|
||||
}
|
||||
} else if (portalDefaultPlan === 'monthly') {
|
||||
if (!portalPlans.includes('monthly')) {
|
||||
// If both yearly and monthly are missing from plans, still set it to yearly
|
||||
updateSetting('portal_default_plan', 'yearly');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// This is a bit unclear in current admin, maybe we should add a message if the settings are disabled?
|
||||
@ -86,13 +100,10 @@ const SignupOptions: React.FC<{
|
||||
}
|
||||
|
||||
const paidActiveTiersResult = getPaidActiveTiers(localTiers) || [];
|
||||
const hasPortalImprovements = useFeatureFlag('portalImprovements');
|
||||
|
||||
// TODO: Hook up with actual values and then delete this
|
||||
const selectOptions: SelectOption[] = [
|
||||
{value: 'default-yearly', label: 'Yearly'},
|
||||
{value: 'default-monthly', label: 'Monthly'}
|
||||
|
||||
const defaultPlanOptions: SelectOption[] = [
|
||||
{value: 'yearly', label: 'Yearly'},
|
||||
{value: 'monthly', label: 'Monthly'}
|
||||
];
|
||||
|
||||
if (paidActiveTiersResult.length > 0 && isStripeEnabled) {
|
||||
@ -145,9 +156,17 @@ const SignupOptions: React.FC<{
|
||||
]}
|
||||
title='Prices available at signup'
|
||||
/>
|
||||
{hasPortalImprovements && <Select disabled={(portalPlans.includes('yearly') && portalPlans.includes('monthly')) ? false : true} options={selectOptions} selectedOption={selectOptions.find(option => option.value === (portalPlans.includes('yearly') ? 'default-yearly' : 'default-monthly'))} title='Price shown by default' onSelect={(value) => {
|
||||
alert(value);
|
||||
}} />}
|
||||
{hasPortalImprovements &&
|
||||
<Select
|
||||
disabled={(portalPlans.includes('yearly') && portalPlans.includes('monthly')) ? false : true}
|
||||
options={defaultPlanOptions}
|
||||
selectedOption={defaultPlanOptions.find(option => option.value === portalDefaultPlan)}
|
||||
title='Price shown by default'
|
||||
onSelect={(option) => {
|
||||
updateSetting('portal_default_plan', option?.value ?? 'yearly');
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
)}
|
||||
|
||||
|
@ -39,7 +39,12 @@ export const getPortalPreviewUrl = ({settings, config, tiers, siteData, selected
|
||||
settingsParam.append('allowSelfSignup', allowSelfSignup ? 'true' : 'false');
|
||||
settingsParam.append('signupTermsHtml', getSettingValue(settings, 'portal_signup_terms_html') || '');
|
||||
settingsParam.append('signupCheckboxRequired', getSettingValue(settings, 'portal_signup_checkbox_required') ? 'true' : 'false');
|
||||
settingsParam.append('portalProducts', encodeURIComponent(portalTiers.join(','))); // assuming that it might be more than 1
|
||||
settingsParam.append('portalProducts', portalTiers.join(',')); // assuming that it might be more than 1
|
||||
|
||||
const portalDefaultPlan = getSettingValue<string>(settings, 'portal_default_plan');
|
||||
if (portalDefaultPlan) {
|
||||
settingsParam.append('portalDefaultPlan', portalDefaultPlan);
|
||||
}
|
||||
|
||||
if (portalPlans && portalPlans.length) {
|
||||
settingsParam.append('portalPrices', encodeURIComponent(portalPlans.join(',')));
|
||||
|
@ -311,7 +311,10 @@ export default class App extends React.Component {
|
||||
// Handle the query params key/value pairs
|
||||
for (let pair of qsParams.entries()) {
|
||||
const key = pair[0];
|
||||
|
||||
// Note: this needs to be cleaned up, there is no reason why we need to double encode/decode
|
||||
const value = decodeURIComponent(pair[1]);
|
||||
|
||||
if (key === 'button') {
|
||||
data.site.portal_button = JSON.parse(value);
|
||||
} else if (key === 'name') {
|
||||
@ -357,6 +360,8 @@ export default class App extends React.Component {
|
||||
data.site.allow_self_signup = JSON.parse(value);
|
||||
} else if (key === 'membersSignupAccess' && value) {
|
||||
data.site.members_signup_access = value;
|
||||
} else if (key === 'portalDefaultPlan' && value) {
|
||||
data.site.portal_default_plan = value;
|
||||
}
|
||||
}
|
||||
data.site.portal_plans = allowedPlans;
|
||||
@ -389,6 +394,7 @@ export default class App extends React.Component {
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
@ -894,7 +894,7 @@ function getSelectedPrice({products, selectedProduct, selectedInterval}) {
|
||||
return selectedPrice;
|
||||
}
|
||||
|
||||
function getActiveInterval({portalPlans, selectedInterval = 'year'}) {
|
||||
function getActiveInterval({portalPlans, portalDefaultPlan, selectedInterval}) {
|
||||
if (selectedInterval === 'month' && portalPlans.includes('monthly')) {
|
||||
return 'month';
|
||||
}
|
||||
@ -903,26 +903,32 @@ function getActiveInterval({portalPlans, selectedInterval = 'year'}) {
|
||||
return 'year';
|
||||
}
|
||||
|
||||
if (portalPlans.includes('monthly')) {
|
||||
return 'month';
|
||||
if (portalDefaultPlan) {
|
||||
if (portalDefaultPlan === 'monthly' && portalPlans.includes('monthly')) {
|
||||
return 'month';
|
||||
}
|
||||
}
|
||||
|
||||
if (portalPlans.includes('yearly')) {
|
||||
return 'year';
|
||||
}
|
||||
|
||||
if (portalPlans.includes('monthly')) {
|
||||
return 'month';
|
||||
}
|
||||
}
|
||||
|
||||
function ProductsSection({onPlanSelect, products, type = null, handleChooseSignup, errors}) {
|
||||
const {site, member, t} = useContext(AppContext);
|
||||
const {portal_plans: portalPlans} = site;
|
||||
const defaultInterval = getActiveInterval({portalPlans});
|
||||
|
||||
const {portal_plans: portalPlans, portal_default_plan: portalDefaultPlan} = site;
|
||||
const defaultProductId = products.length > 0 ? products[0].id : 'free';
|
||||
const [selectedInterval, setSelectedInterval] = useState(defaultInterval);
|
||||
|
||||
// Note: by default we set it to null, so that it changes reactively in the preview version of Portal
|
||||
const [selectedInterval, setSelectedInterval] = useState(null);
|
||||
const [selectedProduct, setSelectedProduct] = useState(defaultProductId);
|
||||
|
||||
const selectedPrice = getSelectedPrice({products, selectedInterval, selectedProduct});
|
||||
const activeInterval = getActiveInterval({portalPlans, selectedInterval});
|
||||
const activeInterval = getActiveInterval({portalPlans, portalDefaultPlan, selectedInterval});
|
||||
|
||||
const isComplimentary = isComplimentaryMember({member});
|
||||
|
||||
|
@ -77,6 +77,7 @@ export default [
|
||||
setting('portal', 'portal_name', true),
|
||||
setting('portal', 'portal_button', true),
|
||||
setting('portal', 'portal_plans', JSON.stringify(['free'])),
|
||||
setting('portal', 'portal_default_plan', 'yearly'),
|
||||
setting('portal', 'portal_products', JSON.stringify([])),
|
||||
setting('portal', 'portal_button_style', 'icon-and-text'),
|
||||
setting('portal', 'portal_button_icon', null),
|
||||
|
@ -38,6 +38,7 @@ const EDITABLE_SETTINGS = [
|
||||
'portal_name',
|
||||
'portal_button',
|
||||
'portal_plans',
|
||||
'portal_default_plan',
|
||||
'portal_button_style',
|
||||
'firstpromoter',
|
||||
'firstpromoter_id',
|
||||
|
@ -0,0 +1,8 @@
|
||||
const {addSetting} = require('../../utils');
|
||||
|
||||
module.exports = addSetting({
|
||||
key: 'portal_default_plan',
|
||||
value: 'yearly',
|
||||
type: 'string',
|
||||
group: 'portal'
|
||||
});
|
@ -330,6 +330,14 @@
|
||||
"defaultValue": "[\"free\"]",
|
||||
"type": "array"
|
||||
},
|
||||
"portal_default_plan": {
|
||||
"defaultValue": "yearly",
|
||||
"validations": {
|
||||
"isEmpty": false,
|
||||
"isIn": [["yearly", "monthly"]]
|
||||
},
|
||||
"type": "string"
|
||||
},
|
||||
"portal_products": {
|
||||
"defaultValue": "[]",
|
||||
"type": "array"
|
||||
|
@ -38,6 +38,7 @@ module.exports = {
|
||||
portal_signup_terms_html: 'portal_signup_terms_html',
|
||||
portal_signup_checkbox_required: 'portal_signup_checkbox_required',
|
||||
portal_plans: 'portal_plans',
|
||||
portal_default_plan: 'portal_default_plan',
|
||||
portal_name: 'portal_name',
|
||||
portal_button: 'portal_button',
|
||||
comments_enabled: 'comments_enabled',
|
||||
|
@ -180,6 +180,10 @@ Object {
|
||||
"key": "portal_plans",
|
||||
"value": "[\\"free\\"]",
|
||||
},
|
||||
Object {
|
||||
"key": "portal_default_plan",
|
||||
"value": "yearly",
|
||||
},
|
||||
Object {
|
||||
"key": "portal_products",
|
||||
"value": "[]",
|
||||
@ -602,6 +606,10 @@ Object {
|
||||
"key": "portal_plans",
|
||||
"value": "[\\"free\\"]",
|
||||
},
|
||||
Object {
|
||||
"key": "portal_default_plan",
|
||||
"value": "yearly",
|
||||
},
|
||||
Object {
|
||||
"key": "portal_products",
|
||||
"value": "[]",
|
||||
@ -967,6 +975,10 @@ Object {
|
||||
"key": "portal_plans",
|
||||
"value": "[\\"free\\"]",
|
||||
},
|
||||
Object {
|
||||
"key": "portal_default_plan",
|
||||
"value": "yearly",
|
||||
},
|
||||
Object {
|
||||
"key": "portal_products",
|
||||
"value": "[]",
|
||||
@ -1143,7 +1155,7 @@ exports[`Settings API Edit Can edit a setting 2: [headers] 1`] = `
|
||||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "4487",
|
||||
"content-length": "4534",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -1333,6 +1345,10 @@ Object {
|
||||
"key": "portal_plans",
|
||||
"value": "[\\"free\\"]",
|
||||
},
|
||||
Object {
|
||||
"key": "portal_default_plan",
|
||||
"value": "yearly",
|
||||
},
|
||||
Object {
|
||||
"key": "portal_products",
|
||||
"value": "[]",
|
||||
@ -1698,6 +1714,10 @@ Object {
|
||||
"key": "portal_plans",
|
||||
"value": "[\\"free\\"]",
|
||||
},
|
||||
Object {
|
||||
"key": "portal_default_plan",
|
||||
"value": "yearly",
|
||||
},
|
||||
Object {
|
||||
"key": "portal_products",
|
||||
"value": "[]",
|
||||
@ -2526,6 +2546,10 @@ Object {
|
||||
"key": "portal_plans",
|
||||
"value": "[\\"free\\"]",
|
||||
},
|
||||
Object {
|
||||
"key": "portal_default_plan",
|
||||
"value": "yearly",
|
||||
},
|
||||
Object {
|
||||
"key": "portal_products",
|
||||
"value": "[]",
|
||||
@ -2892,6 +2916,10 @@ Object {
|
||||
"key": "portal_plans",
|
||||
"value": "[\\"free\\"]",
|
||||
},
|
||||
Object {
|
||||
"key": "portal_default_plan",
|
||||
"value": "yearly",
|
||||
},
|
||||
Object {
|
||||
"key": "portal_products",
|
||||
"value": "[]",
|
||||
@ -3258,6 +3286,10 @@ Object {
|
||||
"key": "portal_plans",
|
||||
"value": "[\\"free\\"]",
|
||||
},
|
||||
Object {
|
||||
"key": "portal_default_plan",
|
||||
"value": "yearly",
|
||||
},
|
||||
Object {
|
||||
"key": "portal_products",
|
||||
"value": "[]",
|
||||
@ -3628,6 +3660,10 @@ Object {
|
||||
"key": "portal_plans",
|
||||
"value": "[\\"free\\"]",
|
||||
},
|
||||
Object {
|
||||
"key": "portal_default_plan",
|
||||
"value": "yearly",
|
||||
},
|
||||
Object {
|
||||
"key": "portal_products",
|
||||
"value": "[]",
|
||||
@ -3993,6 +4029,10 @@ Object {
|
||||
"key": "portal_plans",
|
||||
"value": "[\\"free\\"]",
|
||||
},
|
||||
Object {
|
||||
"key": "portal_default_plan",
|
||||
"value": "yearly",
|
||||
},
|
||||
Object {
|
||||
"key": "portal_products",
|
||||
"value": "[]",
|
||||
@ -4363,6 +4403,10 @@ Object {
|
||||
"key": "portal_plans",
|
||||
"value": "[\\"free\\"]",
|
||||
},
|
||||
Object {
|
||||
"key": "portal_default_plan",
|
||||
"value": "yearly",
|
||||
},
|
||||
Object {
|
||||
"key": "portal_products",
|
||||
"value": "[]",
|
||||
@ -5097,6 +5141,10 @@ Object {
|
||||
"key": "portal_plans",
|
||||
"value": "[\\"free\\"]",
|
||||
},
|
||||
Object {
|
||||
"key": "portal_default_plan",
|
||||
"value": "yearly",
|
||||
},
|
||||
Object {
|
||||
"key": "portal_products",
|
||||
"value": "[]",
|
||||
@ -5463,6 +5511,10 @@ Object {
|
||||
"key": "portal_plans",
|
||||
"value": "[\\"free\\"]",
|
||||
},
|
||||
Object {
|
||||
"key": "portal_default_plan",
|
||||
"value": "yearly",
|
||||
},
|
||||
Object {
|
||||
"key": "portal_products",
|
||||
"value": "[]",
|
||||
@ -5893,6 +5945,10 @@ Object {
|
||||
"key": "portal_plans",
|
||||
"value": "[\\"free\\"]",
|
||||
},
|
||||
Object {
|
||||
"key": "portal_default_plan",
|
||||
"value": "yearly",
|
||||
},
|
||||
Object {
|
||||
"key": "portal_products",
|
||||
"value": "[]",
|
||||
|
@ -9,15 +9,17 @@ const models = require('../../../core/server/models');
|
||||
const {mockLabsDisabled, mockLabsEnabled} = require('../../utils/e2e-framework-mock-manager');
|
||||
const {anyErrorId} = matchers;
|
||||
|
||||
const CURRENT_SETTINGS_COUNT = 86;
|
||||
const CURRENT_SETTINGS_COUNT = 87;
|
||||
|
||||
const settingsMatcher = {};
|
||||
|
||||
const publicHashSettingMatcher = {
|
||||
key: 'public_hash',
|
||||
value: stringMatching(/[a-z0-9]{30}/)
|
||||
};
|
||||
|
||||
const labsSettingMatcher = {
|
||||
key: 'labs',
|
||||
value: stringMatching(/\{[^\s]+\}/)
|
||||
};
|
||||
|
||||
@ -30,10 +32,10 @@ const matchSettingsArray = (length) => {
|
||||
settingsArray[26] = publicHashSettingMatcher;
|
||||
}
|
||||
|
||||
if (length > 60) {
|
||||
if (length > 61) {
|
||||
// Added a setting that is alphabetically before 'labs'? then you need to increment this counter.
|
||||
// Item at index x is the lab settings, which changes as we add and remove features
|
||||
settingsArray[60] = labsSettingMatcher;
|
||||
settingsArray[61] = labsSettingMatcher;
|
||||
}
|
||||
|
||||
return settingsArray;
|
||||
|
@ -54,6 +54,7 @@ Object {
|
||||
"portal_button_icon": null,
|
||||
"portal_button_signup_text": "Subscribe",
|
||||
"portal_button_style": "icon-and-text",
|
||||
"portal_default_plan": "yearly",
|
||||
"portal_name": true,
|
||||
"portal_plans": Array [
|
||||
"free",
|
||||
|
@ -1406,6 +1406,7 @@ Object {
|
||||
"portal_button_icon": null,
|
||||
"portal_button_signup_text": "Subscribe",
|
||||
"portal_button_style": "icon-and-text",
|
||||
"portal_default_plan": "yearly",
|
||||
"portal_name": true,
|
||||
"portal_plans": Array [
|
||||
"free",
|
||||
@ -1507,6 +1508,7 @@ Object {
|
||||
"portal_button_icon": null,
|
||||
"portal_button_signup_text": "Subscribe",
|
||||
"portal_button_style": "icon-and-text",
|
||||
"portal_default_plan": "yearly",
|
||||
"portal_name": true,
|
||||
"portal_plans": Array [
|
||||
"free",
|
||||
|
@ -5,7 +5,7 @@ const db = require('../../../core/server/data/db');
|
||||
// Stuff we are testing
|
||||
const models = require('../../../core/server/models');
|
||||
|
||||
const SETTINGS_LENGTH = 93;
|
||||
const SETTINGS_LENGTH = 94;
|
||||
|
||||
describe('Settings Model', function () {
|
||||
before(models.init);
|
||||
|
@ -236,7 +236,7 @@ describe('Exporter', function () {
|
||||
|
||||
// NOTE: if default settings changed either modify the settings keys blocklist or increase allowedKeysLength
|
||||
// This is a reminder to think about the importer/exporter scenarios ;)
|
||||
const allowedKeysLength = 85;
|
||||
const allowedKeysLength = 86;
|
||||
totalKeysLength.should.eql(SETTING_KEYS_BLOCKLIST.length + allowedKeysLength);
|
||||
});
|
||||
});
|
||||
|
@ -37,7 +37,7 @@ describe('DB version integrity', function () {
|
||||
// Only these variables should need updating
|
||||
const currentSchemaHash = '34a9fa4dc1223ef6c45f8ed991d25de5';
|
||||
const currentFixturesHash = '4db87173699ad9c9d8a67ccab96dfd2d';
|
||||
const currentSettingsHash = '3128d4ec667a50049486b0c21f04be07';
|
||||
const currentSettingsHash = '5c957ceb48c4878767d7d3db484c592d';
|
||||
const currentRoutesHash = '3d180d52c663d173a6be791ef411ed01';
|
||||
|
||||
// If this test is failing, then it is likely a change has been made that requires a DB version bump,
|
||||
|
@ -326,6 +326,14 @@
|
||||
"defaultValue": "[\"free\"]",
|
||||
"type": "array"
|
||||
},
|
||||
"portal_default_plan": {
|
||||
"defaultValue": "yearly",
|
||||
"validations": {
|
||||
"isEmpty": false,
|
||||
"isIn": [["yearly", "monthly"]]
|
||||
},
|
||||
"type": "string"
|
||||
},
|
||||
"portal_products": {
|
||||
"defaultValue": "[]",
|
||||
"type": "array"
|
||||
|
@ -338,6 +338,14 @@
|
||||
"defaultValue": "[\"free\"]",
|
||||
"type": "array"
|
||||
},
|
||||
"portal_default_plan": {
|
||||
"defaultValue": "yearly",
|
||||
"validations": {
|
||||
"isEmpty": false,
|
||||
"isIn": [["yearly", "monthly"]]
|
||||
},
|
||||
"type": "string"
|
||||
},
|
||||
"portal_products": {
|
||||
"defaultValue": "[]",
|
||||
"type": "array"
|
||||
|
Loading…
Reference in New Issue
Block a user