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