Skip to content

Commit 17d548b

Browse files
committed
chore: add different pro upsell dialogs
1 parent 12f7d78 commit 17d548b

File tree

4 files changed

+86
-27
lines changed

4 files changed

+86
-27
lines changed

src/nls/root/strings.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1668,7 +1668,6 @@ define({
16681668
// promos
16691669
"PROMO_UPGRADE_TITLE": "You’ve been upgraded to {0}",
16701670
"PROMO_UPGRADE_MESSAGE": "Enjoy full access to all premium features for the next {0} days:",
1671-
"PROMO_ENDED_MESSAGE": "Subscribe now to continue using these advanced features:",
16721671
"PROMO_CARD_1": "Drag & Drop Elements",
16731672
"PROMO_CARD_1_MESSAGE": "Rearrange sections visually — Phoenix updates the HTML & CSS for you.",
16741673
"PROMO_CARD_2": "Image Replacement",
@@ -1680,6 +1679,10 @@ define({
16801679
"PROMO_LEARN_MORE": "Learn More\u2026",
16811680
"PROMO_GET_APP_UPSELL_BUTTON": "Get {0}",
16821681
"PROMO_PRO_ENDED_TITLE": "Your {0} Trial has ended",
1682+
"PROMO_ENDED_MESSAGE": "Subscribe now to continue using these advanced features:",
1683+
"PROMO_PRO_UNLOCK_PRO_TITLE": "Unlock the power of {0}",
1684+
"PROMO_PRO_UNLOCK_LIVE_EDIT_TITLE": "Unlock Live Edit with {0}",
1685+
"PROMO_PRO_UNLOCK_MESSAGE": "Subscribe now to unlock these advanced features:",
16831686
"PROMO_PRO_TRIAL_DAYS_LEFT": "Phoenix Pro Trial ({0} days left)",
16841687
"GET_PHOENIX_PRO": "Get Phoenix Pro",
16851688
"USER_FREE_PLAN_NAME": "Free Plan"

src/services/entitlements.js

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,17 @@ define(function (require, exports, module) {
4646
const EVENT_ENTITLEMENTS_CHANGED = "entitlements_changed";
4747

4848
/**
49-
* Check if user is logged in
49+
* Check if user is logged in. Best to check after `EVENT_ENTITLEMENTS_CHANGED`.
5050
* @returns {*}
5151
*/
5252
function isLoggedIn() {
5353
return LoginService.isLoggedIn();
5454
}
5555

5656
/**
57-
* Get the plan details from entitlements with fallback to free plan defaults
57+
* Get the plan details from entitlements with fallback to free plan defaults. If the user is
58+
* in pro trial(isInProTrial API), then paidSubscriber will always be true as we need to treat user as paid.
59+
* you should use isInProTrial API to check if user is in pro trial if some trial-related logic needs to be done.
5860
* @returns {Promise<Object>} Plan details object
5961
*/
6062
async function getPlanDetails() {
@@ -74,7 +76,7 @@ define(function (require, exports, module) {
7476
}
7577

7678
/**
77-
* Check if user is in a pro trial
79+
* Check if user is in a pro trial. IF the user is in pro trail, then `plan.paidSubscriber` will always be true.
7880
* @returns {Promise<boolean>} True if user is in pro trial, false otherwise
7981
*/
8082
async function isInProTrial() {
@@ -92,16 +94,35 @@ define(function (require, exports, module) {
9294
}
9395

9496
/**
95-
* Get raw entitlements from server
97+
* Get current raw entitlements. Should not be used directly, use individual feature entitlement instead
98+
* like getLiveEditEntitlement.
9699
* @returns {Promise<Object|null>} Raw entitlements object or null
97100
*/
98101
async function getRawEntitlements() {
99102
return await LoginService.getEntitlements();
100103
}
101104

102105
/**
103-
* Get live edit entitlement with fallback defaults
104-
* @returns {Promise<Object>} Live edit entitlement object
106+
* Get live edit is enabled for user, based on his logged in pro-user/trial status.
107+
*
108+
* @returns {Promise<Object>} Live edit entitlement object with the following shape:
109+
* @returns {Promise<Object>} entitlement
110+
* @returns {Promise<boolean>} entitlement.activated - If true, enable live edit feature.
111+
* If false, use promotions.showProUpsellDialog
112+
* with UPSELL_TYPE_LIVE_EDIT to show an upgrade dialog if needed.
113+
* @returns {Promise<string>} entitlement.subscribeURL - URL to subscribe/purchase if not activated
114+
* @returns {Promise<string>} entitlement.upgradeToPlan - Plan name that includes live edit entitlement
115+
* @returns {Promise<number>} [entitlement.validTill] - Timestamp when entitlement expires (if from server)
116+
*
117+
* @example
118+
* const liveEditEntitlement = await Entitlements.getLiveEditEntitlement();
119+
* if (liveEditEntitlement.activated) {
120+
* // Enable live edit feature
121+
* enableLiveEditFeature();
122+
* } else {
123+
* // Show upgrade dialog when user tries to use live edit
124+
* promotions.showProUpsellDialog(promotions.UPSELL_TYPE_LIVE_EDIT);
125+
* }
105126
*/
106127
async function getLiveEditEntitlement() {
107128
const entitlements = await LoginService.getEffectiveEntitlements();

src/services/pro-dialogs.js

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ define(function (require, exports, module) {
4545
// save a copy of window.fetch so that extensions wont tamper with it.
4646
let fetchFn = window.fetch;
4747

48-
function showProUpgradeDialog(trialDays) {
48+
const UPSELL_TYPE_LIVE_EDIT = "live_edit";
49+
const UPSELL_TYPE_PRO_TRIAL_ENDED = "pro_trial_ended";
50+
const UPSELL_TYPE_GET_PRO = "get_pro";
51+
52+
function showProTrialStartDialog(trialDays) {
4953
const title = StringUtils.format(Strings.PROMO_UPGRADE_TITLE, proTitle);
5054
const message = StringUtils.format(Strings.PROMO_UPGRADE_MESSAGE, trialDays);
5155
const $template = $(Mustache.render(proUpgradeHTML, {
@@ -65,12 +69,38 @@ define(function (require, exports, module) {
6569
});
6670
}
6771

68-
function _showLocalProEndedDialog() {
69-
const title = StringUtils.format(Strings.PROMO_PRO_ENDED_TITLE, proTitle);
70-
const buttonGetPro = StringUtils.format(Strings.PROMO_GET_APP_UPSELL_BUTTON, proTitlePlain);
72+
function _getUpsellDialogText(upsellType) {
73+
// our pro dialog has 2 flavors. Local which is shipped with the release for showing if user is offline
74+
// and remote which is fetched from the server if we have a remote offer to show. This fn will be called
75+
// by both of these flavors and we need to return the appropriate text for each.
76+
const buttonGetProText = StringUtils.format(Strings.PROMO_GET_APP_UPSELL_BUTTON, proTitlePlain);
77+
switch (upsellType) {
78+
case UPSELL_TYPE_PRO_TRIAL_ENDED: return {
79+
title: StringUtils.format(Strings.PROMO_PRO_ENDED_TITLE, proTitle),
80+
localDialogMessage: Strings.PROMO_ENDED_MESSAGE, // this will be shown in the local dialog
81+
buttonGetProText
82+
};
83+
case UPSELL_TYPE_LIVE_EDIT: return {
84+
title: StringUtils.format(Strings.PROMO_PRO_UNLOCK_LIVE_EDIT_TITLE, proTitle),
85+
localDialogMessage: Strings.PROMO_PRO_UNLOCK_MESSAGE,
86+
buttonGetProText
87+
};
88+
case UPSELL_TYPE_GET_PRO:
89+
default: return {
90+
title: StringUtils.format(Strings.PROMO_PRO_UNLOCK_PRO_TITLE, proTitle),
91+
localDialogMessage: Strings.PROMO_PRO_UNLOCK_MESSAGE,
92+
buttonGetProText
93+
};
94+
}
95+
}
96+
97+
function _showLocalProEndedDialog(upsellType) {
98+
const dlgText = _getUpsellDialogText(upsellType);
99+
const title = dlgText.title;
100+
const buttonGetPro = dlgText.buttonGetProText;
71101
const $template = $(Mustache.render(proUpgradeHTML, {
72102
title, Strings,
73-
message: Strings.PROMO_ENDED_MESSAGE,
103+
message: dlgText.localDialogMessage,
74104
secondaryButton: Strings.CANCEL,
75105
primaryButton: buttonGetPro
76106
}));
@@ -86,13 +116,14 @@ define(function (require, exports, module) {
86116
});
87117
}
88118

89-
function _showRemoteProEndedDialog(currentVersion, promoHtmlURL, upsellPurchaseURL) {
90-
const buttonGetPro = StringUtils.format(Strings.PROMO_GET_APP_UPSELL_BUTTON, proTitlePlain);
91-
const title = StringUtils.format(Strings.PROMO_PRO_ENDED_TITLE, proTitle);
119+
function _showRemoteProEndedDialog(upsellType, currentVersion, promoHtmlURL, upsellPurchaseURL) {
120+
const dlgText = _getUpsellDialogText(upsellType);
121+
const title = dlgText.title;
122+
const buttonGetPro = dlgText.buttonGetProText;
92123
const currentTheme = ThemeManager.getCurrentTheme();
93124
const theme = currentTheme && currentTheme.dark ? "dark" : "light";
94125
const promoURL = `${promoHtmlURL}?lang=${
95-
brackets.getLocale()}&theme=${theme}&version=${currentVersion}`;
126+
brackets.getLocale()}&theme=${theme}&version=${currentVersion}&upsellType=${upsellType}`;
96127
const $template = $(Mustache.render(proEndedHTML, {Strings, title, buttonGetPro, promoURL}));
97128
Dialogs.showModalDialogUsingTemplate($template).done(function (id) {
98129
console.log("Dialog closed with id: " + id);
@@ -106,30 +137,31 @@ define(function (require, exports, module) {
106137
});
107138
}
108139

109-
async function showProEndedDialog() {
140+
async function showProUpsellDialog(upsellType) {
110141
const currentVersion = window.AppConfig.apiVersion;
111142

112143
if (!navigator.onLine) {
113-
_showLocalProEndedDialog();
144+
_showLocalProEndedDialog(upsellType);
114145
return;
115146
}
116147

117148
try {
118149
const configURL = `${brackets.config.promotions_url}app/config.json`;
119150
const response = await fetchFn(configURL);
120151
if (!response.ok) {
121-
_showLocalProEndedDialog();
152+
_showLocalProEndedDialog(upsellType);
122153
return;
123154
}
124155

125156
const config = await response.json();
126157
if (config.upsell_after_trial_url) {
127-
_showRemoteProEndedDialog(currentVersion, config.upsell_after_trial_url, config.upsell_purchase_url);
158+
_showRemoteProEndedDialog(upsellType, currentVersion,
159+
config.upsell_after_trial_url, config.upsell_purchase_url);
128160
} else {
129-
_showLocalProEndedDialog();
161+
_showLocalProEndedDialog(upsellType);
130162
}
131163
} catch (error) {
132-
_showLocalProEndedDialog();
164+
_showLocalProEndedDialog(upsellType);
133165
}
134166
}
135167

@@ -141,6 +173,9 @@ define(function (require, exports, module) {
141173
};
142174
}
143175

144-
exports.showProUpgradeDialog = showProUpgradeDialog;
145-
exports.showProEndedDialog = showProEndedDialog;
176+
exports.showProTrialStartDialog = showProTrialStartDialog;
177+
exports.showProUpsellDialog = showProUpsellDialog;
178+
exports.UPSELL_TYPE_PRO_TRIAL_ENDED = UPSELL_TYPE_PRO_TRIAL_ENDED;
179+
exports.UPSELL_TYPE_GET_PRO = UPSELL_TYPE_GET_PRO;
180+
exports.UPSELL_TYPE_LIVE_EDIT = UPSELL_TYPE_LIVE_EDIT;
146181
});

src/services/promotions.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ define(function (require, exports, module) {
275275
// For corruption, show trial ended dialog and create expired marker
276276
// Do not grant any new trial as possible tampering.
277277
console.warn("trial data corrupted");
278-
ProDialogs.showProEndedDialog(); // Show ended dialog for security
278+
ProDialogs.showProUpsellDialog(ProDialogs.UPSELL_TYPE_PRO_TRIAL_ENDED); // Show ended dialog for security
279279

280280
// Create expired trial marker to prevent future trial grants
281281
await _setTrialData({
@@ -300,7 +300,7 @@ define(function (require, exports, module) {
300300
const hasProSubscription = await _hasProSubscription();
301301
if (!hasProSubscription) {
302302
console.log("Existing trial expired, showing promo ended dialog");
303-
ProDialogs.showProEndedDialog();
303+
ProDialogs.showProUpsellDialog(ProDialogs.UPSELL_TYPE_PRO_TRIAL_ENDED);
304304
} else {
305305
console.log("Existing trial expired, but user has pro subscription - skipping promo dialog");
306306
}
@@ -354,7 +354,7 @@ define(function (require, exports, module) {
354354
// Check if user has pro subscription before showing upgrade dialog
355355
const hasProSubscription = await _hasProSubscription();
356356
if (!hasProSubscription) {
357-
ProDialogs.showProUpgradeDialog(trialDays);
357+
ProDialogs.showProTrialStartDialog(trialDays);
358358
} else {
359359
console.log("Pro trial activated, but user has pro subscription - skipping upgrade dialog");
360360
}

0 commit comments

Comments
 (0)