This commit is contained in:
Todd
2026-05-24 13:30:30 -04:00
parent 753bc04141
commit f29035ce81
26 changed files with 1092 additions and 385 deletions

View File

@ -8,112 +8,137 @@
{
"id": "swing-residential",
"name": "Residential Swing Gate Operator",
"model": "SG-1000",
"model": "LA500",
"category": "swing",
"description": "12V DC swing gate operator for residential gates up to 14 ft",
"description": "24V DC Swing Gate Linear Operator ",
"basePrice": 2495,
"image": "swing",
"imageFile": "liftmaster-la500-bundle.jpg",
"requiredParts": [
{ "id": "op-unit-swing", "name": "Operator Unit (SG-1000)", "qty": 2, "unitPrice": 1247.50 },
{ "id": "bracket-kit", "name": "Gate Bracket Kit", "qty": 2, "unitPrice": 85 },
{ "id": "photo-eye", "name": "Photo Eye Kit", "qty": 1, "unitPrice": 125 },
{ "id": "hinges", "name": "Gate Hinges (Heavy Duty)", "qty": 4, "unitPrice": 45 }
{ "id": "op-unit-swing", "name": "Operator Unit (LA500)", "qty": 2, "unitPrice": 1247.50 },
{ "id": "mounting-post", "name": "Mounting Post", "qty": 1, "unitPrice": 85 },
{ "id": "prep-cost", "name": "In House Prep Cost", "qty": 1, "unitPrice": 150 },
{ "id": "shipping", "name": "Shipping Cost", "qty": 1, "unitPrice": 200 }
]
},
{
"id": "swing-commercial",
"name": "Commercial Swing Gate Operator",
"model": "SG-2000",
"model": "CSW24UL",
"category": "swing",
"description": "24V DC heavy-duty swing operator for commercial gates up to 24 ft",
"description": "24V DC Heavy Duty Swing Gate Operator",
"basePrice": 3995,
"image": "swing",
"imageFile": "liftmaster-csw24.jpg",
"requiredParts": [
{ "id": "op-unit-swing-hd", "name": "Heavy Duty Operator Unit (SG-2000)", "qty": 2, "unitPrice": 1997.50 },
{ "id": "bracket-kit-hd", "name": "Commercial Bracket Kit", "qty": 2, "unitPrice": 145 },
{ "id": "photo-eye", "name": "Photo Eye Kit", "qty": 1, "unitPrice": 125 },
{ "id": "hinges-hd", "name": "Commercial Gate Hinges", "qty": 4, "unitPrice": 75 },
{ "id": "warning-light", "name": "Strobe Warning Light", "qty": 1, "unitPrice": 195 }
{ "id": "op-unit-swing-hd", "name": "Heavy Duty Operator Unit (CSW24UL)", "qty": 2, "unitPrice": 1997.50 },
{ "id": "mount-pad", "name": "Mounting Pad", "qty": 2, "unitPrice": 145 },
{ "id": "prep-cost", "name": "In House Prep Cost", "qty": 1, "unitPrice": 150 },
{ "id": "shipping", "name": "Shipping Cost", "qty": 1, "unitPrice": 200 },
{ "id": "mounting-post", "name": "Mounting Post", "qty": 2, "unitPrice": 85 }
]
},
{
"id": "slide-residential",
"name": "Light Commercial Slide Gate Operator",
"model": "SL-1500",
"model": "CSL24UL",
"category": "slide",
"description": "12V DC slide operator for residential and light commercial gates up to 20 ft",
"description": "24V DC Light Duty Slide Gate Operator",
"basePrice": 2495,
"image": "slide",
"imageFile": "liftmaster-csl24.jpg",
"requiredParts": [
{ "id": "op-unit-slide-lc", "name": "Slide Operator Unit (SL-1500)", "qty": 1, "unitPrice": 2495 },
{ "id": "rack-kit-lc", "name": "Slide Gate Rack Kit (10ft sections)", "qty": 2, "unitPrice": 175 },
{ "id": "photo-eye", "name": "Photo Eye Kit", "qty": 1, "unitPrice": 125 }
{ "id": "op-unit-slide-lc", "name": "Slide Operator Unit (CSL24UL)", "qty": 1, "unitPrice": 2495 },
{ "id": "mount-pad", "name": "Mounting Pad", "qty": 2, "unitPrice": 145 },
{ "id": "prep-cost", "name": "In House Prep Cost", "qty": 1, "unitPrice": 150 },
{ "id": "shipping", "name": "Shipping Cost", "qty": 1, "unitPrice": 200 },
{ "id": "mounting-post", "name": "Mounting Post", "qty": 2, "unitPrice": 85 }
]
},
{
"id": "slide-commercial",
"name": "Commercial Slide Gate Operator",
"model": "SL-3000",
"model": "IHSL24UL",
"category": "slide",
"description": "24V DC slide gate operator for commercial gates up to 40 ft",
"description": "24V DC Commercial Gate Operator",
"basePrice": 3895,
"image": "slide",
"imageFile": "liftmaster-ihsl24ul.jpg",
"requiredParts": [
{ "id": "op-unit-slide", "name": "Heavy Duty Operator Unit (SL-3000)", "qty": 1, "unitPrice": 3895 },
{ "id": "rack-kit", "name": "Slide Gate Rack Kit (10ft sections)", "qty": 4, "unitPrice": 175 },
{ "id": "photo-eye", "name": "Photo Eye Kit", "qty": 1, "unitPrice": 125 },
{ "id": "warning-light", "name": "Strobe Warning Light", "qty": 1, "unitPrice": 195 },
{ "id": "sensor-loop", "name": "Vehicle Loop Sensor", "qty": 1, "unitPrice": 285 }
{ "id": "op-unit-slide", "name": "Slide Operator Unit (IHSL24UL)", "qty": 1, "unitPrice": 3895 },
{ "id": "prep-cost", "name": "In House Prep Cost", "qty": 1, "unitPrice": 150 },
{ "id": "shipping", "name": "Shipping Cost", "qty": 1, "unitPrice": 200 },
{ "id": "mounting-post", "name": "Mounting Post", "qty": 2, "unitPrice": 85 }
]
},
{
"id": "barrier-parking",
"name": "Parking Barrier Gate Operator",
"model": "BG-200",
"name": "Barrier Gate Operator",
"model": "MAT",
"category": "barrier",
"description": "120V AC barrier gate for parking lots and access control up to 20 ft",
"description": "Mega Arm Tower Barrier Gate Operator",
"basePrice": 3195,
"image": "barrier",
"imageFile": "Mat.jpeg",
"requiredParts": [
{ "id": "op-unit-barrier", "name": "Barrier Arm Assembly (BG-200)", "qty": 1, "unitPrice": 3195 },
{ "id": "barrier-arm", "name": "Barrier Arm (8ft Aluminum)", "qty": 1, "unitPrice": 395 },
{ "id": "loop-detector", "name": "Loop Detector Card", "qty": 1, "unitPrice": 345 },
{ "id": "warning-light", "name": "LED Warning Light", "qty": 2, "unitPrice": 145 },
{ "id": "signage", "name": "Gate Signage Kit", "qty": 1, "unitPrice": 120 }
{ "id": "op-unit-barrier", "name": "Mega Arm Tower Barrier Gate Operator(MAT)", "qty": 1, "unitPrice": 3195 },
{ "id": "barrier-arm", "name": "Barrier Arm (8ft Aluminum)", "qty": 1, "unitPrice": 395 }
]
},
{
"id": "barrier-heavy",
"name": "Heavy Duty Barrier Gate Operator",
"model": "BG-500",
"name": "Techna Barrier Gate Operator",
"model": "CBG24DC",
"category": "barrier",
"description": "230V AC heavy-duty barrier gate for industrial sites up to 30 ft",
"description": "Commercial Barrier Gate operator",
"basePrice": 5495,
"image": "barrier",
"imageFile": "techno.jpeg",
"requiredParts": [
{ "id": "op-unit-barrier-hd", "name": "Heavy Duty Barrier Assembly (BG-500)", "qty": 1, "unitPrice": 5495 },
{ "id": "barrier-arm-hd", "name": "Barrier Arm (16ft Steel)", "qty": 1, "unitPrice": 695 },
{ "id": "loop-detector", "name": "Loop Detector Card", "qty": 2, "unitPrice": 345 },
{ "id": "warning-light-hd", "name": "LED Warning Light Kit", "qty": 2, "unitPrice": 195 },
{ "id": "signage", "name": "Gate Signage Kit", "qty": 1, "unitPrice": 120 },
{ "id": "safety-edge", "name": "Safety Edge Sensor", "qty": 1, "unitPrice": 295 }
{ "id": "op-unit-barrier-hd", "name": "Techna Barrier Gate Operator (CBG24DC)", "qty": 1, "unitPrice": 5495 },
{ "id": "barrier-arm-hd", "name": "Barrier Arm (16ft Steel)", "qty": 1, "unitPrice": 695 }
]
},
{
"id": "tilt",
"name": "Tilt Gate Operator",
"model": "",
"category": "tilt",
"description": "Enter tilt gate operator details below",
"basePrice": 0,
"image": "tilt",
"imageFile": "",
"isCustom": true,
"requiredParts": []
}
],
"groundLoopStyles": [
{ "id": "saw-cut", "name": "Saw-Cut Installation", "description": "Loop cut into existing pavement and sealed flush", "additionalCost": 0 },
{ "id": "pave-over", "name": "Pave-Over Installation", "description": "Surface-mount loop installed on top of pavement with protective casing", "additionalCost": 195 }
],
"loopDetectors": [
{ "id": "single", "name": "Single-Channel Loop Detector", "description": "Detects vehicle presence over the loop wire", "price": 250 }
],
"groundLoopSizes": [
{ "id": "4x8", "name": "4' x 8'", "description": "Standard loop size for residential applications", "additionalCost": 0 },
{ "id": "6x12", "name": "6' x 12'", "description": "Larger loop size for commercial or wide driveways", "additionalCost": 175 }
],
"groundLoopTypes": [
{ "id": "interrupt", "name": "Interrupt Loop", "description": "Primary vehicle detection loop for gate activation", "price": 425 },
{ "id": "shadow", "name": "Shadow Loop", "description": "Secondary safety loop located behind gate to prevent closure on vehicle", "price": 375 },
{ "id": "exit", "name": "Exit Loop", "description": "Free exit loop on departure side for automatic exit detection", "price": 375 }
],
"remoteButtonOptions": [
{ "id": "1", "name": "1-Button Remote", "price": 95 },
{ "id": "2", "name": "2-Button Remote", "price": 125 },
{ "id": "3", "name": "3-Button Remote", "price": 165 },
{ "id": "4", "name": "4-Button Remote", "price": 195 }
],
"accessControl": [
{ "id": "keypad", "name": "Digital Keypad", "model": "KP-200", "description": "Weatherproof digital keypad with backlit keys, 500 user codes", "price": 295 },
{ "id": "card-reader", "name": "Proximity Card Reader", "model": "PR-500", "description": "125kHz proximity card reader with 1000 card capacity", "price": 445 },
{ "id": "intercom", "name": "Audio/Video Intercom", "model": "AV-700", "description": "2-wire audio/video intercom with color camera", "price": 895 },
{ "id": "remote-kit", "name": "Remote Control Kit", "model": "RC-4", "description": "4-button remote control with 2 remotes, rolling code", "price": 195 },
{ "id": "remote-kit", "name": "Remote Control", "model": "", "description": "Remote controls with rolling code technology", "price": 0 },
{ "id": "solar-kit", "name": "Solar Panel Kit", "model": "SP-100", "description": "100W solar panel with charge controller and battery", "price": 695 },
{ "id": "gsm-controller", "name": "GSM Cellular Controller", "model": "GSM-4G", "description": "4G cellular controller with app, no phone line needed", "price": 595 }
]

View File

@ -1,35 +1,98 @@
require('dotenv').config({ path: require('path').join(__dirname, '..', '.env') });
const express = require('express');
const cors = require('cors');
const path = require('path');
const fs = require('fs');
const os = require('os');
const session = require('express-session');
const app = express();
const PORT = process.env.PORT || 3001;
app.use(cors());
app.use(cors({
origin: process.env.NODE_ENV === 'production' ? false : 'http://localhost:5173',
credentials: true,
}));
app.use(express.json());
app.use(session({
secret: process.env.APP_SESSION_SECRET || 'dev-secret-change-in-production',
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: process.env.NODE_ENV === 'production' ? 'strict' : 'lax',
maxAge: 24 * 60 * 60 * 1000, // 24 hours
},
}));
const pricingData = JSON.parse(
fs.readFileSync(path.join(__dirname, 'data', 'pricing.json'), 'utf-8')
);
app.get('/api/pricing', (req, res) => {
// Auth middleware
function requireAuth(req, res, next) {
if (req.session && req.session.authenticated) {
return next();
}
res.status(401).json({ error: 'Unauthorized' });
}
// Login
app.post('/api/login', (req, res) => {
const { password } = req.body;
if (password === process.env.APP_PASSWORD) {
req.session.authenticated = true;
return res.json({ success: true });
}
res.status(401).json({ error: 'Invalid password' });
});
// Logout
app.post('/api/logout', (req, res) => {
req.session.destroy((err) => {
if (err) {
return res.status(500).json({ error: 'Failed to logout' });
}
res.clearCookie('connect.sid');
res.json({ success: true });
});
});
// Check auth status
app.get('/api/auth/status', (req, res) => {
res.json({ authenticated: !!(req.session && req.session.authenticated) });
});
// Protected API routes
app.get('/api/pricing', requireAuth, (req, res) => {
res.json(pricingData);
});
app.get('/api/pricing/:category', (req, res) => {
app.get('/api/pricing/:category', requireAuth, (req, res) => {
const { category } = req.params;
const validCategories = ['operators', 'groundLoopStyles', 'groundLoopTypes', 'accessControl'];
const validCategories = ['operators', 'groundLoopStyles', 'groundLoopSizes', 'loopDetectors', 'groundLoopTypes', 'accessControl', 'remoteButtonOptions'];
if (validCategories.includes(category)) {
return res.json(pricingData[category]);
}
res.status(400).json({ error: `Invalid category. Use: ${validCategories.join(', ')}` });
});
// Serve static files with auth protection
const clientDist = path.join(__dirname, '..', 'client', 'dist');
if (fs.existsSync(clientDist)) {
app.use(express.static(clientDist));
app.get('*', (req, res) => {
// Allow unauthenticated access to login page assets and auth endpoints
app.use('/assets', express.static(path.join(clientDist, 'assets')));
// Login page is always accessible
app.get('/login', (req, res) => {
res.sendFile(path.join(clientDist, 'index.html'));
});
// All other routes require auth
app.get('*', requireAuth, (req, res) => {
res.sendFile(path.join(clientDist, 'index.html'));
});
}
@ -47,5 +110,6 @@ function getLocalIP() {
app.listen(PORT, '0.0.0.0', () => {
const ip = getLocalIP();
console.log(`\n Local: http://localhost:${PORT}`);
console.log(` Network: http://${ip}:${PORT}\n`);
console.log(` Network: http://${ip}:${PORT}`);
console.log(` Login: http://localhost:${PORT}/login\n`);
});

View File

@ -9,7 +9,9 @@
"version": "1.0.0",
"dependencies": {
"cors": "^2.8.5",
"express": "^4.18.2"
"dotenv": "^17.4.2",
"express": "^4.18.2",
"express-session": "^1.19.0"
}
},
"node_modules/accepts": {
@ -174,6 +176,18 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/dotenv": {
"version": "17.4.2",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
"integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@ -294,6 +308,29 @@
"url": "https://opencollective.com/express"
}
},
"node_modules/express-session": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.19.0.tgz",
"integrity": "sha512-0csaMkGq+vaiZTmSMMGkfdCOabYv192VbytFypcvI0MANrp+4i/7yEkJ0sbAEhycQjntaKGzYfjfXQyVb7BHMA==",
"license": "MIT",
"dependencies": {
"cookie": "~0.7.2",
"cookie-signature": "~1.0.7",
"debug": "~2.6.9",
"depd": "~2.0.0",
"on-headers": "~1.1.0",
"parseurl": "~1.3.3",
"safe-buffer": "~5.2.1",
"uid-safe": "~2.1.5"
},
"engines": {
"node": ">= 0.8.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/finalhandler": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
@ -576,6 +613,15 @@
"node": ">= 0.8"
}
},
"node_modules/on-headers": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
"integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@ -619,6 +665,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/random-bytes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
"integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@ -823,6 +878,18 @@
"node": ">= 0.6"
}
},
"node_modules/uid-safe": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
"license": "MIT",
"dependencies": {
"random-bytes": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",

View File

@ -8,6 +8,8 @@
},
"dependencies": {
"cors": "^2.8.5",
"express": "^4.18.2"
"dotenv": "^17.4.2",
"express": "^4.18.2",
"express-session": "^1.19.0"
}
}