contact-form-production.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. // ==========================================================================
  2. // CONTACT FORM PRODUCTION VERSION - contact-form-production.js
  3. // Full functionality with real PHP submission, email sending, and debug logging
  4. // ==========================================================================
  5. document.addEventListener('DOMContentLoaded', function() {
  6. // Get main form elements
  7. const contactForm = document.getElementById('contactForm');
  8. const submitBtn = contactForm.querySelector('.form-submit-btn');
  9. const formSuccess = document.getElementById('form-success');
  10. const formError = document.getElementById('form-error');
  11. // Debug panel (optional - can be disabled in production)
  12. const DEBUG_MODE = false; // Set to false to disable debug panel
  13. /**
  14. * Create debug panel for development
  15. */
  16. function createDebugPanel() {
  17. if (!DEBUG_MODE) return null;
  18. const debugDiv = document.createElement('div');
  19. debugDiv.id = 'debug-panel';
  20. debugDiv.innerHTML = `
  21. <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
  22. <strong>📊 Debug Log</strong>
  23. <button onclick="this.parentElement.parentElement.style.display='none'" style="background: none; border: none; color: #e2e8f0; cursor: pointer;">×</button>
  24. </div>
  25. <div id="debug-content"></div>
  26. `;
  27. debugDiv.style.cssText = `
  28. position: fixed;
  29. bottom: 20px;
  30. right: 20px;
  31. width: 350px;
  32. max-height: 250px;
  33. background: #1e293b;
  34. color: #e2e8f0;
  35. padding: 15px;
  36. border-radius: 8px;
  37. font-family: monospace;
  38. font-size: 11px;
  39. overflow-y: auto;
  40. z-index: 9999;
  41. border: 1px solid #22c55e;
  42. box-shadow: 0 10px 25px rgba(0,0,0,0.5);
  43. `;
  44. document.body.appendChild(debugDiv);
  45. return debugDiv;
  46. }
  47. /**
  48. * Debug logging function
  49. */
  50. function debugLog(message, data = null) {
  51. if (!DEBUG_MODE) return;
  52. const debugPanel = document.getElementById('debug-panel') || createDebugPanel();
  53. if (!debugPanel) return;
  54. const debugContent = document.getElementById('debug-content');
  55. const timestamp = new Date().toLocaleTimeString();
  56. let logEntry = `<div style="margin: 5px 0; padding: 5px; background: rgba(34, 197, 94, 0.1); border-radius: 4px;">
  57. <strong>${timestamp}:</strong> ${message}
  58. `;
  59. if (data) {
  60. logEntry += `<br><pre style="margin: 5px 0; font-size: 10px; color: #94a3b8;">${JSON.stringify(data, null, 2)}</pre>`;
  61. }
  62. logEntry += '</div>';
  63. debugContent.innerHTML += logEntry;
  64. debugContent.scrollTop = debugContent.scrollHeight;
  65. }
  66. /**
  67. * Display field-specific error messages
  68. */
  69. function showFieldError(fieldName, message) {
  70. debugLog(`❌ Field error - ${fieldName}:`, message);
  71. const errorElement = document.getElementById(`${fieldName}-error`);
  72. if (errorElement) {
  73. errorElement.textContent = message;
  74. errorElement.classList.add('show');
  75. }
  76. }
  77. /**
  78. * Clear all field error messages
  79. */
  80. function clearFieldErrors() {
  81. const errorElements = contactForm.querySelectorAll('.form-error');
  82. errorElements.forEach(element => {
  83. element.textContent = '';
  84. element.classList.remove('show');
  85. });
  86. debugLog('🧹 Cleared all field errors');
  87. }
  88. /**
  89. * Show success message
  90. */
  91. function showSuccessMessage(message) {
  92. debugLog('🎉 Showing success message:', message);
  93. formError.style.display = 'none';
  94. formSuccess.style.display = 'flex';
  95. if (message) {
  96. formSuccess.querySelector('span').textContent = message;
  97. }
  98. setTimeout(() => {
  99. formSuccess.scrollIntoView({ behavior: 'smooth', block: 'center' });
  100. }, 100);
  101. }
  102. /**
  103. * Show error message
  104. */
  105. function showErrorMessage(message) {
  106. debugLog('❌ Showing error message:', message);
  107. formSuccess.style.display = 'none';
  108. formError.style.display = 'flex';
  109. if (message) {
  110. formError.querySelector('span').textContent = message;
  111. }
  112. setTimeout(() => {
  113. formError.scrollIntoView({ behavior: 'smooth', block: 'center' });
  114. }, 100);
  115. }
  116. /**
  117. * Setup real-time validation
  118. */
  119. function setupRealTimeValidation() {
  120. debugLog('🔧 Setting up real-time validation');
  121. const fields = {
  122. name: {
  123. element: contactForm.querySelector('#name'),
  124. validate: (value) => {
  125. if (!value.trim()) return 'Name is required';
  126. if (value.trim().length < 2) return 'Name must be at least 2 characters';
  127. return null;
  128. }
  129. },
  130. email: {
  131. element: contactForm.querySelector('#email'),
  132. validate: (value) => {
  133. if (!value.trim()) return 'Email is required';
  134. const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  135. if (!emailRegex.test(value)) return 'Please enter a valid email address';
  136. return null;
  137. }
  138. },
  139. phone: {
  140. element: contactForm.querySelector('#phone'),
  141. validate: (value) => {
  142. if (value.trim() && !/^[\+]?[1-9][\d\s\-\(\)]{0,15}$/.test(value)) {
  143. return 'Please enter a valid phone number';
  144. }
  145. return null;
  146. }
  147. },
  148. message: {
  149. element: contactForm.querySelector('#message'),
  150. validate: (value) => {
  151. if (!value.trim()) return 'Message is required';
  152. if (value.trim().length < 10) return 'Message must be at least 10 characters';
  153. if (value.trim().length > 2000) return 'Message must be less than 2000 characters';
  154. return null;
  155. }
  156. }
  157. };
  158. Object.keys(fields).forEach(fieldName => {
  159. const field = fields[fieldName];
  160. const errorElement = document.getElementById(`${fieldName}-error`);
  161. field.element.addEventListener('blur', function() {
  162. const error = field.validate(this.value);
  163. if (error) {
  164. showFieldError(fieldName, error);
  165. } else if (errorElement) {
  166. errorElement.textContent = '';
  167. errorElement.classList.remove('show');
  168. }
  169. });
  170. field.element.addEventListener('input', function() {
  171. if (errorElement && errorElement.classList.contains('show')) {
  172. const error = field.validate(this.value);
  173. if (!error) {
  174. errorElement.textContent = '';
  175. errorElement.classList.remove('show');
  176. debugLog(`✅ Field ${fieldName} is now valid`);
  177. }
  178. }
  179. });
  180. });
  181. const privacyCheckbox = contactForm.querySelector('input[name="privacy"]');
  182. privacyCheckbox.addEventListener('change', function() {
  183. const errorElement = document.getElementById('privacy-error');
  184. if (this.checked && errorElement) {
  185. errorElement.textContent = '';
  186. errorElement.classList.remove('show');
  187. debugLog('✅ Privacy policy accepted');
  188. }
  189. });
  190. }
  191. /**
  192. * Handle form submission with REAL PHP backend
  193. */
  194. contactForm.addEventListener('submit', async function(e) {
  195. e.preventDefault();
  196. debugLog('📤 Form submission started');
  197. debugLog('🌍 Current URL:', window.location.href);
  198. // Clear previous messages
  199. clearFieldErrors();
  200. formSuccess.style.display = 'none';
  201. formError.style.display = 'none';
  202. // Get form data
  203. const formData = new FormData(contactForm);
  204. // Debug: Show form data
  205. const formDataObj = {};
  206. for (let [key, value] of formData.entries()) {
  207. formDataObj[key] = value;
  208. }
  209. debugLog('📝 Form data to send:', formDataObj);
  210. // Client-side validation
  211. let isValid = true;
  212. if (!formData.get('name').trim()) {
  213. showFieldError('name', 'Name is required');
  214. isValid = false;
  215. }
  216. if (!formData.get('email').trim()) {
  217. showFieldError('email', 'Email is required');
  218. isValid = false;
  219. } else {
  220. const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  221. if (!emailRegex.test(formData.get('email'))) {
  222. showFieldError('email', 'Please enter a valid email address');
  223. isValid = false;
  224. }
  225. }
  226. if (!formData.get('message').trim()) {
  227. showFieldError('message', 'Message is required');
  228. isValid = false;
  229. } else if (formData.get('message').trim().length < 10) {
  230. showFieldError('message', 'Message must be at least 10 characters');
  231. isValid = false;
  232. }
  233. if (!formData.get('privacy')) {
  234. showFieldError('privacy', 'You must accept the privacy policy');
  235. isValid = false;
  236. }
  237. if (!isValid) {
  238. debugLog('❌ Validation failed, stopping submission');
  239. const firstError = contactForm.querySelector('.form-error.show');
  240. if (firstError) {
  241. firstError.scrollIntoView({ behavior: 'smooth', block: 'center' });
  242. }
  243. return;
  244. }
  245. debugLog('✅ Validation passed, sending to PHP...');
  246. // Show loading state
  247. submitBtn.disabled = true;
  248. const originalText = submitBtn.textContent;
  249. submitBtn.textContent = 'Sending...';
  250. submitBtn.style.opacity = '0.7';
  251. try {
  252. debugLog('🌐 Making HTTP request to contact.php');
  253. const response = await fetch('contact.php', {
  254. method: 'POST',
  255. body: formData,
  256. headers: {
  257. 'X-Requested-With': 'XMLHttpRequest'
  258. }
  259. });
  260. debugLog(`📡 Response received - Status: ${response.status} ${response.statusText}`);
  261. // Check if response is ok
  262. if (!response.ok) {
  263. throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  264. }
  265. // Check content type
  266. const contentType = response.headers.get('content-type');
  267. debugLog(`📋 Content-Type: ${contentType}`);
  268. if (!contentType || !contentType.includes('application/json')) {
  269. const textResponse = await response.text();
  270. debugLog('⚠️ Non-JSON response received:', textResponse.substring(0, 200) + '...');
  271. // Try to show useful error to user
  272. if (textResponse.includes('Parse error') || textResponse.includes('Fatal error')) {
  273. showErrorMessage('Server configuration error. Please contact support.');
  274. } else {
  275. showErrorMessage('Server returned invalid response format. Please try again.');
  276. }
  277. return;
  278. }
  279. const result = await response.json();
  280. debugLog('📦 Parsed JSON response:', result);
  281. if (result.success) {
  282. debugLog('🎉 SUCCESS: Message sent successfully!');
  283. const userName = formData.get('name').trim();
  284. showSuccessMessage(result.message || `Thank you ${userName}! Your message has been sent successfully.`);
  285. // Reset form after 1.5 seconds
  286. setTimeout(() => {
  287. contactForm.reset();
  288. updateCharCounter();
  289. debugLog('🔄 Form reset completed');
  290. }, 1500);
  291. // Track successful submission
  292. if (typeof gtag !== 'undefined') {
  293. gtag('event', 'form_submit', {
  294. event_category: 'contact',
  295. event_label: 'success'
  296. });
  297. }
  298. } else {
  299. debugLog('❌ ERROR: Server returned error:', result);
  300. if (result.errors) {
  301. // Show field-specific errors
  302. Object.keys(result.errors).forEach(fieldName => {
  303. showFieldError(fieldName, result.errors[fieldName]);
  304. });
  305. // Scroll to first error
  306. const firstError = contactForm.querySelector('.form-error.show');
  307. if (firstError) {
  308. firstError.scrollIntoView({ behavior: 'smooth', block: 'center' });
  309. }
  310. } else {
  311. showErrorMessage(result.message || 'An error occurred. Please try again.');
  312. }
  313. // Track failed submission
  314. if (typeof gtag !== 'undefined') {
  315. gtag('event', 'form_submit', {
  316. event_category: 'contact',
  317. event_label: 'validation_error'
  318. });
  319. }
  320. }
  321. } catch (error) {
  322. debugLog('💥 JavaScript error occurred:', error.toString());
  323. console.error('Complete error object:', error);
  324. // User-friendly error messages based on error type
  325. let userMessage = 'An unexpected error occurred. Please try again.';
  326. if (error.name === 'TypeError' && error.message.includes('fetch')) {
  327. userMessage = 'Unable to connect to server. Please check your internet connection.';
  328. } else if (error.message.includes('HTTP 404')) {
  329. userMessage = 'Contact form not properly configured. Please try again later.';
  330. } else if (error.message.includes('HTTP 403')) {
  331. userMessage = 'Access denied. Please refresh the page and try again.';
  332. } else if (error.message.includes('HTTP 500')) {
  333. userMessage = 'Server error. Please try again in a few minutes.';
  334. }
  335. showErrorMessage(userMessage);
  336. // Track network errors
  337. if (typeof gtag !== 'undefined') {
  338. gtag('event', 'form_submit', {
  339. event_category: 'contact',
  340. event_label: 'network_error'
  341. });
  342. }
  343. } finally {
  344. // Always restore button state
  345. submitBtn.disabled = false;
  346. submitBtn.textContent = originalText;
  347. submitBtn.style.opacity = '1';
  348. debugLog('🔄 Button restored to normal state');
  349. }
  350. });
  351. // Initialize everything
  352. setupRealTimeValidation();
  353. debugLog('🎬 Production contact form initialized');
  354. // Character counter
  355. const messageTextarea = contactForm.querySelector('#message');
  356. const messageGroup = messageTextarea.closest('.form-group');
  357. const charCounter = document.createElement('div');
  358. charCounter.className = 'char-counter';
  359. charCounter.style.cssText = 'font-size: 12px; color: #6b7280; margin-top: 5px; text-align: right;';
  360. messageGroup.appendChild(charCounter);
  361. function updateCharCounter() {
  362. const length = messageTextarea.value.length;
  363. const maxLength = 2000;
  364. charCounter.textContent = `${length}/${maxLength} characters`;
  365. if (length > maxLength * 0.9) {
  366. charCounter.style.color = '#f59e0b';
  367. } else if (length > maxLength) {
  368. charCounter.style.color = '#dc3545';
  369. } else {
  370. charCounter.style.color = '#6b7280';
  371. }
  372. }
  373. messageTextarea.addEventListener('input', updateCharCounter);
  374. updateCharCounter();
  375. // Auto-resize textarea
  376. messageTextarea.addEventListener('input', function() {
  377. this.style.height = 'auto';
  378. this.style.height = Math.min(this.scrollHeight, 200) + 'px';
  379. });
  380. // Enhanced UX features
  381. const formInputs = contactForm.querySelectorAll('input, select, textarea');
  382. formInputs.forEach((input, index) => {
  383. input.addEventListener('focus', function() {
  384. this.closest('.form-group').classList.add('focused');
  385. });
  386. input.addEventListener('blur', function() {
  387. this.closest('.form-group').classList.remove('focused');
  388. });
  389. if (input.type !== 'textarea') {
  390. input.addEventListener('keydown', function(e) {
  391. if (e.key === 'Enter') {
  392. e.preventDefault();
  393. const nextInput = formInputs[index + 1];
  394. if (nextInput) {
  395. nextInput.focus();
  396. } else {
  397. submitBtn.click();
  398. }
  399. }
  400. });
  401. }
  402. });
  403. // Auto-focus budget when service is selected
  404. const serviceSelect = contactForm.querySelector('#service');
  405. const budgetSelect = contactForm.querySelector('#budget');
  406. serviceSelect.addEventListener('change', function() {
  407. if (this.value && !budgetSelect.value) {
  408. setTimeout(() => budgetSelect.focus(), 100);
  409. }
  410. });
  411. // Form analytics
  412. let formStartTime = null;
  413. let fieldsInteracted = new Set();
  414. contactForm.addEventListener('focusin', function() {
  415. if (!formStartTime) {
  416. formStartTime = Date.now();
  417. debugLog('⏱️ User started interacting with form');
  418. }
  419. }, { once: true });
  420. formInputs.forEach(input => {
  421. input.addEventListener('input', function() {
  422. fieldsInteracted.add(this.name || this.id);
  423. });
  424. });
  425. contactForm.addEventListener('submit', function() {
  426. if (formStartTime) {
  427. const completionTime = Date.now() - formStartTime;
  428. const analytics = {
  429. mode: 'production',
  430. completionTime: Math.round(completionTime / 1000) + 's',
  431. fieldsInteracted: fieldsInteracted.size,
  432. totalFields: formInputs.length,
  433. timestamp: new Date().toISOString()
  434. };
  435. debugLog('📊 Form Analytics:', analytics);
  436. // Send to Google Analytics if available
  437. if (typeof gtag !== 'undefined') {
  438. gtag('event', 'form_interaction', {
  439. event_category: 'contact',
  440. custom_parameter_completion_time: analytics.completionTime,
  441. custom_parameter_fields_used: analytics.fieldsInteracted
  442. });
  443. }
  444. }
  445. });
  446. });