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()
}