/** * InvestEdu - Utility Functions * Common helper functions used across the application */ const Utils = { /** * Format currency amount * @param {number} amount - The amount to format * @param {string} currency - Currency code (default: USD) * @returns {string} Formatted currency string */ formatCurrency(amount, currency = 'USD') { return new Intl.NumberFormat('en-US', { style: 'currency', currency: currency, minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(amount); }, /** * Format date * @param {Date|string} date - Date to format * @param {string} format - Format string (default: 'MM/DD/YYYY') * @returns {string} Formatted date string */ formatDate(date, format = 'MM/DD/YYYY') { const d = new Date(date); const day = String(d.getDate()).padStart(2, '0'); const month = String(d.getMonth() + 1).padStart(2, '0'); const year = d.getFullYear(); return format .replace('MM', month) .replace('DD', day) .replace('YYYY', year); }, /** * Format date with time * @param {Date|string} date - Date to format * @returns {string} Formatted date and time string */ formatDateTime(date) { const d = new Date(date); return `${this.formatDate(d)} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`; }, /** * Get relative time (e.g., "2 hours ago") * @param {Date|string} date - Date to compare * @returns {string} Relative time string */ getRelativeTime(date) { const now = new Date(); const then = new Date(date); const diffInSeconds = Math.floor((now - then) / 1000); if (diffInSeconds < 60) return 'Just now'; if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)} minutes ago`; if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)} hours ago`; if (diffInSeconds < 604800) return `${Math.floor(diffInSeconds / 86400)} days ago`; return this.formatDate(then); }, /** * Debounce function * @param {Function} func - Function to debounce * @param {number} wait - Wait time in milliseconds * @returns {Function} Debounced function */ debounce(func, wait = 300) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; }, /** * Copy text to clipboard * @param {string} text - Text to copy * @returns {Promise} Success status */ async copyToClipboard(text) { try { await navigator.clipboard.writeText(text); return true; } catch (err) { console.error('Failed to copy:', err); return false; } }, /** * Show toast notification * @param {string} message - Message to display * @param {string} type - Type: 'success', 'error', 'warning', 'info' * @param {number} duration - Duration in milliseconds */ showToast(message, type = 'info', duration = 5000) { // Create toast element const toast = document.createElement('div'); toast.className = `toast toast-${type}`; toast.textContent = message; // Add to page let toastContainer = document.getElementById('toast-container'); if (!toastContainer) { toastContainer = document.createElement('div'); toastContainer.id = 'toast-container'; toastContainer.style.cssText = 'position: fixed; top: 20px; right: 20px; z-index: 9999; display: flex; flex-direction: column; gap: 10px;'; document.body.appendChild(toastContainer); } toastContainer.appendChild(toast); // Remove after duration setTimeout(() => { toast.remove(); }, duration); }, /** * Validate email format * @param {string} email - Email to validate * @returns {boolean} Is valid email */ isValidEmail(email) { const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return pattern.test(email); }, /** * Validate password strength * @param {string} password - Password to validate * @returns {Object} Validation result with isValid and errors */ validatePassword(password) { const errors = []; if (password.length < 8) { errors.push('Password must be at least 8 characters long'); } if (!/[A-Z]/.test(password)) { errors.push('Password must contain at least one uppercase letter'); } if (!/[a-z]/.test(password)) { errors.push('Password must contain at least one lowercase letter'); } if (!/[0-9]/.test(password)) { errors.push('Password must contain at least one number'); } return { isValid: errors.length === 0, errors: errors }; }, /** * Toggle password visibility * @param {string} inputId - ID of password input element */ togglePasswordVisibility(inputId) { const input = document.getElementById(inputId); const icon = document.querySelector(`[data-toggle-password="${inputId}"]`); if (input && icon) { if (input.type === 'password') { input.type = 'text'; icon.classList.remove('fa-eye'); icon.classList.add('fa-eye-slash'); } else { input.type = 'password'; icon.classList.remove('fa-eye-slash'); icon.classList.add('fa-eye'); } } }, /** * Format number with commas * @param {number} num - Number to format * @returns {string} Formatted number string */ formatNumber(num) { return new Intl.NumberFormat('en-US').format(num); }, /** * Get URL parameters * @param {string} name - Parameter name * @returns {string|null} Parameter value */ getUrlParameter(name) { const urlParams = new URLSearchParams(window.location.search); return urlParams.get(name); }, /** * Scroll to element smoothly * @param {string} elementId - ID of element to scroll to */ scrollTo(elementId) { const element = document.getElementById(elementId); if (element) { element.scrollIntoView({ behavior: 'smooth', block: 'start' }); } } }; // Export for use in other modules if (typeof module !== 'undefined' && module.exports) { module.exports = Utils; }