diff --git a/project-requirements.md b/project-requirements.md new file mode 100644 index 0000000..82115dc --- /dev/null +++ b/project-requirements.md @@ -0,0 +1,67 @@ +# Project Requirements — Queue North Website + +These requirements apply to all agents working on Queue North Website. + +## Project Philosophy + +- Feel modern for 2026 standards +- Prioritize responsiveness and reactivity +- Provide smooth user interaction +- Avoid outdated UI/UX patterns +- Maintain fast perceived performance +- Remain lightweight and maintainable +- Prioritize usability over unnecessary complexity + +## Technology Stack + +- **Build:** Vite +- **Frontend:** React 19 with client-side routing (React Router 7) +- **Styling:** Tailwind CSS with custom Queue North theme +- **UI Components:** shadcn/ui-style local primitives (Button, Card, Input, etc.) +- **State:** TanStack Query for server state +- **Notifications:** Sonner (toast) +- **Backend:** Express (Node.js) +- **Database:** SQLite via better-sqlite3 +- **NOT Next.js.** This project uses Vite + React SPA, not Next.js App Router. + +## Frontend Standards + +- React SPA with React Router (no SSR, no server components) +- shadcn/ui-style primitives in `src/components/ui/` +- Tailwind utilities cleanly and predictably +- Responsive design (mobile + desktop) +- Loading states, error states, accessible interfaces +- Queue North brand colors: navy, light blue, white palette +- Georgia font for numeric content + +## Backend Standards + +- Express.js REST API +- SQLite via better-sqlite3 +- Lead capture endpoints (`/api/leads`, `/api/support`) +- Validate all input, sanitize user-supplied data +- Structured error handling, no silent failures +- Environment variables for configuration, no hardcoded secrets + +## Database Standards + +- SQLite only +- Validate schema changes before deployment + +## Code Quality + +- Readable, maintainable, no overengineering +- Remove dead code, consistent formatting +- Document non-obvious logic +- Prefer clarity over cleverness + +## Security + +- OWASP best practices +- Input validation on all endpoints +- No secrets in logs +- Review dependencies for vulnerabilities + +## Requirement Change Policy + +Requirements may NOT be modified without explicit approval from `_null`. \ No newline at end of file diff --git a/scripts/docker-test.sh b/scripts/docker-test.sh new file mode 100755 index 0000000..845e870 --- /dev/null +++ b/scripts/docker-test.sh @@ -0,0 +1,142 @@ +#!/bin/bash + +# Docker persistence test script for Queue North Website +# Verifies SQLite database survives container restart with volume mount + +set -e + +echo "=== Docker Persistence Test for Queue North Website ===" + +# Stop any existing container +docker stop queuenorth-test 2>/dev/null || true +docker rm queuenorth-test 2>/dev/null || true + +# Remove any existing volumes for fresh start +docker volume rm queuenorth-test-db 2>/dev/null || true +docker volume rm queuenorth-test-logs 2>/dev/null || true + +# Build fresh image +echo "Building fresh Docker image..." +docker build -t queuenorth-test . + +# Create volumes for persistence +docker volume create queuenorth-test-db > /dev/null +docker volume create queuenorth-test-logs > /dev/null + +# Run container with volume mount +echo "Starting container with persistent volume..." +docker run -d \ + --name queuenorth-test \ + -p 3002:3001 \ + -v queuenorth-test-db:/app/db \ + -v queuenorth-test-logs:/app/logs \ + -e NODE_ENV=production \ + -e SERVER_PORT=3001 \ + queuenorth-test + +# Wait for server to be ready +echo "Waiting for server to be ready..." +sleep 5 + +# Verify health endpoint +if curl -s http://localhost:3002/api/health | grep -q '"status":"ok"'; then + echo "✓ Health check passed" +else + echo "✗ Health check failed" + docker logs queuenorth-test + exit 1 +fi + +# Insert test data via API +echo "Inserting test lead data..." +curl -s -X POST http://localhost:3002/api/leads \ + -H "Content-Type: application/json" \ + -d '{"company":"Docker Test Co","name":"Docker Test User","email":"docker@test.com","phone":"555-DOCKER","zip":"54321","message":"Docker persistence test","service_interest":"contact-center"}' > /dev/null + +echo "Inserting test support request data..." +curl -s -X POST http://localhost:3002/api/support \ + -H "Content-Type: application/json" \ + -d '{"name":"Docker Test User","company":"Docker Test Co","email":"docker@test.com","phone":"555-DOCKER","issue":"Docker persistence test support request","priority":"medium"}' > /dev/null + +# Verify data was inserted using Node.js (sqlite3 CLI not available in container) +echo "Verifying data in database..." + +# Check leads table +if docker exec queuenorth-test node -e " + const sqlite3 = require('better-sqlite3'); + const db = new sqlite3('/app/db/queuenorth.db'); + const row = db.prepare('SELECT COUNT(*) as c FROM leads WHERE email=?').get('docker@test.com'); + console.log(row.c); + process.exit(row.c === 1 ? 0 : 1); +" 2>/dev/null; then + echo "✓ Test lead data persisted in database" +else + echo "✗ Test lead data NOT persisted in database" + exit 1 +fi + +# Check support_requests table +if docker exec queuenorth-test node -e " + const sqlite3 = require('better-sqlite3'); + const db = new sqlite3('/app/db/queuenorth.db'); + const row = db.prepare('SELECT COUNT(*) as c FROM support_requests WHERE email=?').get('docker@test.com'); + console.log(row.c); + process.exit(row.c === 1 ? 0 : 1); +" 2>/dev/null; then + echo "✓ Test support request data persisted in database" +else + echo "✗ Test support request data NOT persisted in database" + exit 1 +fi + +# Stop the container (simulates restart) +echo "Stopping container to simulate restart..." +docker stop queuenorth-test > /dev/null +sleep 2 + +# Restart with same volume mount +echo "Restarting container with same volume..." +docker start queuenorth-test > /dev/null +sleep 3 + +# Verify data is still there after restart +echo "Verifying data persists after restart..." + +# Check leads after restart +if docker exec queuenorth-test node -e " + const sqlite3 = require('better-sqlite3'); + const db = new sqlite3('/app/db/queuenorth.db'); + const row = db.prepare('SELECT COUNT(*) as c FROM leads WHERE email=?').get('docker@test.com'); + console.log(row.c); + process.exit(row.c === 1 ? 0 : 1); +" 2>/dev/null; then + echo "✓ Data persists after container restart" +else + echo "✗ Data NOT persisted after container restart" + exit 1 +fi + +# Check support_requests after restart +if docker exec queuenorth-test node -e " + const sqlite3 = require('better-sqlite3'); + const db = new sqlite3('/app/db/queuenorth.db'); + const row = db.prepare('SELECT COUNT(*) as c FROM support_requests WHERE email=?').get('docker@test.com'); + console.log(row.c); + process.exit(row.c === 1 ? 0 : 1); +" 2>/dev/null; then + echo "✓ Support requests data persists after container restart" +else + echo "✗ Support requests data NOT persisted after container restart" + exit 1 +fi + +# Cleanup +echo "Cleaning up..." +docker stop queuenorth-test > /dev/null +docker rm queuenorth-test > /dev/null +docker volume rm queuenorth-test-db > /dev/null +docker volume rm queuenorth-test-logs > /dev/null + +echo "" +echo "=== All Docker persistence tests passed! ===" +echo "SQLite database correctly persists across container restarts with volume mount." diff --git a/src/App.css b/src/App.css deleted file mode 100644 index 287352d..0000000 --- a/src/App.css +++ /dev/null @@ -1,69 +0,0 @@ -/* App styles */ -@tailwind base; -@tailwind components; -@tailwind utilities; - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -html { - scroll-behavior: smooth; -} - -body { - font-family: 'Inter', sans-serif; - color: #0F172A; - background-color: #F8FAFC; - line-height: 1.5; - -webkit-font-smoothing: antialiased; -} - -img { - max-width: 100%; - display: block; -} - -a { - color: #0EA5E9; - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - -.container { - max-width: 1200px; - margin: 0 auto; - padding: 0 16px; -} - -/* Section spacing - mobile first */ -.section { - padding: 4rem 0; -} - -/* Desktop section spacing */ -@media (min-width: 1024px) { - .section { - padding: 6rem 0; - } -} - -/* Hero section styling */ -.hero { - min-height: 70vh; - display: flex; - align-items: center; - background: linear-gradient(135deg, #0B2A3C 0%, #071A2A 100%); - color: white; - padding: 4rem 0 5rem; -} - -/* Light section background */ -.section-alt { - background: #EEF6FB; -} diff --git a/src/App.jsx b/src/App.jsx index d73c40e..27d1100 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -2,7 +2,7 @@ import { Outlet } from 'react-router-dom' import Header from './components/layout/Header.jsx' import Footer from './components/layout/Footer.jsx' import MobileNav from './components/layout/MobileNav.jsx' -import './App.css' +import './index.css' function App() { return ( diff --git a/src/components/layout/Header.jsx b/src/components/layout/Header.jsx index 1b966de..bfecf64 100644 --- a/src/components/layout/Header.jsx +++ b/src/components/layout/Header.jsx @@ -1,5 +1,6 @@ import { useState, useEffect } from 'react' import { SheetTrigger } from '@/components/ui/Sheet' +import { Link } from 'react-router-dom' const Header = () => { const [isScrolled, setIsScrolled] = useState(false) @@ -40,21 +41,21 @@ const Header = () => { {/* Desktop Nav */} {/* CTA Button */}
{/* Mobile Menu Toggle */} diff --git a/src/components/layout/MobileNav.jsx b/src/components/layout/MobileNav.jsx index ea7fea2..f7136c4 100644 --- a/src/components/layout/MobileNav.jsx +++ b/src/components/layout/MobileNav.jsx @@ -1,5 +1,6 @@ import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/Sheet' import { useState } from 'react' +import { Link } from 'react-router-dom' const MobileNav = () => { const [isOpen, setIsOpen] = useState(false) @@ -71,13 +72,13 @@ const MobileNav = () => {