refactor: make theme service non-blocking and fix captcha issues (#1564)

Replaced inline styles with CSS classes for the captcha modal to improve maintainability and readability. Adjusted JavaScript to accommodate the new structure and ensure proper functionality. Enhanced error handling and loading state management during the captcha verification process.
This commit is contained in:
Nariman Jelveh
2025-09-14 20:20:19 -07:00
committed by GitHub
parent 42abfcc5e7
commit 735f16725d
3 changed files with 168 additions and 139 deletions

View File

@@ -2615,6 +2615,106 @@ label {
padding-bottom: 5px !important;
}
/*****************************************************
* Captcha
*****************************************************/
.captcha-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 10000;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.captcha-modal .modal-content {
background-color: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
max-width: 400px;
width: 90%;
text-align: center;
position: relative;
}
.captcha-modal .captcha-logo {
width: 40px;
height: 40px;
margin: 0 auto 15px;
display: block;
padding: 15px;
background-color: blue;
border-radius: 8px;
}
.captcha-modal .captcha-title {
margin: 0;
color: #1f2937;
font-size: 24px;
font-weight: 500;
line-height: 1.2;
-webkit-font-smoothing: antialiased;
}
.captcha-modal .captcha-description {
margin: 10px 0 0 0;
color: #6b7280;
font-size: 14px;
line-height: 1.4;
}
.captcha-modal .captcha-container {
display: flex;
justify-content: center;
margin: 20px 0;
min-height: 80px;
align-items: center;
}
.captcha-modal .loading-state {
display: none;
margin: 20px 0;
color: #6b7280;
font-size: 16px;
height: 80px;
line-height: 70px;
}
.captcha-modal .loading-state-icon {
display: inline-block;
width: 20px;
height: 20px;
border: 2px solid #e5e7eb;
border-radius: 50%;
border-top: 2px solid #3b82f6;
animation: spin 1s linear infinite;
margin-right: 10px;
vertical-align: middle;
}
.captcha-modal .error-message {
display: none;
color: #dc2626;
font-size: 14px;
margin-top: 15px;
padding: 10px;
background-color: #fef2f2;
border: 1px solid #fecaca;
border-radius: 6px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/*****************************************************
* Task Manager
*****************************************************/

View File

@@ -169,103 +169,25 @@ window.showTurnstileChallenge = function(options) {
// Create modal HTML
let modalHtml = `
<div id="${modalId}" class="modal" style="
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 10000;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
">
<div class="modal-content" style="
background-color: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
max-width: 400px;
width: 90%;
text-align: center;
position: relative;
">
<div id="${modalId}" class="captcha-modal">
<div class="modal-content">
<div class="modal-header" style="margin-bottom: 20px;">
<img src="${window.icons['logo-white.svg']}" style="
width: 40px;
height: 40px;
margin: 0 auto 15px;
display: block;
padding: 15px;
background-color: blue;
border-radius: 8px;
">
<h2 style="
margin: 0;
color: #1f2937;
font-size: 24px;
font-weight: 600;
line-height: 1.2;
">Welcome to Puter</h2>
<p style="
margin: 10px 0 0 0;
color: #6b7280;
font-size: 14px;
line-height: 1.4;
">Please complete the security verification to continue</p>
<img src="${window.icons['logo-white.svg']}" class="captcha-logo">
<h2 class="captcha-title">Welcome to Puter!</h2>
</div>
<div class="turnstile-container" style="
display: flex;
justify-content: center;
margin: 20px 0;
min-height: 80px;
align-items: center;
">
<div id="turnstile-widget-${modalId}" class="cf-turnstile" data-sitekey="${siteKey}"></div>
<div class="captcha-container">
<div id="captcha-widget-${modalId}" data-sitekey="${siteKey}"></div>
</div>
<div class="loading-state" style="
display: none;
margin: 20px 0;
color: #6b7280;
font-size: 14px;
">
<div style="
display: inline-block;
width: 20px;
height: 20px;
border: 2px solid #e5e7eb;
border-radius: 50%;
border-top: 2px solid #3b82f6;
animation: spin 1s linear infinite;
margin-right: 10px;
vertical-align: middle;
"></div>
<div class="loading-state">
<div class="loading-state-icon"></div>
Setting up your account...
</div>
<div class="error-message" style="
display: none;
color: #dc2626;
font-size: 14px;
margin-top: 15px;
padding: 10px;
background-color: #fef2f2;
border: 1px solid #fecaca;
border-radius: 6px;
"></div>
<div class="error-message"></div>
</div>
</div>
<style>
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
`;
// Add modal to DOM
@@ -273,7 +195,7 @@ window.showTurnstileChallenge = function(options) {
const modal = document.getElementById(modalId);
const errorMessage = modal.querySelector('.error-message');
const loadingState = modal.querySelector('.loading-state');
const turnstileContainer = modal.querySelector('.turnstile-container');
const turnstileContainer = modal.querySelector('.captcha-container');
// Initialize Turnstile widget
const initTurnstile = () => {
@@ -283,21 +205,20 @@ window.showTurnstileChallenge = function(options) {
}
try {
window.turnstile.render(`#turnstile-widget-${modalId}`, {
window.turnstile.render(`#captcha-widget-${modalId}`, {
sitekey: siteKey,
callback: function(token) {
window.turnstile_success_ts = Date.now();
// Show loading state
turnstileContainer.style.display = 'none';
loadingState.style.display = 'block';
$(turnstileContainer).hide();
$(loadingState).show();
// Call success callback
options.onSuccess(token);
// Remove modal after a brief delay
setTimeout(() => {
modal.remove();
resolve();
}, 500);
// resolve the promise
resolve();
},
'expired-callback': function() {
showError('Verification expired. Please try again.');
@@ -1069,20 +990,26 @@ window.initgui = async function(options){
contentType: "application/json",
data: JSON.stringify(requestData),
success: async function (data){
// if this is a popup, hide the spinner, make sure it was visible for at least 2 seconds
if(window.embedded_in_popup){
let spinner_duration = (Date.now() - spinner_init_ts);
setTimeout(() => {
window.update_auth_data(data.token, data.user);
document.dispatchEvent(new Event("login", { bubbles: true}));
puter.ui.hideSpinner();
}, spinner_duration > 2000 ? 10 : 2000 - spinner_duration);
setTimeout(() => {
$('.captcha-modal').fadeOut(200, function(){
$(this).remove();
return;
}else{
window.update_auth_data(data.token, data.user);
document.dispatchEvent(new Event("login", { bubbles: true}));
}
// if this is a popup, hide the spinner, make sure it was visible for at least 2 seconds
if(window.embedded_in_popup){
let spinner_duration = (Date.now() - spinner_init_ts);
setTimeout(() => {
window.update_auth_data(data.token, data.user);
document.dispatchEvent(new Event("login", { bubbles: true}));
puter.ui.hideSpinner();
}, spinner_duration > 2000 ? 10 : 2000 - spinner_duration);
return;
}else{
window.update_auth_data(data.token, data.user);
document.dispatchEvent(new Event("login", { bubbles: true}));
}
});
}, (Date.now() - window.turnstile_success_ts) > 2000 ? 10 : 2000 - (Date.now() - window.turnstile_success_ts));
},
error: async (err) => {
UIAlert({

View File

@@ -51,13 +51,36 @@ export class ThemeService extends Service {
this.save_cooldown_ = undefined;
let data = undefined;
try {
data = await puter.fs.read(PUTER_THEME_DATA_FILENAME);
if ( typeof data === 'object' ) {
data = await data.text();
// Load theme data using .then() for non-blocking operation
puter.fs.read(PUTER_THEME_DATA_FILENAME).then(async (data) => {
try {
if ( typeof data === 'object' ) {
data = await data.text();
}
if ( data ) {
try {
data = JSON.parse(data.toString());
if ( data && data.colors ) {
this.state = {
...this.state,
...data.colors,
};
this.reload_();
}
} catch (e) {
console.error(e);
UIAlert({
title: 'Error loading theme data',
message: `Could not parse "${PUTER_THEME_DATA_FILENAME}": ` +
e.message,
});
}
}
} catch (e) {
console.error('Error processing theme data:', e);
}
} catch (e) {
}).catch((e) => {
if ( e.code !== 'subject_does_not_exist' ) {
// TODO: once we have an event log,
// log this error to the event log
@@ -66,28 +89,7 @@ export class ThemeService extends Service {
// We don't show an alert because it's likely
// other things also aren't working.
}
}
if ( data ) try {
data = JSON.parse(data.toString());
} catch (e) {
data = undefined;
console.error(e);
UIAlert({
title: 'Error loading theme data',
message: `Could not parse "${PUTER_THEME_DATA_FILENAME}": ` +
e.message,
});
}
if ( data && data.colors ) {
this.state = {
...this.state,
...data.colors,
};
this.reload_();
}
});
}
reset () {