Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2024-05-14 - explicit loading states for asynchronous ui interactions
**Learning:** For asynchronous ui interactions without obvious native loading indicators (like form submissions or oauth redirects), explicit visual states are crucial. Users quickly assume the app is frozen or ignore repeated clicks if feedback isn't immediate, especially when network latency or simulated delays are involved.
**Action:** When implementing async form submissions or simulated delays (e.g. auth flows), always add a visual loading state (like '⏳ Loading...' and disabled status) to the interactive element, ensuring safe property access (`e.submitter`) and reliable state restoration in `finally` blocks by using awaitable Promises.
86 changes: 64 additions & 22 deletions web-demo/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,14 @@ class ClimaAI {
e.preventDefault();
const email = document.getElementById('loginEmail').value;
const password = document.getElementById('loginPassword').value;
let originalText = '';

if (e.submitter) {
originalText = e.submitter.innerHTML;
e.submitter.innerHTML = '⏳ Loading...';
e.submitter.disabled = true;
e.submitter.style.opacity = '0.7';
}

try {
this.showToast('Logging in...', 'info');
Expand All @@ -155,6 +163,12 @@ class ClimaAI {
this.checkSubscription();
} catch (error) {
this.showToast(error.message || 'Login failed', 'error');
} finally {
if (e.submitter) {
e.submitter.innerHTML = originalText;
e.submitter.disabled = false;
e.submitter.style.opacity = '1';
}
}
}

Expand All @@ -163,6 +177,14 @@ class ClimaAI {
const name = document.getElementById('registerName').value;
const email = document.getElementById('registerEmail').value;
const password = document.getElementById('registerPassword').value;
let originalText = '';

if (e.submitter) {
originalText = e.submitter.innerHTML;
e.submitter.innerHTML = '⏳ Loading...';
e.submitter.disabled = true;
e.submitter.style.opacity = '0.7';
}

try {
this.showToast('Creating account...', 'info');
Expand All @@ -174,6 +196,12 @@ class ClimaAI {
this.checkSubscription();
} catch (error) {
this.showToast(error.message || 'Registration failed', 'error');
} finally {
if (e.submitter) {
e.submitter.innerHTML = originalText;
e.submitter.disabled = false;
e.submitter.style.opacity = '1';
}
}
}

Expand All @@ -186,6 +214,15 @@ class ClimaAI {
}

async handleGoogleSignIn() {
const btn = document.getElementById('googleSignInBtn');
let originalText = '';
if (btn) {
originalText = btn.innerHTML;
btn.innerHTML = '⏳ Loading...';
btn.disabled = true;
btn.style.opacity = '0.7';
}

try {
this.showToast('πŸ” Signing in with Google...', 'info');

Expand All @@ -197,31 +234,36 @@ class ClimaAI {
// 5. Backend creates/updates user and returns JWT

// For demo purposes, we'll simulate successful OAuth with demo account
setTimeout(async () => {
try {
// Auto-login with demo account
const response = await api.login('demo@climaai.com', 'Test1234');
this.user = response.user;
this.showToast('βœ… Welcome! Signed in with Google', 'success');
this.showScreen('homeScreen');
this.loadWeatherData();
this.checkSubscription();
} catch (error) {
this.showToast('Google Sign-In succeeded! Welcome!', 'success');
// Create a demo user object
this.user = {
email: 'google-user@gmail.com',
full_name: 'Google User',
is_premium: true
};
this.isPremium = true;
this.showScreen('homeScreen');
this.loadWeatherData();
}
}, 1500); // Simulate OAuth redirect delay
await new Promise(resolve => setTimeout(resolve, 1500));
try {
// Auto-login with demo account
const response = await api.login('demo@climaai.com', 'Test1234');
this.user = response.user;
this.showToast('βœ… Welcome! Signed in with Google', 'success');
this.showScreen('homeScreen');
this.loadWeatherData();
this.checkSubscription();
} catch (error) {
this.showToast('Google Sign-In succeeded! Welcome!', 'success');
// Create a demo user object
this.user = {
email: 'google-user@gmail.com',
full_name: 'Google User',
is_premium: true
};
this.isPremium = true;
this.showScreen('homeScreen');
this.loadWeatherData();
}

} catch (error) {
this.showToast(error.message || 'Google Sign-In failed', 'error');
} finally {
if (btn) {
btn.innerHTML = originalText;
btn.disabled = false;
btn.style.opacity = '1';
}
}
}

Expand Down