diff --git a/src/extensionsIntegrated/Phoenix/html/login-dialog.html b/src/extensionsIntegrated/Phoenix/html/login-dialog.html new file mode 100644 index 0000000000..dbe692d262 --- /dev/null +++ b/src/extensionsIntegrated/Phoenix/html/login-dialog.html @@ -0,0 +1,17 @@ +
+ + +
diff --git a/src/extensionsIntegrated/Phoenix/html/profile-panel.html b/src/extensionsIntegrated/Phoenix/html/profile-panel.html new file mode 100644 index 0000000000..52dc6fcb59 --- /dev/null +++ b/src/extensionsIntegrated/Phoenix/html/profile-panel.html @@ -0,0 +1,39 @@ +
+ + +
diff --git a/src/extensionsIntegrated/Phoenix/main.js b/src/extensionsIntegrated/Phoenix/main.js index 94f0015324..5bb8dcacbc 100644 --- a/src/extensionsIntegrated/Phoenix/main.js +++ b/src/extensionsIntegrated/Phoenix/main.js @@ -32,23 +32,24 @@ define(function (require, exports, module) { Strings = require("strings"), Dialogs = require("widgets/Dialogs"), NotificationUI = require("widgets/NotificationUI"), - DefaultDialogs = require("widgets/DefaultDialogs"); + DefaultDialogs = require("widgets/DefaultDialogs"), + ProfileMenu = require("./profile-menu"); const PERSIST_STORAGE_DIALOG_DELAY_SECS = 60000; let $icon; function _addToolbarIcon() { - const helpButtonID = "help-button"; + const helpButtonID = "user-profile-button"; $icon = $("") .attr({ id: helpButtonID, href: "#", - class: "help", - title: Strings.CMD_SUPPORT + class: "user", + title: Strings.CMD_USER_PROFILE }) .appendTo($("#main-toolbar .bottom-buttons")); $icon.on('click', ()=>{ - Phoenix.app.openURLInDefaultBrowser(brackets.config.support_url); + ProfileMenu.init(); }); } function _showUnSupportedBrowserDialogue() { diff --git a/src/extensionsIntegrated/Phoenix/profile-menu.js b/src/extensionsIntegrated/Phoenix/profile-menu.js new file mode 100644 index 0000000000..4b4e6e927b --- /dev/null +++ b/src/extensionsIntegrated/Phoenix/profile-menu.js @@ -0,0 +1,221 @@ +define(function (require, exports, module) { + const Mustache = require("thirdparty/mustache/mustache"); + const PopUpManager = require("widgets/PopUpManager"); + + // HTML templates + const loginTemplate = require("text!./html/login-dialog.html"); + const profileTemplate = require("text!./html/profile-panel.html"); + + // for the popup DOM element + let $popup = null; + + // this is to track whether the popup is visible or not + let isPopupVisible = false; + + // if user is logged in we show the profile menu, otherwise we show the login menu + const isLoggedIn = false; + + const defaultLoginData = { + welcomeTitle: "Welcome to Phoenix Code", + signInBtnText: "Sign in to your account", + supportBtnText: "Contact support" + }; + + const defaultProfileData = { + initials: "CA", + userName: "Charly A.", + planName: "Paid Plan", + quotaLabel: "AI quota used", + quotaUsed: "7,000", + quotaTotal: "10,000", + quotaUnit: "tokens", + quotaPercent: 70, + accountBtnText: "Account details", + supportBtnText: "Contact support", + signOutBtnText: "Sign out" + }; + + function _handleSignInBtnClick() { + console.log("User clicked sign in button"); + } + + function _handleSignOutBtnClick() { + console.log("User clicked sign out"); + } + + function _handleContactSupportBtnClick() { + Phoenix.app.openURLInDefaultBrowser(brackets.config.support_url); + } + + function _handleAccountDetailsBtnClick() { + console.log("User clicked account details"); + } + + /** + * Close the popup if it's open + * this is called at various instances like when the user click on the profile icon even if the popup is open + * or when user clicks somewhere else on the document + */ + function closePopup() { + if ($popup) { + PopUpManager.removePopUp($popup); + $popup = null; + isPopupVisible = false; + } + } + + /** + * this function is to position the popup near the profile button + */ + function positionPopup() { + const $profileButton = $("#user-profile-button"); + + if ($profileButton.length && $popup) { + const buttonPos = $profileButton.offset(); + const popupWidth = $popup.outerWidth(); + const windowWidth = $(window).width(); + + // pos above the profile button + let top = buttonPos.top - $popup.outerHeight() - 10; + + // If popup would go off the right edge of the window, align right edge of popup with right edge of button + let left = Math.min( + buttonPos.left - popupWidth + $profileButton.outerWidth(), + windowWidth - popupWidth - 10 + ); + + // never go off left edge + left = Math.max(10, left); + + $popup.css({ + top: top + "px", + left: left + "px" + }); + } + } + + /** + * Shows the sign-in popup when the user is not logged in + * @param {Object} loginData - Data to populate the template (optional) + */ + function showLoginPopup(loginData) { + // If popup is already visible, just close it + if (isPopupVisible) { + closePopup(); + return; + } + + // Merge provided data with defaults + const templateData = $.extend({}, defaultLoginData, loginData || {}); + + // create the popup element + closePopup(); // close any existing popup first + + // Render template with data + const renderedTemplate = Mustache.render(loginTemplate, templateData); + $popup = $(renderedTemplate); + + $("body").append($popup); + isPopupVisible = true; + + positionPopup(); + + PopUpManager.addPopUp($popup, function() { + $popup.remove(); + $popup = null; + isPopupVisible = false; + }, true, { closeCurrentPopups: true }); + + // event handlers for buttons + $popup.find("#phoenix-signin-btn").on("click", function () { + _handleSignInBtnClick(); + closePopup(); + }); + + $popup.find("#phoenix-support-btn").on("click", function () { + _handleContactSupportBtnClick(); + closePopup(); + }); + + // handle window resize to reposition popup + $(window).on("resize.profilePopup", function () { + if (isPopupVisible) { + positionPopup(); + } + }); + } + + /** + * Shows the user profile popup when the user is logged in + * @param {Object} profileData - Data to populate the template (optional) + */ + function showProfilePopup(profileData) { + // If popup is already visible, just close it + if (isPopupVisible) { + closePopup(); + return; + } + + // Merge provided data with defaults + const templateData = $.extend({}, defaultProfileData, profileData || {}); + + closePopup(); + + // Render template with data + const renderedTemplate = Mustache.render(profileTemplate, templateData); + $popup = $(renderedTemplate); + + $("body").append($popup); + isPopupVisible = true; + positionPopup(); + + PopUpManager.addPopUp($popup, function() { + $popup.remove(); + $popup = null; + isPopupVisible = false; + }, true, { closeCurrentPopups: true }); + + $popup.find("#phoenix-account-btn").on("click", function () { + _handleAccountDetailsBtnClick(); + closePopup(); + }); + + $popup.find("#phoenix-support-btn").on("click", function () { + _handleContactSupportBtnClick(); + closePopup(); + }); + + $popup.find("#phoenix-signout-btn").on("click", function () { + _handleSignOutBtnClick(); + closePopup(); + }); + + // handle window resize to reposition popup + $(window).on("resize.profilePopup", function () { + if (isPopupVisible) { + positionPopup(); + } + }); + } + + /** + * Toggle the profile popup based on the user's login status + * this function is called inside the src/extensionsIntegrated/Phoenix/main.js when user clicks on the profile icon + * @param {Object} data - Data to populate the templates (optional) + */ + function init(data) { + // check if the popup is already visible or not. if visible close it + if (isPopupVisible) { + closePopup(); + return; + } + + if (isLoggedIn) { + showProfilePopup(data); + } else { + showLoginPopup(data); + } + } + + exports.init = init; +}); diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 7b996f7da4..4f4fe37d5d 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -626,6 +626,7 @@ define({ "CMD_AUTO_UPDATE": "Auto Update", "CMD_HOW_TO_USE_BRACKETS": "How to Use {APP_NAME}", "CMD_SUPPORT": "{APP_NAME} Support", + "CMD_USER_PROFILE": "User Profile", "CMD_DOCS": "Help, Getting Started", "CMD_SUGGEST": "Suggest a Feature", "CMD_REPORT_ISSUE": "Report Issue", diff --git a/src/styles/Extn-UserProfile.less b/src/styles/Extn-UserProfile.less new file mode 100644 index 0000000000..5ab1ac49e9 --- /dev/null +++ b/src/styles/Extn-UserProfile.less @@ -0,0 +1,246 @@ +@import "brackets_variables.less"; + +.profile-popup { + background-color: @bc-menu-bg; + color: @bc-menu-text; + border-radius: @bc-border-radius; + box-shadow: 0 3px 9px @bc-shadow; + width: 300px; + overflow: hidden; + position: absolute; + z-index: @z-index-brackets-context-menu-base; + animation: user-profile-dropdown-animation 90ms cubic-bezier(0, .97, .2, .99); + transform-origin: 0 0; + + .popup-header { + padding: 12px 15px; + border-bottom: 1px solid @bc-menu-separator; + } + + .popup-body { + padding: 12px 15px; + } + + .popup-title { + margin: 0; + font-size: 16px; + font-weight: @font-weight-semibold; + color: @bc-menu-text; + } + + .user-profile-header { + display: flex; + align-items: center; + } + + .user-avatar { + width: 40px; + height: 40px; + border-radius: 50%; + background-color: @bc-primary-btn-bg; + color: @bc-text-alt; + display: flex; + align-items: center; + justify-content: center; + margin-right: 15px; + font-weight: @font-weight-semibold; + font-size: 16px; + } + + .user-info { + display: flex; + flex-direction: column; + } + + .user-name { + font-weight: @font-weight-semibold; + color: @bc-menu-text; + } + + .user-plan { + color: #3c3; + font-size: 16px; + } + + .quota-section { + margin-bottom: 20px; + } + + .quota-header { + display: flex; + justify-content: space-between; + margin-bottom: 5px; + font-size: 13px; + } + + .progress-bar { + width: 100%; + height: 8px; + background-color: @bc-input-bg; + border-radius: 4px; + overflow: hidden; + + .progress-fill { + height: 100%; + background-color: @bc-primary-btn-bg; + border-radius: 4px; + } + } + + .support-link { + margin-top: 20px; + text-align: center; + } + + .menu-button { + width: 100%; + text-align: left; + padding: 8px 12px; + + i { + margin-right: 8px; + width: 20px; + text-align: center; + } + + &.signout { + color: #f55; + } + } + + .btn { + margin: 5px 0; + transition: background-color 0.2s ease; + + &.btn-primary { + background-color: @bc-primary-btn-bg; + border: 1px solid @bc-primary-btn-border; + box-shadow: inset 0 1px 0 @bc-highlight; + color: @bc-text-alt; + width: 100%; + padding: 10px 0; + + i { + margin-right: 5px; + } + + &:hover { + background-color: lighten(@bc-primary-btn-bg, 10%); + } + + &:active { + background-color: @bc-primary-btn-bg-down; + box-shadow: inset 0 1px 0 @bc-shadow-small; + } + } + + &.btn-default { + background-color: @bc-btn-bg; + border: 1px solid @bc-btn-border; + box-shadow: inset 0 1px 0 @bc-highlight; + + &:hover { + background-color: lighten(@bc-btn-bg, 10%); + } + + &:active { + background-color: @bc-btn-bg-down; + box-shadow: inset 0 1px 0 @bc-shadow-small; + } + } + + &.btn-link { + background: none; + border: none; + box-shadow: none; + color: @bc-text-thin-quiet; + + i { + margin-right: 5px; + } + + &:hover { + color: @bc-text; + text-decoration: none; + } + } + } +} + +.dark .profile-popup { + background-color: @dark-bc-menu-bg; + color: @dark-bc-menu-text; + box-shadow: 0 3px 9px @dark-bc-shadow; + + .popup-header { + border-bottom: 1px solid @dark-bc-menu-separator; + } + + .popup-title { + color: @dark-bc-menu-text; + } + + .user-avatar { + background-color: @dark-bc-primary-btn-bg; + color: @dark-bc-text-alt; + } + + .user-name { + color: @dark-bc-menu-text; + } + + .progress-bar { + background-color: @dark-bc-input-bg; + + .progress-fill { + background-color: @dark-bc-primary-btn-bg; + } + } + + .btn { + &.btn-primary { + background-color: @dark-bc-primary-btn-bg; + border: 1px solid @dark-bc-primary-btn-border; + box-shadow: inset 0 1px 0 @dark-bc-highlight; + color: @dark-bc-text-alt; + + &:hover { + background-color: lighten(@dark-bc-primary-btn-bg, 10%); + } + + &:active { + background-color: @dark-bc-primary-btn-bg-down; + box-shadow: inset 0 1px 0 @dark-bc-shadow-small; + } + } + + &.btn-default { + background-color: @dark-bc-btn-bg; + border: 1px solid @dark-bc-btn-border; + box-shadow: inset 0 1px 0 @dark-bc-highlight; + color: @dark-bc-text; + + &:hover { + background-color: lighten(@dark-bc-btn-bg, 10%); + } + + &:active { + background-color: @dark-bc-btn-bg-down; + box-shadow: inset 0 1px 0 @dark-bc-shadow-small; + } + } + + &.btn-link { + color: @dark-bc-text-thin-quiet; + + &:hover { + color: @dark-bc-text; + } + } + } +} + +@keyframes user-profile-dropdown-animation { + 0% { opacity: 0.5; transform: translate3d(0, 0, 0) scale(0.5); } + 100% { opacity: 1; transform: translate3d(0, 0, 0) scale(1); } +} diff --git a/src/styles/brackets.less b/src/styles/brackets.less index 6b4b43a611..2d09b8f41d 100644 --- a/src/styles/brackets.less +++ b/src/styles/brackets.less @@ -44,6 +44,7 @@ @import "Extn-TabBar.less"; @import "Extn-DisplayShortcuts.less"; @import "Extn-CSSColorPreview.less"; +@import "Extn-UserProfile.less"; /* Overall layout */ @@ -668,8 +669,8 @@ a, img { background-image: url("images/share-fill.svg"); } -.help { - background-image: url("images/discuss-icon.svg"); +.user { + background-image: url("images/circle-user-solid.svg"); } #editor-holder { diff --git a/src/styles/images/circle-user-solid.svg b/src/styles/images/circle-user-solid.svg new file mode 100644 index 0000000000..836691525d --- /dev/null +++ b/src/styles/images/circle-user-solid.svg @@ -0,0 +1 @@ +