Files
clinch/app/javascript/controllers/login_form_controller.js
Dan Milne bf104a9983
Some checks failed
CI / scan_ruby (push) Has been cancelled
CI / scan_js (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
CI / system-test (push) Has been cancelled
Fix CSP errors - migrate inline JS to stimulus controllers. Add a URL for applications so users can discover them
2025-11-04 17:06:53 +11:00

93 lines
3.0 KiB
JavaScript

import { Controller } from "@hotwired/stimulus"
// Handles login form UI changes based on WebAuthn availability
export default class extends Controller {
static targets = ["webauthnSection", "passwordSection", "statusMessage", "loadingOverlay"]
connect() {
// Listen for WebAuthn availability events from the webauthn controller
this.element.addEventListener('webauthn:webauthn-available', this.handleWebAuthnAvailable.bind(this));
// Listen for WebAuthn registration events (from profile page)
this.element.addEventListener('webauthn:passkey-registered', this.handlePasskeyRegistered.bind(this));
// Listen for authentication start/end to show/hide loading
document.addEventListener('webauthn:authenticate-start', this.showLoading.bind(this));
document.addEventListener('webauthn:authenticate-end', this.hideLoading.bind(this));
}
disconnect() {
// Clean up event listeners
document.removeEventListener('webauthn:authenticate-start', this.showLoading.bind(this));
document.removeEventListener('webauthn:authenticate-end', this.hideLoading.bind(this));
}
handleWebAuthnAvailable(event) {
const detail = event.detail;
if (!this.hasWebauthnSectionTarget || !this.hasPasswordSectionTarget) {
return;
}
if (detail.hasWebauthn) {
this.webauthnSectionTarget.classList.remove('hidden');
// If WebAuthn is required, hide password section
if (detail.requiresWebauthn) {
this.passwordSectionTarget.classList.add('hidden');
} else {
// Show both options with a divider
this.passwordSectionTarget.classList.add('border-t', 'pt-4', 'mt-4');
this.addOrDivider();
}
}
}
handlePasskeyRegistered(event) {
if (!this.hasStatusMessageTarget) {
return;
}
// Show success message
this.statusMessageTarget.className = 'mt-4 p-3 rounded-md bg-green-50 text-green-800 border border-green-200';
this.statusMessageTarget.textContent = 'Passkey registered successfully!';
this.statusMessageTarget.classList.remove('hidden');
// Hide after 3 seconds
setTimeout(() => {
this.statusMessageTarget.classList.add('hidden');
}, 3000);
}
showLoading() {
if (this.hasLoadingOverlayTarget) {
this.loadingOverlayTarget.classList.remove('hidden');
}
}
hideLoading() {
if (this.hasLoadingOverlayTarget) {
this.loadingOverlayTarget.classList.add('hidden');
}
}
addOrDivider() {
// Check if divider already exists
if (this.element.querySelector('.login-divider')) {
return;
}
const orDiv = document.createElement('div');
orDiv.className = 'relative my-4 login-divider';
orDiv.innerHTML = `
<div class="absolute inset-0 flex items-center">
<div class="w-full border-t border-gray-300"></div>
</div>
<div class="relative flex justify-center text-sm">
<span class="px-2 bg-white text-gray-500">Or</span>
</div>
`;
this.webauthnSectionTarget.parentNode.insertBefore(orDiv, this.passwordSectionTarget);
}
}