diff --git a/src/gui/src/UI/Dashboard/TabAccount.js b/src/gui/src/UI/Dashboard/TabAccount.js new file mode 100644 index 00000000..7210b031 --- /dev/null +++ b/src/gui/src/UI/Dashboard/TabAccount.js @@ -0,0 +1,219 @@ +/** + * Copyright (C) 2024-present Puter Technologies Inc. + * + * This file is part of Puter. + * + * Puter is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import UIWindowChangePassword from '../UIWindowChangePassword.js'; +import UIWindowChangeEmail from '../Settings/UIWindowChangeEmail.js'; +import UIWindowChangeUsername from '../UIWindowChangeUsername.js'; +import UIWindowConfirmUserDeletion from '../Settings/UIWindowConfirmUserDeletion.js'; +import UIWindowManageSessions from '../UIWindowManageSessions.js'; +import UIWindow from '../UIWindow.js'; + +const TabAccount = { + id: 'account', + label: i18n('account'), + icon: ``, + + html () { + let h = ''; + h += '
'; + + // Profile section header + h += '
'; + h += '

' + i18n('account') + '

'; + h += '

Manage your account settings and profile

'; + h += '
'; + + // Profile picture card + h += '
'; + h += '
'; + h += `
`; + h += '
'; + h += ''; + h += '
'; + h += '
'; + h += '
'; + h += `

${html_encode(window.user?.username || 'User')}

`; + h += `

${html_encode(window.user?.email || '')}

`; + h += 'Click the avatar to change your profile picture'; + h += '
'; + h += '
'; + h += '
'; + + // Account settings cards + h += '
'; + + // Username card + h += '
'; + h += '
'; + h += '
'; + h += ''; + h += '
'; + h += '
'; + h += `${i18n('username')}`; + h += `${html_encode(window.user.username)}`; + h += '
'; + h += '
'; + h += ``; + h += '
'; + + // Password card (only for non-temp users) + if ( !window.user.is_temp ) { + h += '
'; + h += '
'; + h += '
'; + h += ''; + h += '
'; + h += '
'; + h += `${i18n('password')}`; + h += '••••••••'; + h += '
'; + h += '
'; + h += ``; + h += '
'; + } + + // Email card (only if email exists) + if ( window.user.email ) { + h += '
'; + h += '
'; + h += '
'; + h += ''; + h += '
'; + h += '
'; + h += `${i18n('email')}`; + h += `${html_encode(window.user.email)}`; + h += '
'; + h += '
'; + h += ``; + h += '
'; + } + + h += '
'; // end settings-grid + + // Danger zone + h += '
'; + h += '

Danger Zone

'; + h += '
'; + h += '
'; + h += '
'; + h += `${i18n('delete_account')}`; + h += 'Permanently delete your account and all associated data. This action cannot be undone.'; + h += '
'; + h += '
'; + h += ``; + h += '
'; + h += '
'; + + h += '
'; // end dashboard-tab-content + return h; + }, + + init ($el_window) { + $el_window.find('.dashboard-section-account .change-password').on('click', function (e) { + UIWindowChangePassword({ + window_options: { + parent_uuid: $el_window.attr('data-element_uuid'), + disable_parent_window: true, + parent_center: true, + }, + }); + }); + $el_window.find('.dashboard-section-account .change-username').on('click', function (e) { + UIWindowChangeUsername({ + window_options: { + parent_uuid: $el_window.attr('data-element_uuid'), + disable_parent_window: true, + parent_center: true, + }, + }); + }); + $el_window.find('.dashboard-section-account .change-email').on('click', function (e) { + UIWindowChangeEmail({ + window_options: { + parent_uuid: $el_window.attr('data-element_uuid'), + disable_parent_window: true, + parent_center: true, + }, + }); + }); + $el_window.find('.dashboard-section-account .manage-sessions').on('click', function (e) { + UIWindowManageSessions({ + window_options: { + parent_uuid: $el_window.attr('data-element_uuid'), + disable_parent_window: true, + parent_center: true, + }, + }); + }); + $el_window.find('.dashboard-section-account .delete-account').on('click', function (e) { + UIWindowConfirmUserDeletion({ + window_options: { + parent_uuid: $el_window.attr('data-element_uuid'), + disable_parent_window: true, + parent_center: true, + }, + }); + }); + $el_window.find('.dashboard-section-account .change-profile-picture').on('click', async function (e) { + // open dialog + UIWindow({ + path: `/${ window.user.username }/Desktop`, + // this is the uuid of the window to which this dialog will return + parent_uuid: $el_window.attr('data-element_uuid'), + allowed_file_types: ['.png', '.jpg', '.jpeg'], + show_maximize_button: false, + show_minimize_button: false, + title: 'Open', + is_dir: true, + is_openFileDialog: true, + selectable_body: false, + }); + }); + $el_window.on('file_opened', async function (e) { + let selected_file = Array.isArray(e.detail) ? e.detail[0] : e.detail; + // set profile picture + const profile_pic = await puter.fs.read(selected_file.path); + // blob to base64 + const reader = new FileReader(); + reader.readAsDataURL(profile_pic); + reader.onloadend = function () { + // resizes the image to 150x150 + const img = new Image(); + img.src = reader.result; + img.onload = function () { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + canvas.width = 150; + canvas.height = 150; + ctx.drawImage(img, 0, 0, 150, 150); + const base64data = canvas.toDataURL('image/png'); + // update profile picture + $el_window.find('.dashboard-profile-avatar').css('background-image', `url(${ html_encode(base64data) })`); + $('.profile-image').css('background-image', `url(${ html_encode(base64data) })`); + $('.profile-image').addClass('profile-image-has-picture'); + // update profile picture + update_profile(window.user.username, { picture: base64data }); + }; + }; + }); + }, +}; + +export default TabAccount; + diff --git a/src/gui/src/UI/Dashboard/UIDashboard.js b/src/gui/src/UI/Dashboard/UIDashboard.js index 8f7380f8..2a8cd11f 100644 --- a/src/gui/src/UI/Dashboard/UIDashboard.js +++ b/src/gui/src/UI/Dashboard/UIDashboard.js @@ -30,6 +30,7 @@ import TabHome from './TabHome.js'; import TabFiles from './TabFiles.js'; import TabApps from './TabApps.js'; import TabUsage from './TabUsage.js'; +import TabAccount from './TabAccount.js'; // Registry of all available tabs const tabs = [ @@ -37,6 +38,7 @@ const tabs = [ // TabApps, // TabFiles, TabUsage, + TabAccount, ]; async function UIDashboard (options) { diff --git a/src/gui/src/css/style.css b/src/gui/src/css/style.css index 92d93212..11a11405 100644 --- a/src/gui/src/css/style.css +++ b/src/gui/src/css/style.css @@ -6409,4 +6409,282 @@ html.dark-mode .usage-table-show-less:hover { .bento-card-fancy-subtitle { font-size: 12px; } +} + +/* ============================================== */ +/* Dashboard Account Tab */ +/* ============================================== */ + +.dashboard-tab-content { + max-width: 700px; + margin: 0 auto; + padding: 8px 0; +} + +.dashboard-section-header { + margin-bottom: 28px; +} + +.dashboard-section-header h2 { + margin: 0 0 6px 0; + font-size: 26px; + font-weight: 700; + color: #1e293b; + letter-spacing: -0.02em; +} + +.dashboard-section-header p { + margin: 0; + font-size: 15px; + color: #64748b; +} + +.dashboard-card { + background: #fff; + border-radius: 16px; + border: 1px solid rgba(0, 0, 0, 0.06); + box-shadow: + 0 1px 3px rgba(0, 0, 0, 0.04), + 0 4px 12px rgba(0, 0, 0, 0.03); +} + +/* Profile card */ +.dashboard-profile-card { + padding: 32px; + margin-bottom: 24px; + background: + radial-gradient(circle at 100% 0%, rgba(99, 102, 241, 0.06) 0%, transparent 50%), + radial-gradient(circle at 0% 100%, rgba(168, 85, 247, 0.04) 0%, transparent 40%), + #fff; +} + +.dashboard-profile-picture-section { + display: flex; + align-items: center; + gap: 24px; +} + +.dashboard-profile-avatar { + width: 96px; + height: 96px; + border-radius: 50%; + background-size: cover; + background-position: center; + background-color: #e2e8f0; + flex-shrink: 0; + position: relative; + cursor: pointer; + transition: transform 0.2s ease; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); +} + +.dashboard-profile-avatar:hover { + transform: scale(1.05); +} + +.dashboard-profile-avatar-overlay { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.5); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + transition: opacity 0.2s ease; +} + +.dashboard-profile-avatar:hover .dashboard-profile-avatar-overlay { + opacity: 1; +} + +.dashboard-profile-avatar-overlay svg { + width: 28px; + height: 28px; + color: white; +} + +.dashboard-profile-info { + display: flex; + flex-direction: column; + gap: 4px; +} + +.dashboard-profile-info h3 { + margin: 0; + font-size: 22px; + font-weight: 700; + color: #1e293b; +} + +.dashboard-profile-info p { + margin: 0; + font-size: 14px; + color: #64748b; +} + +.dashboard-profile-hint { + font-size: 12px; + color: #94a3b8; + margin-top: 4px; +} + +/* Settings grid */ +.dashboard-settings-grid { + display: flex; + flex-direction: column; + gap: 12px; + margin-bottom: 32px; +} + +.dashboard-settings-card { + display: flex; + align-items: center; + justify-content: space-between; + padding: 20px 24px; + gap: 16px; +} + +.dashboard-settings-card-content { + display: flex; + align-items: center; + gap: 16px; + flex: 1; + min-width: 0; +} + +.dashboard-settings-card-icon { + width: 44px; + height: 44px; + border-radius: 12px; + background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.dashboard-settings-card-icon svg { + width: 22px; + height: 22px; + color: #475569; +} + +.dashboard-settings-card-info { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; +} + +.dashboard-settings-card-info strong { + font-size: 14px; + font-weight: 600; + color: #1e293b; +} + +.dashboard-settings-card-info span { + font-size: 14px; + color: #64748b; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.dashboard-settings-card .button { + flex-shrink: 0; +} + +/* Danger zone */ +.dashboard-danger-zone { + margin-top: 40px; + padding-top: 24px; + border-top: 1px solid #fee2e2; +} + +.dashboard-danger-zone h3 { + margin: 0 0 16px 0; + font-size: 14px; + font-weight: 600; + color: #dc2626; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.dashboard-danger-card { + display: flex; + align-items: center; + justify-content: space-between; + padding: 20px 24px; + gap: 24px; + border-color: #fecaca; + background: linear-gradient(135deg, #fef2f2 0%, #fff 100%); +} + +.dashboard-danger-card-content { + flex: 1; + min-width: 0; +} + +.dashboard-danger-card-info { + display: flex; + flex-direction: column; + gap: 4px; +} + +.dashboard-danger-card-info strong { + font-size: 15px; + font-weight: 600; + color: #dc2626; +} + +.dashboard-danger-card-info span { + font-size: 13px; + color: #64748b; + line-height: 1.5; +} + +/* Responsive styles for Account tab */ +@media (max-width: 768px) { + .dashboard-tab-content { + padding: 0 16px; + } + + .dashboard-section-header h2 { + font-size: 22px; + } + + .dashboard-profile-card { + padding: 24px; + } + + .dashboard-profile-picture-section { + flex-direction: column; + text-align: center; + } + + .dashboard-profile-info { + align-items: center; + } + + .dashboard-settings-card { + flex-direction: column; + align-items: stretch; + gap: 16px; + padding: 16px 20px; + } + + .dashboard-settings-card .button { + width: 100%; + } + + .dashboard-danger-card { + flex-direction: column; + align-items: stretch; + gap: 16px; + } + + .dashboard-danger-card .button { + width: 100%; + } } \ No newline at end of file