diff --git a/src/brackets.config.dist.json b/src/brackets.config.dist.json
index dab5e83374..0e8dfcc466 100644
--- a/src/brackets.config.dist.json
+++ b/src/brackets.config.dist.json
@@ -9,5 +9,6 @@
"buildtype" : "production",
"bugsnagEnv" : "production",
"app_notification_url" : "https://updates.phcode.io/appNotifications/prod/",
- "app_update_url" : "https://updates.phcode.io/tauri/update-latest-stable-prod.json"
+ "app_update_url" : "https://updates.phcode.io/tauri/update-latest-stable-prod.json",
+ "promotions_url" : "https://promotions.phcode.dev/prod/"
}
diff --git a/src/brackets.config.staging.json b/src/brackets.config.staging.json
index 94095484e8..6ba54f9810 100644
--- a/src/brackets.config.staging.json
+++ b/src/brackets.config.staging.json
@@ -9,5 +9,6 @@
"buildtype" : "staging",
"bugsnagEnv" : "staging",
"app_notification_url" : "https://updates.phcode.io/appNotifications/staging/",
- "app_update_url" : "https://updates.phcode.io/tauri/update-latest-pre-release.json"
+ "app_update_url" : "https://updates.phcode.io/tauri/update-latest-pre-release.json",
+ "promotions_url" : "https://promotions.phcode.dev/dev/"
}
diff --git a/src/config.json b/src/config.json
index c5b3efc417..3bba88b870 100644
--- a/src/config.json
+++ b/src/config.json
@@ -4,6 +4,8 @@
"app_name_about": "Phoenix Code",
"about_icon": "styles/images/phoenix-icon.svg",
"account_url": "https://account.phcode.dev/",
+ "promotions_url": "https://promotions.phcode.dev/dev/",
+ "purchase_url": "https://phcode.io/pricing",
"how_to_use_url": "https://github.com/adobe/brackets/wiki/How-to-Use-Brackets",
"docs_url": "https://docs.phcode.dev/",
"support_url": "https://account.phcode.dev/?returnUrl=https%3A%2F%2Faccount.phcode.dev%2F%23support",
diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js
index 6ba78b8cc6..d9efc3ffce 100644
--- a/src/nls/root/strings.js
+++ b/src/nls/root/strings.js
@@ -1667,6 +1667,7 @@ define({
// promos
"PROMO_UPGRADE_TITLE": "You’ve been upgraded to {0}",
"PROMO_UPGRADE_MESSAGE": "Enjoy full access to all premium features for the next {0} days:",
+ "PROMO_ENDED_MESSAGE": "Subscribe now to continue using these advanced features:",
"PROMO_CARD_1": "Drag & Drop Elements",
"PROMO_CARD_1_MESSAGE": "Rearrange sections visually — Phoenix updates the HTML & CSS for you.",
"PROMO_CARD_2": "Image Replacement",
@@ -1675,5 +1676,7 @@ define({
"PROMO_CARD_3_MESSAGE": "Duplicate and delete elements with a single click.",
"PROMO_CARD_4": "Editing Text In Preview",
"PROMO_CARD_4_MESSAGE": "Edit headings, buttons, and copy directly in the preview.",
- "PROMO_LEARN_MORE": "Learn More\u2026"
+ "PROMO_LEARN_MORE": "Learn More\u2026",
+ "PROMO_GET_APP_UPSELL_BUTTON": "Get {0}",
+ "PROMO_PRO_ENDED_TITLE": "Your {0} upgrade has ended"
});
diff --git a/src/services/html/pro-upgrade.html b/src/services/html/pro-upgrade.html
index 323aa91509..48e1ce046e 100644
--- a/src/services/html/pro-upgrade.html
+++ b/src/services/html/pro-upgrade.html
@@ -1,4 +1,4 @@
-
+
@@ -50,7 +50,7 @@
{{Strings.PROMO_CARD_4}}
diff --git a/src/services/html/promo-ended.html b/src/services/html/promo-ended.html
new file mode 100644
index 0000000000..5a3142d8c1
--- /dev/null
+++ b/src/services/html/promo-ended.html
@@ -0,0 +1,22 @@
+
diff --git a/src/services/login-service.js b/src/services/login-service.js
index 6fe2d6b271..86256b97a4 100644
--- a/src/services/login-service.js
+++ b/src/services/login-service.js
@@ -58,7 +58,7 @@ define(function (require, exports, module) {
try {
const accountBaseURL = LoginService.getAccountBaseURL();
- const language = Phoenix.app && Phoenix.app.language ? Phoenix.app.language : 'en';
+ const language = brackets.getLocale();
let url = `${accountBaseURL}/getAppEntitlements?lang=${language}`;
let fetchOptions = {
method: 'GET',
diff --git a/src/services/pro-dialogs.js b/src/services/pro-dialogs.js
index 6ae51b12ee..6dbbf6cb38 100644
--- a/src/services/pro-dialogs.js
+++ b/src/services/pro-dialogs.js
@@ -29,25 +29,107 @@ define(function (require, exports, module) {
const proTitle = `
Phoenix Pro
- `;
+ `,
+ proTitlePlain = `Phoenix Pro
+ `;
require("./setup-login-service"); // this adds loginService to KernalModeTrust
const Dialogs = require("widgets/Dialogs"),
Mustache = require("thirdparty/mustache/mustache"),
Strings = require("strings"),
StringUtils = require("utils/StringUtils"),
- proUpgradeHTML = require("text!./html/pro-upgrade.html");
+ ThemeManager = require("view/ThemeManager"),
+ Metrics = require("utils/Metrics"),
+ proUpgradeHTML = require("text!./html/pro-upgrade.html"),
+ proEndedHTML = require("text!./html/promo-ended.html");
function showProUpgradeDialog(trialDays) {
const title = StringUtils.format(Strings.PROMO_UPGRADE_TITLE, proTitle);
const message = StringUtils.format(Strings.PROMO_UPGRADE_MESSAGE, trialDays);
- const $template = $(Mustache.render(proUpgradeHTML, {title, message, Strings}));
+ const $template = $(Mustache.render(proUpgradeHTML, {
+ title, message, Strings,
+ secondaryButton: Strings.PROMO_LEARN_MORE,
+ primaryButton: Strings.OK
+ }));
Dialogs.showModalDialogUsingTemplate($template).done(function (id) {
console.log("Dialog closed with id: " + id);
- if(id === 'learn_more') {
- Phoenix.app.openURLInDefaultBrowser(brackets.config.homepage_url);
+ Metrics.countEvent(Metrics.EVENT_TYPE.PRO, "dlgShow", "promo");
+ if(id === 'secondaryButton') {
+ Metrics.countEvent(Metrics.EVENT_TYPE.PRO, "dlgAct", "promoLearn");
+ Phoenix.app.openURLInDefaultBrowser(brackets.config.purchase_url);
+ } else {
+ Metrics.countEvent(Metrics.EVENT_TYPE.PRO, "dlgAct", "promoCancel");
}
});
}
+ function _showLocalProEndedDialog() {
+ const title = StringUtils.format(Strings.PROMO_PRO_ENDED_TITLE, proTitle);
+ const buttonGetPro = StringUtils.format(Strings.PROMO_GET_APP_UPSELL_BUTTON, proTitlePlain);
+ const $template = $(Mustache.render(proUpgradeHTML, {
+ title, Strings,
+ message: Strings.PROMO_ENDED_MESSAGE,
+ secondaryButton: Strings.CANCEL,
+ primaryButton: buttonGetPro
+ }));
+ Dialogs.showModalDialogUsingTemplate($template).done(function (id) {
+ console.log("Dialog closed with id: " + id);
+ Metrics.countEvent(Metrics.EVENT_TYPE.PRO, "dlgShow", "localUpgrade");
+ if(id === 'ok') {
+ Metrics.countEvent(Metrics.EVENT_TYPE.PRO, "dlgAct", "localGetPro");
+ Phoenix.app.openURLInDefaultBrowser(brackets.config.purchase_url);
+ } else {
+ Metrics.countEvent(Metrics.EVENT_TYPE.PRO, "dlgAct", "localCancel");
+ }
+ });
+ }
+
+ function _showRemoteProEndedDialog(currentVersion, promoHtmlURL, upsellPurchaseURL) {
+ const buttonGetPro = StringUtils.format(Strings.PROMO_GET_APP_UPSELL_BUTTON, proTitlePlain);
+ const title = StringUtils.format(Strings.PROMO_PRO_ENDED_TITLE, proTitle);
+ const currentTheme = ThemeManager.getCurrentTheme();
+ const theme = currentTheme && currentTheme.dark ? "dark" : "light";
+ const promoURL = `${promoHtmlURL}?lang=${
+ brackets.getLocale()}&theme=${theme}&version=${currentVersion}`;
+ const $template = $(Mustache.render(proEndedHTML, {Strings, title, buttonGetPro, promoURL}));
+ Dialogs.showModalDialogUsingTemplate($template).done(function (id) {
+ console.log("Dialog closed with id: " + id);
+ Metrics.countEvent(Metrics.EVENT_TYPE.PRO, "dlgShow", "remoteUpgrade");
+ if(id === 'get_pro') {
+ Metrics.countEvent(Metrics.EVENT_TYPE.PRO, "dlgAct", "remoteGetPro");
+ Phoenix.app.openURLInDefaultBrowser(upsellPurchaseURL || brackets.config.purchase_url);
+ } else {
+ Metrics.countEvent(Metrics.EVENT_TYPE.PRO, "dlgAct", "remoteCancel");
+ }
+ });
+ }
+
+ async function showProEndedDialog() {
+ const currentVersion = window.AppConfig.apiVersion;
+
+ if (!navigator.onLine) {
+ _showLocalProEndedDialog();
+ return;
+ }
+
+ try {
+ const configURL = `${brackets.config.promotions_url}app/config.json`;
+ const response = await fetch(configURL);
+ if (!response.ok) {
+ _showLocalProEndedDialog();
+ return;
+ }
+
+ const config = await response.json();
+ if (config.upsell_after_trial_url) {
+ _showRemoteProEndedDialog(currentVersion, config.upsell_after_trial_url, config.upsell_purchase_url);
+ } else {
+ _showLocalProEndedDialog();
+ }
+ } catch (error) {
+ _showLocalProEndedDialog();
+ }
+ }
+
exports.showProUpgradeDialog = showProUpgradeDialog;
+ exports.showProEndedDialog = showProEndedDialog;
});
diff --git a/src/services/profile-menu.js b/src/services/profile-menu.js
index 045a42c8d1..89661ec641 100644
--- a/src/services/profile-menu.js
+++ b/src/services/profile-menu.js
@@ -525,7 +525,6 @@ define(function (require, exports, module) {
title: Strings.CMD_USER_PROFILE
})
.appendTo($("#main-toolbar .bottom-buttons"));
- // _updateProfileIcon("CA", "blue");
$icon.on('click', ()=>{
togglePopup();
});
diff --git a/src/services/promotions.js b/src/services/promotions.js
index dff787f786..8f3d60de05 100644
--- a/src/services/promotions.js
+++ b/src/services/promotions.js
@@ -203,7 +203,20 @@ define(function (require, exports, module) {
// Check if we should grant any trial
if (remainingDays <= 0 && !isNewerVersion) {
- console.log("Existing trial expired, same/older version - no new trial");
+ // Check if promo ended dialog was already shown for this version
+ if (existingTrialData.upgradeDialogShownVersion !== currentVersion) {
+ // todo we should not show this to logged in pro subscribers, but at startup time,
+ // we do not know if login is done yet.
+ console.log("Existing trial expired, showing promo ended dialog");
+ ProDialogs.showProEndedDialog();
+ // Store that dialog was shown for this version
+ await _setTrialData({
+ ...existingTrialData,
+ upgradeDialogShownVersion: currentVersion
+ });
+ } else {
+ console.log("Existing trial expired, upgrade dialog already shown for this version");
+ }
return;
}
@@ -253,7 +266,9 @@ define(function (require, exports, module) {
function _isAnyDialogsVisible() {
const $modal = $(`.modal.instance`);
- return $modal.length > 0 && $modal.is(':visible');
+ const $notifications = $(`.notification-ui-tooltip`);
+ return ($modal.length > 0 && $modal.is(':visible')) ||
+ ($notifications.length > 0 && $notifications.is(':visible'));
}
/**
diff --git a/src/styles/brackets.less b/src/styles/brackets.less
index 6ff3d92116..1f1ef12c8b 100644
--- a/src/styles/brackets.less
+++ b/src/styles/brackets.less
@@ -3222,6 +3222,7 @@ label input {
flex-direction: column;
z-index: 100;
height: 100%;
+ pointer-events: none;
}
.notification-popup-container {
@@ -3237,6 +3238,7 @@ label input {
/* animated properties */
opacity: 0;
transform: translateY(-50px);
+ pointer-events: all;
}
.notification-dialog-content strong {
diff --git a/src/styles/brackets_core_ui_variables.less b/src/styles/brackets_core_ui_variables.less
index adbe908d5e..8575b85431 100644
--- a/src/styles/brackets_core_ui_variables.less
+++ b/src/styles/brackets_core_ui_variables.less
@@ -104,6 +104,7 @@
@bc-text-searching-match: #f6a644;
// Panel
+@bc-titlebar-modern-gradient: linear-gradient(135deg, rgba(255,154,60,0.12), rgba(20,115,230,0.08));
@bc-panel-bg: #dfe2e2;
@bc-panel-bg-alt: #e6e9e9;
@bc-panel-bg-promoted: #d4d7d7;
diff --git a/src/styles/brackets_patterns_override.less b/src/styles/brackets_patterns_override.less
index 04e1d8462c..8ea94df2d7 100644
--- a/src/styles/brackets_patterns_override.less
+++ b/src/styles/brackets_patterns_override.less
@@ -1071,13 +1071,12 @@ a:focus {
}
.modal-header {
- background: @bc-panel-bg-promoted;;
+ background: @bc-titlebar-modern-gradient;
border-radius: 4px 4px 0 0;
border-bottom: 1px solid @bc-panel-separator;
box-shadow: inset 0 1px 0 @bc-highlight;
.dark & {
- background: @dark-bc-panel-bg-promoted;;
border-bottom: 1px solid @dark-bc-panel-separator;
box-shadow: inset 0 1px 0 @dark-bc-highlight;
}
diff --git a/src/styles/phoenix-pro.less b/src/styles/phoenix-pro.less
index e708cce20e..86c803d41c 100644
--- a/src/styles/phoenix-pro.less
+++ b/src/styles/phoenix-pro.less
@@ -1,8 +1,28 @@
@import "brackets_variables.less";
@import "brackets_core_ui_variables.less";
+/* ---- Phoenix Pro gradient title ----
+ Light default: use the "light" gradient so it pops on light bg.
+ Dark override: switch to the "dark" gradient for richer contrast. */
+.phoenix-pro-title {
+ background: @phoenix-pro-gradient-light;
+ background-clip: text;
+ -webkit-background-clip: text;
+ color: transparent;
+ -webkit-text-fill-color: transparent;
+ display: inline-block;
+
+ .dark & {
+ background: @phoenix-pro-gradient-dark;
+ background-clip: text;
+ -webkit-background-clip: text;
+ color: transparent;
+ -webkit-text-fill-color: transparent;
+ }
+}
+
/* Dialog styles with light default + .dark overrides */
-.browser-login-waiting-dialog {
+.browser-login-waiting-dialog, .pro-upgrade-dialog{
/* ---- Layout ---- */
.features-grid {
display: grid;
@@ -104,26 +124,6 @@
}
}
- /* ---- Phoenix Pro gradient title ----
- Light default: use the "light" gradient so it pops on light bg.
- Dark override: switch to the "dark" gradient for richer contrast. */
- .phoenix-pro-title {
- background: @phoenix-pro-gradient-light;
- background-clip: text;
- -webkit-background-clip: text;
- color: transparent;
- -webkit-text-fill-color: transparent;
- display: inline-block;
-
- .dark & {
- background: @phoenix-pro-gradient-dark;
- background-clip: text;
- -webkit-background-clip: text;
- color: transparent;
- -webkit-text-fill-color: transparent;
- }
- }
-
/* ---- Links ---- */
a {
color: @bc-text-link; // #0083e8
@@ -138,3 +138,40 @@
text-decoration: underline;
}
}
+
+.pro-ended-dialog.modal {
+ display: flex;
+ flex-direction: column;
+ width: 640px;
+ height: 640px; /* overall modal height */
+}
+
+.pro-ended-dialog .modal-header,
+.pro-ended-dialog .modal-footer {
+ flex: 0 0 auto; /* don’t stretch */
+}
+
+.pro-ended-dialog .modal-body {
+ flex: 1 1 auto; /* take all leftover space */
+ min-height: 0; /* allow shrinking properly */
+ max-height: 100%; /* allow shrinking properly */
+ padding: 0;
+ margin: 0;
+ display: flex;
+ flex-direction: column;
+ background: none;
+}
+
+.pro-ended-dialog .promo-iframe-wrap {
+ flex: 1 1 auto;
+ min-height: 0; /* important inside flex */
+ display: flex;
+}
+
+.pro-ended-dialog .promo-iframe {
+ flex: 1 1 auto;
+ width: 100%;
+ height: 100%;
+ border: 0;
+ display: block;
+}