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 isDev = process.env.NODE_ENV === 'development'
|
||||||
const cspDirectives = {
|
const cspDirectives = {
|
||||||
defaultSrc: ["'self'"],
|
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'],
|
styleSrc: ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com'],
|
||||||
fontSrc: ["'self'", 'https://fonts.gstatic.com'],
|
fontSrc: ["'self'", 'https://fonts.gstatic.com'],
|
||||||
imgSrc: ["'self'", 'data:'],
|
imgSrc: ["'self'", 'data:'],
|
||||||
connectSrc: isDev ? ["'self'", 'ws://localhost:*'] : ["'self'"],
|
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'"],
|
objectSrc: ["'none'"],
|
||||||
baseUri: ["'self'"],
|
baseUri: ["'self'"],
|
||||||
formAction: ["'self'"],
|
formAction: ["'self'", 'https://crm.zoho.com'],
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: connectSrc currently allows 'self' only. Zoho API calls are server-to-server
|
// 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 { HelmetProvider } from 'react-helmet-async'
|
||||||
import router from './router.jsx'
|
import router from './router.jsx'
|
||||||
import App from './App.jsx'
|
import App from './App.jsx'
|
||||||
|
import ErrorBoundary from './components/ErrorBoundary.jsx'
|
||||||
|
|
||||||
// Wrap the router with providers
|
// Wrap the router with providers
|
||||||
const Root = () => (
|
const Root = () => (
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<HelmetProvider>
|
<HelmetProvider>
|
||||||
<RouterProvider router={router} />
|
<ErrorBoundary>
|
||||||
<Toaster position="top-right" />
|
<RouterProvider router={router} />
|
||||||
|
<Toaster position="top-right" />
|
||||||
|
</ErrorBoundary>
|
||||||
</HelmetProvider>
|
</HelmetProvider>
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -36,15 +36,28 @@ const Contact = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const iframe = document.getElementById('zoho_webform_iframe')
|
const iframe = document.getElementById('zoho_webform_iframe')
|
||||||
if (!iframe) return
|
if (!iframe) return
|
||||||
const handleLoad = () => {
|
|
||||||
if (!isSubmitting) return
|
let fallbackTimer = null
|
||||||
|
|
||||||
|
const handleSuccess = () => {
|
||||||
|
if (fallbackTimer) clearTimeout(fallbackTimer)
|
||||||
setIsSubmitting(false)
|
setIsSubmitting(false)
|
||||||
toast.success("Thanks! We'll be in touch shortly.")
|
toast.success("Thanks! We'll be in touch shortly.")
|
||||||
setFormState({ 'Last Name': '', Company: '', Email: '', Phone: '', 'Zip Code': '', Description: '' })
|
setFormState({ 'Last Name': '', Company: '', Email: '', Phone: '', 'Zip Code': '', Description: '' })
|
||||||
setErrors({ 'Last Name': '', Company: '', Email: '', 'Zip Code': '', Description: '', recaptcha_token: '' })
|
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)
|
iframe.addEventListener('load', handleLoad)
|
||||||
return () => iframe.removeEventListener('load', handleLoad)
|
return () => {
|
||||||
|
iframe.removeEventListener('load', handleLoad)
|
||||||
|
if (fallbackTimer) clearTimeout(fallbackTimer)
|
||||||
|
}
|
||||||
}, [isSubmitting])
|
}, [isSubmitting])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -80,6 +93,9 @@ const Contact = () => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (!validateForm()) return
|
if (!validateForm()) return
|
||||||
setIsSubmitting(true)
|
setIsSubmitting(true)
|
||||||
|
if (typeof _wfa_track !== 'undefined' && _wfa_track.wfa_submit) {
|
||||||
|
_wfa_track.wfa_submit(e)
|
||||||
|
}
|
||||||
formRef.current.submit()
|
formRef.current.submit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue