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 @@
## 2026-06-20 - Async Form Submission Loading States
**Learning:** Implementing loading states on form submission buttons significantly improves user feedback and prevents double-submissions. However, accessing the button that triggered the event must be done carefully using `e.submitter` instead of relying on generic button selectors, and a reliable cleanup process (like a `finally` block) must be used to restore the button's state even if the async operation fails.
**Action:** When adding async loading states to form submissions in vanilla JavaScript, always use `e.submitter` conditionally, handle UI updates via explicit state manipulation, and rely on `try/finally` logic to ensure the button is re-enabled regardless of success or error outcomes.
6 changes: 6 additions & 0 deletions web-demo/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,12 @@ body {
transition: all 0.3s ease;
}

.btn:disabled {
opacity: 0.7;
cursor: not-allowed;
transform: none;
}

.btn-primary {
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
color: white;
Expand Down
24 changes: 24 additions & 0 deletions web-demo/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,13 @@ class ClimaAI {
e.preventDefault();
const email = document.getElementById('loginEmail').value;
const password = document.getElementById('loginPassword').value;
const submitBtn = e.submitter;
const originalContent = submitBtn ? submitBtn.innerHTML : '';

if (submitBtn) {
submitBtn.disabled = true;
submitBtn.innerHTML = 'Logging in...';
}

try {
this.showToast('Logging in...', 'info');
Expand All @@ -155,6 +162,11 @@ class ClimaAI {
this.checkSubscription();
} catch (error) {
this.showToast(error.message || 'Login failed', 'error');
} finally {
if (submitBtn) {
submitBtn.disabled = false;
submitBtn.innerHTML = originalContent;
}
}
}

Expand All @@ -163,6 +175,13 @@ class ClimaAI {
const name = document.getElementById('registerName').value;
const email = document.getElementById('registerEmail').value;
const password = document.getElementById('registerPassword').value;
const submitBtn = e.submitter;
const originalContent = submitBtn ? submitBtn.innerHTML : '';

if (submitBtn) {
submitBtn.disabled = true;
submitBtn.innerHTML = 'Creating account...';
}

try {
this.showToast('Creating account...', 'info');
Expand All @@ -174,6 +193,11 @@ class ClimaAI {
this.checkSubscription();
} catch (error) {
this.showToast(error.message || 'Registration failed', 'error');
} finally {
if (submitBtn) {
submitBtn.disabled = false;
submitBtn.innerHTML = originalContent;
}
}
}

Expand Down