fix: 10 bug fixes from code review (batch 0.6.5)

- #63: Fix industry.href undefined → use industry.id for navigation
- #50: Fix sanitized scope error in catch block (let before try)
- #58: Footer.jsx: convert all internal <a href> to <Link to>
- #61: Textarea.jsx: fix className interpolation (quotes → backticks)
- #59: About.jsx: convert CTA <a href> to <Link to>
- #60: Support.jsx: convert Contact button <a href> to <Link to>
- #62: Badge.jsx: text-foreground → text-text
- #64: Support.jsx: hover:bg-navy-darker → hover:bg-primary-navy-dark
- #65: Server: move timeoutMiddleware before catch-all routes
- #66: Contact.jsx: convert self-referencing <a href> to <Link to>
This commit is contained in:
null 2026-05-17 18:03:55 -05:00
parent 4f3e20b7a0
commit 1437b2af07
9 changed files with 54 additions and 47 deletions

View File

@ -1,7 +1,7 @@
{ {
"name": "queuenorth-website", "name": "queuenorth-website",
"private": true, "private": true,
"version": "0.6.2", "version": "0.6.5",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "concurrently \"vite\" \"node server/index.js\"", "dev": "concurrently \"vite\" \"node server/index.js\"",

View File

@ -369,6 +369,7 @@ app.get('/api/health', (req, res) => {
// Submit lead // Submit lead
app.post('/api/leads', express.json({ limit: '1mb' }), (req, res) => { app.post('/api/leads', express.json({ limit: '1mb' }), (req, res) => {
let sanitized
try { try {
const parsed = leadSchema.safeParse(req.body) const parsed = leadSchema.safeParse(req.body)
@ -386,7 +387,7 @@ app.post('/api/leads', express.json({ limit: '1mb' }), (req, res) => {
} }
// Sanitize parsed data before insert (trim, strip tags, truncate) // Sanitize parsed data before insert (trim, strip tags, truncate)
const sanitized = sanitizePayload(parsed.data, { sanitized = sanitizePayload(parsed.data, {
company: 200, company: 200,
name: 100, name: 100,
email: 254, email: 254,
@ -488,28 +489,6 @@ app.post('/api/support', express.json({ limit: '1mb' }), (req, res) => {
} }
}) })
// --- 404 catch-all for API routes (must be after all API routes) ---
app.use((req, res, next) => {
if (req.path.startsWith('/api')) {
log.warn(`API route not found: ${req.method} ${req.originalUrl}`)
return res.status(404).json({ error: 'Not found' })
}
next()
})
// Static file serving for SPA
app.use(express.static(path.join(__dirname, '../dist')))
// SPA catch-all — serve index.html for any non-API, non-asset route
// This lets React Router handle client-side routing
app.get('*', (req, res, next) => {
// Skip API routes (already handled above) and requests for static assets
if (req.path.startsWith('/api/') || req.path.includes('.')) {
return next()
}
res.sendFile(path.join(__dirname, '../dist/index.html'))
})
// --- Request timeout middleware (30 seconds) --- // --- Request timeout middleware (30 seconds) ---
const REQUEST_TIMEOUT_MS = 30000 const REQUEST_TIMEOUT_MS = 30000
@ -547,6 +526,28 @@ const PORT = process.env.SERVER_PORT || 3001
// Register timeout middleware BEFORE catch-all routes // Register timeout middleware BEFORE catch-all routes
app.use(timeoutMiddleware) app.use(timeoutMiddleware)
// --- 404 catch-all for API routes (must be after all API routes) ---
app.use((req, res, next) => {
if (req.path.startsWith('/api')) {
log.warn(`API route not found: ${req.method} ${req.originalUrl}`)
return res.status(404).json({ error: 'Not found' })
}
next()
})
// Static file serving for SPA
app.use(express.static(path.join(__dirname, '../dist')))
// SPA catch-all — serve index.html for any non-API, non-asset route
// This lets React Router handle client-side routing
app.get('*', (req, res, next) => {
// Skip API routes (already handled above) and requests for static assets
if (req.path.startsWith('/api/') || req.path.includes('.')) {
return next()
}
res.sendFile(path.join(__dirname, '../dist/index.html'))
})
app.listen(PORT, () => { app.listen(PORT, () => {
log.info(`Server running on http://localhost:${PORT}`) log.info(`Server running on http://localhost:${PORT}`)
log.info(`Health check: http://localhost:${PORT}/api/health`) log.info(`Health check: http://localhost:${PORT}/api/health`)

View File

@ -1,3 +1,5 @@
import { Link } from 'react-router-dom'
const Footer = () => { const Footer = () => {
const currentYear = new Date().getFullYear() const currentYear = new Date().getFullYear()
@ -58,12 +60,12 @@ const Footer = () => {
<div> <div>
<a href={`tel:${companyInfo.phone.replace(/\D/g, '')}`} className="hover:text-primary-cyan transition-colors">{companyInfo.phone}</a> <a href={`tel:${companyInfo.phone.replace(/\D/g, '')}`} className="hover:text-primary-cyan transition-colors">{companyInfo.phone}</a>
</div> </div>
<a href="/contact" className="inline-block text-primary-cyan hover:underline">Contact Form</a> <Link to="/contact" className="inline-block text-primary-cyan hover:underline">Contact Form</Link>
</div> </div>
<div className="mt-4"> <div className="mt-4">
<a href="/contact" className="inline-flex items-center justify-center rounded-md text-sm font-medium h-9 px-4 bg-primary-cyan text-primary-navy hover:bg-primary-cyan/90 transition-colors"> <Link to="/contact" className="inline-flex items-center justify-center rounded-md text-sm font-medium h-9 px-4 bg-primary-cyan text-primary-navy hover:bg-primary-cyan/90 transition-colors">
Request Consultation Request Consultation
</a> </Link>
</div> </div>
<p className="text-navy-light text-xs">© {currentYear} Queue North Technologies. All rights reserved.</p> <p className="text-navy-light text-xs">© {currentYear} Queue North Technologies. All rights reserved.</p>
</div> </div>
@ -74,12 +76,12 @@ const Footer = () => {
<ul className="space-y-2"> <ul className="space-y-2">
{quickLinks.map((link) => ( {quickLinks.map((link) => (
<li key={link.name}> <li key={link.name}>
<a <Link
href={link.href} to={link.href}
className="text-navy-light hover:text-white transition-colors text-sm" className="text-navy-light hover:text-white transition-colors text-sm"
> >
{link.name} {link.name}
</a> </Link>
</li> </li>
))} ))}
</ul> </ul>
@ -91,12 +93,12 @@ const Footer = () => {
<ul className="space-y-2"> <ul className="space-y-2">
{services.map((service) => ( {services.map((service) => (
<li key={service.name}> <li key={service.name}>
<a <Link
href={service.href} to={service.href}
className="text-navy-light hover:text-white transition-colors text-sm" className="text-navy-light hover:text-white transition-colors text-sm"
> >
{service.name} {service.name}
</a> </Link>
</li> </li>
))} ))}
</ul> </ul>
@ -108,12 +110,12 @@ const Footer = () => {
<ul className="space-y-2"> <ul className="space-y-2">
{industries.map((industry) => ( {industries.map((industry) => (
<li key={industry.name}> <li key={industry.name}>
<a <Link
href={industry.href} to={industry.href}
className="text-navy-light hover:text-white transition-colors text-sm" className="text-navy-light hover:text-white transition-colors text-sm"
> >
{industry.name} {industry.name}
</a> </Link>
</li> </li>
))} ))}
</ul> </ul>

View File

@ -7,7 +7,7 @@ const Badge = React.forwardRef(
const variants = { const variants = {
default: 'border-transparent bg-primary-navy text-white hover:bg-primary-navy-dark', default: 'border-transparent bg-primary-navy text-white hover:bg-primary-navy-dark',
secondary: 'border-transparent bg-section-alt text-text hover:bg-opacity-80', secondary: 'border-transparent bg-section-alt text-text hover:bg-opacity-80',
outline: 'text-foreground', outline: 'text-text',
success: 'border-transparent bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300', success: 'border-transparent bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300',
warning: 'border-transparent bg-yellow-100 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-300', warning: 'border-transparent bg-yellow-100 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-300',
error: 'border-transparent bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300', error: 'border-transparent bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300',

View File

@ -4,7 +4,7 @@ const Textarea = React.forwardRef(
({ className = '', ...props }, ref) => { ({ className = '', ...props }, ref) => {
return ( return (
<textarea <textarea
className="flex min-h-[80px] w-full rounded-md border border-border bg-background px-3 py-2 text-sm ring-offset-[#F8FAFC] placeholder:text-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-navy focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${className}" className={`flex min-h-[80px] w-full rounded-md border border-border bg-background px-3 py-2 text-sm ring-offset-[#F8FAFC] placeholder:text-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-navy focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${className}`}
ref={ref} ref={ref}
{...props} {...props}
/> />

View File

@ -1,3 +1,5 @@
import { Link } from 'react-router-dom'
const About = () => { const About = () => {
return ( return (
<> <>
@ -103,12 +105,12 @@ const About = () => {
<p className="text-xl text-soft-text mb-8 max-w-2xl mx-auto"> <p className="text-xl text-soft-text mb-8 max-w-2xl mx-auto">
Schedule a free consultation with our communications experts to discuss your needs. Schedule a free consultation with our communications experts to discuss your needs.
</p> </p>
<a <Link
href="/contact" to="/contact"
className="inline-block bg-primary-navy text-white px-8 py-3 rounded-md font-bold text-lg hover:bg-primary-navy-dark transition-colors" className="inline-block bg-primary-navy text-white px-8 py-3 rounded-md font-bold text-lg hover:bg-primary-navy-dark transition-colors"
> >
Request Consultation Request Consultation
</a> </Link>
</div> </div>
</div> </div>
</section> </section>

View File

@ -5,6 +5,7 @@ import { Button } from '@/components/ui/Button'
import { Input } from '@/components/ui/Input' import { Input } from '@/components/ui/Input'
import { Textarea } from '@/components/ui/Textarea' import { Textarea } from '@/components/ui/Textarea'
import { Select } from '@/components/ui/Select' import { Select } from '@/components/ui/Select'
import { Link } from 'react-router-dom'
import { api } from '@/lib/api' import { api } from '@/lib/api'
import { useDebounce } from '@/hooks/useDebounce' import { useDebounce } from '@/hooks/useDebounce'
@ -124,9 +125,9 @@ const Contact = () => {
</div> </div>
</div> </div>
<div className="mb-8"> <div className="mb-8">
<a href="/contact" className="inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-6 bg-primary-navy text-white hover:bg-primary-navy-dark transition-colors"> <Link to="/contact" className="inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-6 bg-primary-navy text-white hover:bg-primary-navy-dark transition-colors">
Request Consultation Request Consultation
</a> </Link>
</div> </div>
</div> </div>
</div> </div>

View File

@ -354,7 +354,7 @@ const Home = () => {
<p className="text-sm text-soft-text mb-4"> <p className="text-sm text-soft-text mb-4">
Industry-specific solutions designed to address your unique challenges and requirements. Industry-specific solutions designed to address your unique challenges and requirements.
</p> </p>
<Button variant="link" className="text-primary-navy p-0 h-auto text-sm" onClick={() => navigate(industry.href)}> <Button variant="link" className="text-primary-navy p-0 h-auto text-sm" onClick={() => navigate(`/industries/${industry.id}`)}>
Learn more Learn more
</Button> </Button>
</CardContent> </CardContent>

View File

@ -5,6 +5,7 @@ import { Button } from '@/components/ui/Button'
import { Input } from '@/components/ui/Input' import { Input } from '@/components/ui/Input'
import { Textarea } from '@/components/ui/Textarea' import { Textarea } from '@/components/ui/Textarea'
import { Select } from '@/components/ui/Select' import { Select } from '@/components/ui/Select'
import { Link } from 'react-router-dom'
import { api } from '@/lib/api' import { api } from '@/lib/api'
import { useDebounce } from '@/hooks/useDebounce' import { useDebounce } from '@/hooks/useDebounce'
@ -127,9 +128,9 @@ const Support = () => {
</div> </div>
</div> </div>
<div className="mb-8"> <div className="mb-8">
<a href="/contact" className="inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-6 bg-primary-navy text-white hover:bg-primary-navy-dark transition-colors"> <Link to="/contact" className="inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-6 bg-primary-navy text-white hover:bg-primary-navy-dark transition-colors">
Request Consultation Request Consultation
</a> </Link>
</div> </div>
</div> </div>
</div> </div>
@ -153,7 +154,7 @@ const Support = () => {
href="https://queuenorth.zoho.com/" href="https://queuenorth.zoho.com/"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center justify-center px-4 py-2 bg-primary-navy text-white font-medium rounded-md hover:bg-navy-darker transition-colors text-sm" className="inline-flex items-center justify-center px-4 py-2 bg-primary-navy text-white font-medium rounded-md hover:bg-primary-navy-dark transition-colors text-sm"
> >
Visit Support Center Visit Support Center
<svg className="ml-2 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="ml-2 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">