/** * Main JavaScript Application * Professional Resume Builder - Laravel Application * * @author David Valera Melendez * @created 2025-08-08 * @location Made in Germany 🇩🇪 */ // Import Bootstrap JavaScript import 'bootstrap'; // Import Axios for HTTP requests import axios from 'axios'; // Configure Axios defaults window.axios = axios; window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; // CSRF Token setup const token = document.head.querySelector('meta[name="csrf-token"]'); if (token) { window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content; } else { console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token'); } /** * Application Class - Main Application Logic */ class ResumeBuilderApp { constructor() { this.init(); } /** * Initialize the application */ init() { this.setupEventListeners(); this.setupFormValidation(); this.setupTooltips(); this.setupAnimations(); this.setupAccessibility(); this.setupProgressBars(); // Professional Resume Builder initialized - Made in Germany 🇩🇪 } /** * Setup global event listeners */ setupEventListeners() { document.addEventListener('DOMContentLoaded', () => { this.handlePageLoad(); }); // Handle form submissions with loading states document.addEventListener('submit', (e) => { if (e.target.tagName === 'FORM') { this.handleFormSubmit(e.target); } }); // Handle dynamic content loading document.addEventListener('click', (e) => { if (e.target.matches('[data-action]')) { this.handleDynamicAction(e); } }); } /** * Handle page load events */ handlePageLoad() { // Animate elements on page load this.animateOnLoad(); // Focus management this.setupFocusManagement(); // Auto-dismiss alerts this.setupAlertDismissal(); } /** * Setup form validation */ setupFormValidation() { const forms = document.querySelectorAll('form[data-validate]'); forms.forEach(form => { const inputs = form.querySelectorAll('input, select, textarea'); inputs.forEach(input => { // Real-time validation input.addEventListener('input', () => { this.validateField(input); }); input.addEventListener('blur', () => { this.validateField(input); }); }); // Form submission validation form.addEventListener('submit', (e) => { if (!this.validateForm(form)) { e.preventDefault(); this.showValidationErrors(form); } }); }); } /** * Validate individual field */ validateField(field) { const isValid = field.checkValidity(); const hasValue = field.value.trim().length > 0; field.classList.remove('is-valid', 'is-invalid'); if (hasValue) { field.classList.add(isValid ? 'is-valid' : 'is-invalid'); } // Custom validation rules if (field.type === 'password' && field.name === 'password') { this.validatePassword(field); } if (field.type === 'password' && field.name === 'password_confirmation') { this.validatePasswordConfirmation(field); } return isValid; } /** * Validate password strength */ validatePassword(passwordField) { const password = passwordField.value; const minLength = 8; const hasUpperCase = /[A-Z]/.test(password); const hasLowerCase = /[a-z]/.test(password); const hasNumbers = /\d/.test(password); const hasSpecialChar = /[!@#$%^&*]/.test(password); const isStrong = password.length >= minLength && hasUpperCase && hasLowerCase && hasNumbers; passwordField.classList.remove('is-valid', 'is-invalid'); if (password.length > 0) { passwordField.classList.add(isStrong ? 'is-valid' : 'is-invalid'); } return isStrong; } /** * Validate password confirmation */ validatePasswordConfirmation(confirmField) { const password = document.querySelector('input[name="password"]')?.value || ''; const confirmation = confirmField.value; const matches = password === confirmation && confirmation.length > 0; confirmField.classList.remove('is-valid', 'is-invalid'); if (confirmation.length > 0) { confirmField.classList.add(matches ? 'is-valid' : 'is-invalid'); } return matches; } /** * Validate entire form */ validateForm(form) { const inputs = form.querySelectorAll('input[required], select[required], textarea[required]'); let isValid = true; inputs.forEach(input => { if (!this.validateField(input)) { isValid = false; } }); return isValid; } /** * Show validation errors */ showValidationErrors(form) { const firstInvalidField = form.querySelector('.is-invalid'); if (firstInvalidField) { firstInvalidField.focus(); firstInvalidField.scrollIntoView({ behavior: 'smooth', block: 'center' }); } } /** * Handle form submission with loading states */ handleFormSubmit(form) { const submitButton = form.querySelector('button[type="submit"]'); if (submitButton) { const originalContent = submitButton.innerHTML; const loadingText = submitButton.dataset.loading || 'Processing...'; submitButton.innerHTML = `${loadingText}`; submitButton.disabled = true; // Re-enable if form doesn't redirect (error case) setTimeout(() => { if (document.contains(submitButton)) { submitButton.innerHTML = originalContent; submitButton.disabled = false; } }, 5000); } } /** * Setup tooltips */ setupTooltips() { const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); tooltipTriggerList.map(function (tooltipTriggerEl) { return new bootstrap.Tooltip(tooltipTriggerEl); }); } /** * Setup animations */ setupAnimations() { // Intersection Observer for scroll animations if ('IntersectionObserver' in window) { const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('animate-fade-in-up'); observer.unobserve(entry.target); } }); }); document.querySelectorAll('[data-animate]').forEach(el => { observer.observe(el); }); } } /** * Animate elements on page load */ animateOnLoad() { // Stagger animation for cards const cards = document.querySelectorAll('.card'); cards.forEach((card, index) => { setTimeout(() => { card.style.opacity = '0'; card.style.transform = 'translateY(20px)'; card.style.transition = 'all 0.5s ease'; setTimeout(() => { card.style.opacity = '1'; card.style.transform = 'translateY(0)'; }, 100 * index); }, 0); }); } /** * Setup progress bars with animation */ setupProgressBars() { const progressBars = document.querySelectorAll('.progress-bar'); const animateProgress = (entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { const progressBar = entry.target; const targetWidth = progressBar.style.width; progressBar.style.width = '0%'; setTimeout(() => { progressBar.style.transition = 'width 1s ease-in-out'; progressBar.style.width = targetWidth; }, 200); observer.unobserve(progressBar); } }); }; if ('IntersectionObserver' in window) { const observer = new IntersectionObserver(animateProgress); progressBars.forEach(bar => observer.observe(bar)); } } /** * Setup accessibility features */ setupAccessibility() { // Keyboard navigation for custom elements document.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { if (e.target.matches('[role="button"]:not(button):not(input)')) { e.preventDefault(); e.target.click(); } } }); // Focus management for modals document.addEventListener('shown.bs.modal', (e) => { const modal = e.target; const focusableElement = modal.querySelector('input, button, select, textarea, [tabindex]:not([tabindex="-1"])'); if (focusableElement) { focusableElement.focus(); } }); } /** * Setup focus management */ setupFocusManagement() { // Auto-focus first input in forms const firstInput = document.querySelector('input:not([type="hidden"]):not([readonly])'); if (firstInput && !firstInput.value) { setTimeout(() => firstInput.focus(), 100); } // Skip links for accessibility const skipLinks = document.querySelectorAll('.skip-link'); skipLinks.forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); const target = document.querySelector(link.getAttribute('href')); if (target) { target.focus(); target.scrollIntoView(); } }); }); } /** * Setup alert auto-dismissal */ setupAlertDismissal() { const alerts = document.querySelectorAll('.alert:not(.alert-permanent)'); alerts.forEach(alert => { // Auto-dismiss success alerts if (alert.classList.contains('alert-success')) { setTimeout(() => { this.dismissAlert(alert); }, 5000); } // Auto-dismiss info alerts if (alert.classList.contains('alert-info')) { setTimeout(() => { this.dismissAlert(alert); }, 8000); } }); } /** * Dismiss alert with animation */ dismissAlert(alert) { alert.style.transition = 'opacity 0.5s ease, transform 0.5s ease'; alert.style.opacity = '0'; alert.style.transform = 'translateY(-20px)'; setTimeout(() => { if (alert.parentNode) { alert.parentNode.removeChild(alert); } }, 500); } /** * Handle dynamic actions */ handleDynamicAction(e) { e.preventDefault(); const action = e.target.dataset.action; switch (action) { case 'confirm-delete': this.handleConfirmDelete(e.target); break; case 'copy-link': this.handleCopyLink(e.target); break; case 'toggle-visibility': this.handleToggleVisibility(e.target); break; default: console.warn('Unknown action:', action); } } /** * Handle confirm delete action */ handleConfirmDelete(element) { const message = element.dataset.message || 'Are you sure you want to delete this item?'; const form = element.closest('form') || document.querySelector(element.dataset.form); if (confirm(message) && form) { form.submit(); } } /** * Handle copy link action */ async handleCopyLink(element) { const url = element.dataset.url || window.location.href; try { await navigator.clipboard.writeText(url); this.showToast('Link copied to clipboard!', 'success'); } catch (err) { console.error('Failed to copy link:', err); this.showToast('Failed to copy link', 'error'); } } /** * Handle toggle visibility action */ handleToggleVisibility(element) { const target = document.querySelector(element.dataset.target); const type = element.type; if (target && type === 'password') { const isPassword = target.type === 'password'; target.type = isPassword ? 'text' : 'password'; const icon = element.querySelector('i'); if (icon) { icon.classList.toggle('bi-eye'); icon.classList.toggle('bi-eye-slash'); } } } /** * Show toast notification */ showToast(message, type = 'info') { const toastContainer = this.getOrCreateToastContainer(); const toast = this.createToast(message, type); toastContainer.appendChild(toast); const bsToast = new bootstrap.Toast(toast); bsToast.show(); toast.addEventListener('hidden.bs.toast', () => { toast.remove(); }); } /** * Get or create toast container */ getOrCreateToastContainer() { let container = document.querySelector('.toast-container'); if (!container) { container = document.createElement('div'); container.className = 'toast-container position-fixed top-0 end-0 p-3'; container.style.zIndex = '1055'; document.body.appendChild(container); } return container; } /** * Create toast element */ createToast(message, type) { const toast = document.createElement('div'); toast.className = 'toast'; toast.setAttribute('role', 'alert'); toast.setAttribute('aria-live', 'assertive'); toast.setAttribute('aria-atomic', 'true'); const iconMap = { success: 'bi-check-circle-fill text-success', error: 'bi-exclamation-circle-fill text-danger', warning: 'bi-exclamation-triangle-fill text-warning', info: 'bi-info-circle-fill text-info' }; const icon = iconMap[type] || iconMap.info; toast.innerHTML = `
Resume Builder
${message}
`; return toast; } } // Initialize the application const app = new ResumeBuilderApp(); // Export for global access window.ResumeBuilderApp = app;