error and injection
This commit is contained in:
parent
3a61000c12
commit
0f272fcf19
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="min-h-screen bg-primary-navy flex items-center justify-center px-4">
|
||||
<div className="text-center text-white max-w-md">
|
||||
<p className="text-primary-cyan text-sm font-semibold uppercase tracking-widest mb-4">Something went wrong</p>
|
||||
<h1 className="text-4xl font-bold mb-4">Unexpected Error</h1>
|
||||
<p className="text-white/70 mb-8">
|
||||
A problem occurred while loading this page. Please try refreshing, or contact us if the issue continues.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-3 justify-center">
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="inline-flex h-11 items-center justify-center rounded-md bg-primary-cyan px-6 text-sm font-semibold text-primary-navy hover:bg-white transition-colors"
|
||||
>
|
||||
Refresh Page
|
||||
</button>
|
||||
<Link
|
||||
to="/"
|
||||
onClick={() => 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
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorBoundary
|
||||
|
|
@ -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 = () => (
|
||||
<StrictMode>
|
||||
<HelmetProvider>
|
||||
<ErrorBoundary>
|
||||
<RouterProvider router={router} />
|
||||
<Toaster position="top-right" />
|
||||
</ErrorBoundary>
|
||||
</HelmetProvider>
|
||||
</StrictMode>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue