diff --git a/src/docs/src/Auth/getProfile.md b/src/docs/src/Auth/getProfile.md
index 59abcc5e6..c9f34e9b7 100644
--- a/src/docs/src/Auth/getProfile.md
+++ b/src/docs/src/Auth/getProfile.md
@@ -4,7 +4,7 @@ description: Read a user's profile, returning null when it is not available.
platforms: [websites, apps, nodejs, workers]
---
-Returns a user's profile: their picture, display name, and bio. A user can always read their own profile. Another user's profile is available only while that user is on a paid plan; otherwise the method resolves to `null`, the same as for a user who does not exist.
+Returns a user's profile: their picture, display name, and bio. Users set these on the [Account page](https://puter.com/dashboard#account) of their Puter dashboard. A user can always read their own profile. Another user's profile is available only while that user is on a paid plan; otherwise the method resolves to `null`, the same as for a user who does not exist.
## Syntax
diff --git a/src/gui/src/UI/Dashboard/TabAccount.js b/src/gui/src/UI/Dashboard/TabAccount.js
index 92c1c508f..a677c21a5 100644
--- a/src/gui/src/UI/Dashboard/TabAccount.js
+++ b/src/gui/src/UI/Dashboard/TabAccount.js
@@ -22,6 +22,7 @@ import UIWindowChangeEmail from '../Settings/UIWindowChangeEmail.js';
import UIWindowChangeUsername from '../UIWindowChangeUsername.js';
import UIWindowConfirmUserDeletion from '../Settings/UIWindowConfirmUserDeletion.js';
import UIWindowCopyToken from '../UIWindowCopyToken.js';
+import UIWindowEditProfile from '../UIWindowEditProfile.js';
import UIWindow from '../UIWindow.js';
import UIProfilePictureCropModal from './UIProfilePictureCropModal.js';
import { isOrgSeat, orgSeatTeamName } from './orgSeat.js';
@@ -59,6 +60,20 @@ const TabAccount = {
// Account settings cards
h += '
';
+ // Display name and bio card
+ h += '
';
+ h += '
';
+ h += '
';
+ h += '';
+ h += '
';
+ h += '
';
+ h += `${i18n('profile')}`;
+ h += `${html_encode(window.user?.profile?.displayName || i18n('profile_empty', [], false))}`;
+ h += '
';
+ h += '
';
+ h += ``;
+ h += '
';
+
// Username card
h += '
';
h += '
';
@@ -163,6 +178,22 @@ const TabAccount = {
},
init ($el_window) {
+ $el_window.find('.dashboard-section-account .edit-profile').on('click', function (e) {
+ UIWindowEditProfile({
+ window_options: {
+ parent_uuid: $el_window.attr('data-element_uuid'),
+ backdrop: true,
+ close_on_backdrop_click: true,
+ parent_center: true,
+ stay_on_top: true,
+ has_head: false,
+ },
+ onSave: (profile) => {
+ $el_window.find('.dashboard-section-account .dashboard-profile-display-name')
+ .text(profile.displayName || i18n('profile_empty', [], false));
+ },
+ });
+ });
$el_window.find('.dashboard-section-account .change-password').on('click', function (e) {
UIWindowChangePassword({
window_options: {
diff --git a/src/gui/src/UI/UIWindowEditProfile.js b/src/gui/src/UI/UIWindowEditProfile.js
new file mode 100644
index 000000000..60a21d5d8
--- /dev/null
+++ b/src/gui/src/UI/UIWindowEditProfile.js
@@ -0,0 +1,116 @@
+/*
+ * 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 UIWindow from './UIWindow.js';
+
+// Keep in sync with ProfileService on the backend.
+const DISPLAY_NAME_MAX_LENGTH = 64;
+const BIO_MAX_LENGTH = 280;
+
+/**
+ * Edits the signed-in user's display name and bio. `options.onSave` receives
+ * the saved profile.
+ */
+async function UIWindowEditProfile (options) {
+ options = options ?? {};
+
+ const internal_id = window.uuidv4();
+ let h = '';
+ h += '