From 0f272fcf195c9b11c68ddf2929fbc2ffe049c93c Mon Sep 17 00:00:00 2001 From: null Date: Thu, 28 May 2026 00:18:08 -0500 Subject: [PATCH] error and injection --- server/index.js | 6 ++-- src/components/ErrorBoundary.jsx | 50 ++++++++++++++++++++++++++++++++ src/main.jsx | 7 +++-- src/pages/Contact.jsx | 22 ++++++++++++-- 4 files changed, 77 insertions(+), 8 deletions(-) create mode 100644 src/components/ErrorBoundary.jsx diff --git a/server/index.js b/server/index.js index b252377..aba41f5 100644 --- a/server/index.js +++ b/server/index.js @@ -64,15 +64,15 @@ const apiLimiter = rateLimit({ const isDev = process.env.NODE_ENV === 'development' const cspDirectives = { defaultSrc: ["'self'"], - scriptSrc: ["'self'", 'https://www.google.com/recaptcha/', 'https://www.gstatic.com/recaptcha/'], + scriptSrc: ["'self'", 'https://crm.zohopublic.com', 'https://www.google.com/recaptcha/', 'https://www.gstatic.com/recaptcha/'], styleSrc: ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com'], fontSrc: ["'self'", 'https://fonts.gstatic.com'], imgSrc: ["'self'", 'data:'], connectSrc: isDev ? ["'self'", 'ws://localhost:*'] : ["'self'"], - frameSrc: ["'self'", 'https://www.google.com/recaptcha/', 'https://recaptcha.google.com/recaptcha/'], + frameSrc: ["'self'", 'https://crm.zoho.com', 'https://www.google.com/recaptcha/', 'https://recaptcha.google.com/recaptcha/'], objectSrc: ["'none'"], baseUri: ["'self'"], - formAction: ["'self'"], + formAction: ["'self'", 'https://crm.zoho.com'], } // Note: connectSrc currently allows 'self' only. Zoho API calls are server-to-server diff --git a/src/components/ErrorBoundary.jsx b/src/components/ErrorBoundary.jsx new file mode 100644 index 0000000..92c4d3d --- /dev/null +++ b/src/components/ErrorBoundary.jsx @@ -0,0 +1,50 @@ +import { Component } from 'react' +import { Link } from 'react-router-dom' + +class ErrorBoundary extends Component { + constructor(props) { + super(props) + this.state = { hasError: false, error: null } + } + + static getDerivedStateFromError(error) { + return { hasError: true, error } + } + + componentDidCatch(error, info) { + console.error('[ErrorBoundary] Uncaught error:', error, info.componentStack) + } + + render() { + if (!this.state.hasError) return this.props.children + + return ( +
+
+

Something went wrong

+

Unexpected Error

+

+ A problem occurred while loading this page. Please try refreshing, or contact us if the issue continues. +

+
+ + this.setState({ hasError: false, error: null })} + className="inline-flex h-11 items-center justify-center rounded-md border border-white/35 px-6 text-sm font-semibold text-white hover:bg-white/10 transition-colors" + > + Back to Home + +
+
+
+ ) + } +} + +export default ErrorBoundary diff --git a/src/main.jsx b/src/main.jsx index 554883e..b987019 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -5,13 +5,16 @@ import { Toaster } from 'sonner' import { HelmetProvider } from 'react-helmet-async' import router from './router.jsx' import App from './App.jsx' +import ErrorBoundary from './components/ErrorBoundary.jsx' // Wrap the router with providers const Root = () => ( - - + + + + ) diff --git a/src/pages/Contact.jsx b/src/pages/Contact.jsx index c0e06f5..126e909 100644 --- a/src/pages/Contact.jsx +++ b/src/pages/Contact.jsx @@ -36,15 +36,28 @@ const Contact = () => { useEffect(() => { const iframe = document.getElementById('zoho_webform_iframe') if (!iframe) return - const handleLoad = () => { - if (!isSubmitting) return + + let fallbackTimer = null + + const handleSuccess = () => { + if (fallbackTimer) clearTimeout(fallbackTimer) setIsSubmitting(false) toast.success("Thanks! We'll be in touch shortly.") setFormState({ 'Last Name': '', Company: '', Email: '', Phone: '', 'Zip Code': '', Description: '' }) setErrors({ 'Last Name': '', Company: '', Email: '', 'Zip Code': '', Description: '', recaptcha_token: '' }) } + + const handleLoad = () => { if (isSubmitting) handleSuccess() } + + if (isSubmitting) { + fallbackTimer = setTimeout(handleSuccess, 3500) + } + iframe.addEventListener('load', handleLoad) - return () => iframe.removeEventListener('load', handleLoad) + return () => { + iframe.removeEventListener('load', handleLoad) + if (fallbackTimer) clearTimeout(fallbackTimer) + } }, [isSubmitting]) useEffect(() => { @@ -80,6 +93,9 @@ const Contact = () => { e.preventDefault() if (!validateForm()) return setIsSubmitting(true) + if (typeof _wfa_track !== 'undefined' && _wfa_track.wfa_submit) { + _wfa_track.wfa_submit(e) + } formRef.current.submit() }