--- a/onboarding.js +++ b/onboarding.js @@ -1,37 +1,355 @@ -// doany.ai workspace onboarding — current production JS - -function nextStep(stepNumber) { - // Hide all steps - document.querySelectorAll('.onboarding-step').forEach(function(el) { - el.classList.remove('active'); - }); - - // Show target step - var target = document.getElementById('step-' + stepNumber); - if (target) { - target.classList.add('active'); - } - - // No validation before advancing - // No progress indicator update - // No analytics tracking - // No transition animation -} - -// No form validation -// No inline error messages -// No loading state when submitting -// No error handling for failed API calls -// No keyboard navigation support -// No screen reader announcements for step changes - -// Signup form "submit" — no actual validation -document.getElementById('signup-form').addEventListener('submit', function(e) { - e.preventDefault(); - nextStep(2); -}); - -// No empty state enhancement -// No skeleton loading screens -// No retry logic for network failures -// No timeout handling +// doany.ai — Onboarding Controller +// Handles: step navigation, validation, loading/error states, accessibility + +(function () { + 'use strict'; + + var currentStep = 1; + var totalSteps = 3; + + // ============================================ + // STEP NAVIGATION + // ============================================ + + window.goToStep = function (stepNumber) { + if (stepNumber < 1 || stepNumber > totalSteps) return; + + // Hide all steps + document.querySelectorAll('.onboarding-step').forEach(function (el) { + el.classList.remove('active'); + }); + + // Show target + var target = document.getElementById('step-' + stepNumber); + if (target) { + target.classList.add('active'); + // Focus the heading for screen readers + var heading = target.querySelector('h1'); + if (heading) heading.focus(); + } + + currentStep = stepNumber; + updateProgress(); + announceStep(stepNumber); + }; + + function showSection(id) { + document.querySelectorAll('.onboarding-step').forEach(function (el) { + el.classList.remove('active'); + }); + var el = document.getElementById(id); + if (el) el.classList.add('active'); + } + + // ============================================ + // PROGRESS BAR + // ============================================ + + function updateProgress() { + document.querySelectorAll('.progress-step').forEach(function (step) { + var num = parseInt(step.getAttribute('data-step'), 10); + step.classList.remove('active', 'completed'); + step.querySelector('.progress-dot').removeAttribute('aria-current'); + + if (num < currentStep) { + step.classList.add('completed'); + } else if (num === currentStep) { + step.classList.add('active'); + step.querySelector('.progress-dot').setAttribute('aria-current', 'step'); + } + }); + } + + // ============================================ + // SCREEN READER ANNOUNCEMENTS + // ============================================ + + function announceStep(stepNumber) { + var labels = { + 1: 'Step 1 of 3: Create your account', + 2: 'Step 2 of 3: Set up your workspace', + 3: 'Step 3 of 3: Invite your team' + }; + var region = document.getElementById('sr-announce'); + if (region && labels[stepNumber]) { + region.textContent = labels[stepNumber]; + } + } + + // ============================================ + // VALIDATION + // ============================================ + + function validateEmail(email) { + return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); + } + + function showFieldError(inputId, errorId) { + var input = document.getElementById(inputId); + var error = document.getElementById(errorId); + if (input) input.classList.add('error'); + if (error) error.classList.add('visible'); + } + + function clearFieldError(inputId, errorId) { + var input = document.getElementById(inputId); + var error = document.getElementById(errorId); + if (input) input.classList.remove('error'); + if (error) error.classList.remove('visible'); + } + + function clearAllErrors(formEl) { + formEl.querySelectorAll('.form-input, .form-select, .form-textarea').forEach(function (el) { + el.classList.remove('error'); + }); + formEl.querySelectorAll('.form-error').forEach(function (el) { + el.classList.remove('visible'); + }); + } + + // Real-time validation: clear error on input + document.querySelectorAll('.form-input, .form-textarea').forEach(function (input) { + input.addEventListener('input', function () { + input.classList.remove('error'); + var errorEl = input.getAttribute('aria-describedby'); + if (errorEl) { + var err = document.getElementById(errorEl); + if (err) err.classList.remove('visible'); + } + }); + }); + + // ============================================ + // LOADING STATE + // ============================================ + + function setLoading(btnId, isLoading) { + var btn = document.getElementById(btnId); + if (!btn) return; + if (isLoading) { + btn.classList.add('loading'); + btn.disabled = true; + btn.setAttribute('aria-busy', 'true'); + } else { + btn.classList.remove('loading'); + btn.disabled = false; + btn.removeAttribute('aria-busy'); + } + } + + // Simulate async API call + function simulateAPI(duration) { + return new Promise(function (resolve) { + setTimeout(resolve, duration || 1200); + }); + } + + // ============================================ + // ERROR BANNER + // ============================================ + + function showError(bannerId, message) { + var banner = document.getElementById(bannerId); + if (!banner) return; + if (message) { + banner.querySelector('.error-banner-text').textContent = message; + } + banner.classList.add('visible'); + } + + window.dismissError = function (bannerId) { + var banner = document.getElementById(bannerId); + if (banner) banner.classList.remove('visible'); + }; + + // ============================================ + // STEP 1: SIGN UP + // ============================================ + + document.getElementById('signup-form').addEventListener('submit', function (e) { + e.preventDefault(); + var form = this; + clearAllErrors(form); + + var email = document.getElementById('email'); + var password = document.getElementById('password'); + var terms = document.getElementById('terms'); + var valid = true; + + if (!email.value.trim() || !validateEmail(email.value.trim())) { + showFieldError('email', 'email-error'); + valid = false; + } + + if (!password.value || password.value.length < 8) { + showFieldError('password', 'password-error'); + valid = false; + } + + if (!terms.checked) { + document.getElementById('terms-error').classList.add('visible'); + valid = false; + } + + if (!valid) return; + + setLoading('signup-btn', true); + + simulateAPI(1400).then(function () { + setLoading('signup-btn', false); + goToStep(2); + + // Auto-generate workspace URL from email domain + var domain = email.value.split('@')[1]; + if (domain) { + var slug = domain.split('.')[0].toLowerCase(); + var urlInput = document.getElementById('workspace-url'); + var nameInput = document.getElementById('workspace-name'); + if (urlInput && !urlInput.value) urlInput.value = slug; + if (nameInput && !nameInput.value) { + nameInput.value = slug.charAt(0).toUpperCase() + slug.slice(1); + } + } + }); + }); + + // ============================================ + // STEP 2: WORKSPACE + // ============================================ + + document.getElementById('workspace-form').addEventListener('submit', function (e) { + e.preventDefault(); + var form = this; + clearAllErrors(form); + + var name = document.getElementById('workspace-name'); + var url = document.getElementById('workspace-url'); + var valid = true; + + if (!name.value.trim()) { + showFieldError('workspace-name', 'workspace-name-error'); + valid = false; + } + + if (!url.value.trim()) { + showFieldError('workspace-url', 'workspace-url-error'); + valid = false; + } + + if (!valid) return; + + setLoading('workspace-btn', true); + + simulateAPI(1000).then(function () { + setLoading('workspace-btn', false); + goToStep(3); + }); + }); + + // Auto-slugify workspace name -> URL + document.getElementById('workspace-name').addEventListener('input', function () { + var slug = this.value + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-|-$/g, ''); + document.getElementById('workspace-url').value = slug; + }); + + // ============================================ + // STEP 3: INVITES + // ============================================ + + document.getElementById('invite-form').addEventListener('submit', function (e) { + e.preventDefault(); + + var textarea = document.getElementById('invite-emails'); + var emails = textarea.value.trim(); + + // If empty, just finish + if (!emails) { + finishOnboarding(); + return; + } + + setLoading('invite-btn', true); + + simulateAPI(1500).then(function () { + setLoading('invite-btn', false); + finishOnboarding(); + }); + }); + + window.finishOnboarding = function () { + // Mark all progress steps completed + document.querySelectorAll('.progress-step').forEach(function (step) { + step.classList.remove('active'); + step.classList.add('completed'); + step.querySelector('.progress-dot').removeAttribute('aria-current'); + }); + + showSection('step-success'); + + var region = document.getElementById('sr-announce'); + if (region) region.textContent = 'Onboarding complete. Your workspace is ready.'; + + // Focus the success heading + var heading = document.getElementById('success-heading'); + if (heading) heading.focus(); + }; + + // ============================================ + // DASHBOARD / EMPTY STATE + // ============================================ + + window.goToDashboard = function () { + // Hide progress bar + var progress = document.querySelector('.progress-bar'); + if (progress) progress.style.display = 'none'; + + showSection('empty-state'); + + var heading = document.getElementById('empty-heading'); + if (heading) heading.focus(); + + var region = document.getElementById('sr-announce'); + if (region) region.textContent = 'Dashboard. No projects yet.'; + }; + + window.createProject = function () { + // Placeholder — would open project creation flow + alert('Project creation flow would open here.'); + }; + + // ============================================ + // PASSWORD TOGGLE + // ============================================ + + window.togglePassword = function () { + var input = document.getElementById('password'); + var toggle = document.querySelector('.password-toggle'); + if (!input || !toggle) return; + + if (input.type === 'password') { + input.type = 'text'; + toggle.textContent = 'Hide'; + toggle.setAttribute('aria-label', 'Hide password'); + } else { + input.type = 'password'; + toggle.textContent = 'Show'; + toggle.setAttribute('aria-label', 'Show password'); + } + }; + + // ============================================ + // INIT + // ============================================ + + // Focus visible headings for tab navigation + document.querySelectorAll('.step-heading, .success-heading, .empty-heading').forEach(function (el) { + el.setAttribute('tabindex', '-1'); + }); + + // Initialize progress bar + updateProgress(); + announceStep(1); +})();