Files
puter/src/gui/src/UI/UIWindowSessionList.js
T
jelveh 085ac116c7 fix(gui): keep app landings through signup, and unstack the session picker
Signing up from a direct /app/<name> landing redirected to '/' on
success, silently dropping the app the URL asked for — the deep link's
launch (and its dashboard intro) never happened. Signup now mirrors
login's pathname-preserving redirect, but only for app-landing routes
(/app/<name>, /desktop/app/<name>): every other route keeps the
historical '/', notably /action/signup, where returning to the same
path would just show the signup form again. The query string stays
dropped, matching login's credential-leak hygiene. This covers all
three signup entry points reachable from a landing: the login cover's
"Sign up", the session picker's "Create Account", and the
must_login_or_signup fallback.

The session picker (UIWindowSessionList) also left itself on top of
the cover windows its two links open, hiding their username fields:
"Create Account" tried to close the picker via the LOGIN window's
c2a selector (which matches nothing in the picker), and "Log Into
Another Account" never closed it at all. Both now close the picker —
in the reload flows only: the no-reload (popup) flows keep it open,
where it doubles as the fallback UI when the login/signup window is
abandoned mid-flow.

Picking an account was already correct (location.reload() keeps the
landing URL); with these fixes all three picker paths, plain login,
and signup all return to the app landing, where the boot replays the
launch and its intro.
2026-08-07 11:51:44 -07:00

186 lines
8.1 KiB
JavaScript

/*
* 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 <https://www.gnu.org/licenses/>.
*/
import UIWindow from './UIWindow.js';
import UIWindowLogin from './UIWindowLogin.js';
import UIWindowSignup from './UIWindowSignup.js';
async function UIWindowSessionList (options) {
options = options ?? {};
options.reload_on_success = options.reload_on_success ?? true;
return new Promise(async (resolve) => {
let h = '';
h += '<div style="margin:10px;">';
// loading indicator with spinner
h += `<div class="loading" style="z-index: 999999999;">
<div style="display: flex; flex-direction: column; align-items: center; gap: 15px;">
<div style="
width: 40px;
height: 40px;
border: 3px solid #e0e0e0;
border-top-color: #3b82f6;
border-radius: 50%;
animation: spin 0.8s linear infinite;
"></div>
<div style="
font-size: 15px;
color: #555;
font-weight: 500;
">${i18n('signing_in')}</div>
</div>
</div>`;
// session list
h += '<div class="hide-scrollbar" style="overflow-y: scroll; max-width: 400px; margin: 0 auto;">';
h += `<h1 style="text-align: center; font-size: 18px; font-weight: normal; color: #757575; margin-bottom: 30px;"><img src="${window.icons['logo-white.svg']}" style="padding: 4px; background-color: blue; border-radius: 5px; width: 25px; box-sizing: border-box; margin-bottom: -6px; margin-right: 6px;">${i18n('sign_in_with_puter')}</h1>`;
for ( let index = 0; index < window.logged_in_users.length; index++ ) {
const l_user = window.logged_in_users[index];
h += `<div data-uuid="${l_user.uuid}" class="session-entry" style="display: flex; padding: 15px 10px;">`;
// profile picture
h += `<div class="profile-picture" style="background-color: #cbced1; width: 30px; height: 30px; margin:0; margin-right: 10px; background-image: url('${l_user.profile.picture ?? window.icons['profile.svg']}');"></div>`;
h += `<div style="display: flex; align-items: center;">${l_user.username}</div>`;
h += '</div>';
}
h += '</div>';
// c2a
h += `<div style="margin-top: 20px; margin-bottom: 20px; text-align:center;"><span class="login-c2a-session-list">Log Into Another Account</span> &bull; <span class="signup-c2a-session-list">${i18n('create_account')}</span></div>`;
h += '</div>';
const el_window = await UIWindow({
title: i18n('window_title_session_list'),
app: 'session-list',
single_instance: true,
icon: null,
uid: null,
is_dir: false,
body_content: h,
has_head: false,
selectable_body: false,
draggable_body: options.draggable_body ?? true,
allow_context_menu: false,
is_resizable: false,
is_droppable: false,
init_center: true,
allow_native_ctxmenu: false,
allow_user_select: false,
width: 350,
height: 'auto',
dominant: true,
show_in_taskbar: false,
update_window_url: false,
cover_page: options.cover_page ?? false,
window_class: 'window-session-list',
body_css: {
width: 'initial',
height: '100%',
'background-color': 'rgb(245 247 249)',
'backdrop-filter': 'blur(3px)',
'display': 'flex',
'flex-direction': 'column',
'justify-content': 'center',
},
});
$(el_window).find('.login-c2a-session-list').on('click', async function (e) {
// The login window is a centered cover and this picker would
// otherwise float on top of it, hiding its username field. Only
// the reload flows close it: in the no-reload (popup) flows the
// picker doubles as the fallback UI when the login window is
// abandoned, and the resolve() below still needs a live promise
// chain either way.
if ( options.reload_on_success ) $(el_window).close();
const login = await UIWindowLogin({
referrer: options.referrer,
reload_on_success: options.reload_on_success,
cover_page: options.cover_page ?? false,
has_head: options.has_head,
send_confirmation_code: options.send_confirmation_code,
window_options: {
has_head: false,
cover_page: options.cover_page ?? false,
},
});
if ( login ) {
if ( options.reload_on_success ) {
// disable native browser exit confirmation
window.onbeforeunload = null;
// refresh
location.reload();
} else {
resolve(login);
}
}
});
$(el_window).find('.signup-c2a-session-list').on('click', async function (e) {
// Same picker-over-cover overlap as the login c2a above, same
// reload-flows-only close. (The selector this replaces —
// '.signup-c2a-clickable', the LOGIN window's c2a class —
// matched nothing in this window and closed nothing.)
if ( options.reload_on_success ) $(el_window).close();
// create Signup window
const signup = await UIWindowSignup({
referrer: options.referrer,
reload_on_success: options.reload_on_success,
send_confirmation_code: options.send_confirmation_code,
window_options: {
has_head: false,
cover_page: options.cover_page ?? false,
},
});
if ( signup ) {
if ( options.reload_on_success ) {
// disable native browser exit confirmation
window.onbeforeunload = null;
// refresh
location.reload();
} else {
resolve(signup);
}
}
});
$(el_window).find('.session-entry').on('click', function (e) {
$(el_window).find('.loading').css({ display: 'flex' });
setTimeout(async () => {
let selected_uuid = $(this).attr('data-uuid');
let selected_user;
for ( let index = 0; index < window.logged_in_users.length; index++ ) {
const l_user = window.logged_in_users[index];
if ( l_user.uuid === selected_uuid ) {
selected_user = l_user;
}
}
// new logged in user
await window.update_auth_data(selected_user.auth_token, selected_user);
if ( options.reload_on_success ) {
// disable native browser exit confirmation
window.onbeforeunload = null;
// refresh
location.reload();
} else {
resolve(true);
}
}, 500);
});
});
}
export default UIWindowSessionList;