diff --git a/package-lock.json b/package-lock.json
index 94f1dde..1c3a43c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,16 +1,15 @@
{
"name": "queuenorth-website",
- "version": "0.6.6",
+ "version": "0.7.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "queuenorth-website",
- "version": "0.6.6",
+ "version": "0.7.0",
"dependencies": {
"@radix-ui/react-dialog": "^1.1.0",
"@radix-ui/react-visually-hidden": "^1.2.4",
- "@tanstack/react-query": "^5.62.0",
"better-sqlite3": "^11.8.0",
"cors": "^2.8.6",
"express": "^4.21.2",
@@ -1646,32 +1645,6 @@
"win32"
]
},
- "node_modules/@tanstack/query-core": {
- "version": "5.100.10",
- "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.10.tgz",
- "integrity": "sha512-8UR0yJR+GiQ40m3lPhUr0xbfAupe6GSQiksSBSa9SM2NjezFyxXCIA69/lz8cSoNKZLrw1/PktIyQBJcVeMi3w==",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/tannerlinsley"
- }
- },
- "node_modules/@tanstack/react-query": {
- "version": "5.100.10",
- "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.10.tgz",
- "integrity": "sha512-FLaZf2RCrA/Zgp4aiu5tG3TyasTRO7aZ99skxQpr3Hg/zXOhu6yq5FZCYQ/tRaJtM9ylnoK8tFK7PolXQadv6Q==",
- "license": "MIT",
- "dependencies": {
- "@tanstack/query-core": "5.100.10"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/tannerlinsley"
- },
- "peerDependencies": {
- "react": "^18 || ^19"
- }
- },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
diff --git a/package.json b/package.json
index 955f9b4..cb63376 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,6 @@
"dependencies": {
"@radix-ui/react-dialog": "^1.1.0",
"@radix-ui/react-visually-hidden": "^1.2.4",
- "@tanstack/react-query": "^5.62.0",
"better-sqlite3": "^11.8.0",
"cors": "^2.8.6",
"express": "^4.21.2",
diff --git a/server/index.js b/server/index.js
index 0b649b1..f2aa5b9 100644
--- a/server/index.js
+++ b/server/index.js
@@ -74,6 +74,10 @@ const cspDirectives = {
formAction: ["'self'"],
}
+// Note: connectSrc currently allows 'self' only. Zoho API calls are server-to-server
+// and are not affected by CSP. If client-side Zoho calls are added in the future,
+// add Zoho domains here (e.g., 'https://www.zohoapis.com', 'https://accounts.zoho.com')
+
app.use(helmet({
contentSecurityPolicy: {
directives: cspDirectives,
@@ -95,6 +99,16 @@ app.use(helmet({
log.info('[Security] Helmet enabled with CSP configured')
+// Redirect HTTP to HTTPS in production
+if (process.env.NODE_ENV === 'production') {
+ app.use((req, res, next) => {
+ if (req.headers['x-forwarded-proto'] === 'http') {
+ return res.redirect(301, `https://${req.headers.host}${req.url}`)
+ }
+ next()
+ })
+}
+
// --- CORS Configuration ---
const corsOrigin = process.env.CORS_ORIGIN || 'https://queuenorth.com' // Default to production domain
const corsConfig = cors({
diff --git a/src/lib/api.js b/src/lib/api.js
index c3a64ea..7a4cf75 100644
--- a/src/lib/api.js
+++ b/src/lib/api.js
@@ -1,6 +1,36 @@
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:3001/api'
-// Exponential backoff retry helper
+export async function submitLead(data) {
+ const response = await fetch(`${API_BASE_URL}/leads`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(data),
+ })
+ if (!response.ok) {
+ const errorData = await response.json().catch(() => ({}))
+ const error = new Error(errorData.error || `API error: ${response.status}`)
+ error.response = { status: response.status }
+ throw error
+ }
+ return response.json()
+}
+
+export async function submitSupport(data) {
+ const response = await fetch(`${API_BASE_URL}/support`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(data),
+ })
+ if (!response.ok) {
+ const errorData = await response.json().catch(() => ({}))
+ const error = new Error(errorData.error || `API error: ${response.status}`)
+ error.response = { status: response.status }
+ throw error
+ }
+ return response.json()
+}
+
+// Exponential backoff retry helper (deprecated, kept for other API calls)
const retryFetch = async (fn, { maxRetries = 3, baseDelay = 1000 } = {}) => {
let lastError
for (let attempt = 0; attempt <= maxRetries; attempt++) {
diff --git a/src/main.jsx b/src/main.jsx
index 2fc67e2..554883e 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -1,28 +1,17 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { RouterProvider } from 'react-router-dom'
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { Toaster } from 'sonner'
import { HelmetProvider } from 'react-helmet-async'
import router from './router.jsx'
import App from './App.jsx'
-const queryClient = new QueryClient({
- defaultOptions: {
- queries: {
- staleTime: 1000 * 60 * 5, // 5 minutes
- },
- },
-})
-
// Wrap the router with providers
const Root = () => (