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 @@ - 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; +}