Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
986d159d20 |
33
admin.html
@ -2,38 +2,11 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
<meta http-equiv="refresh" content="0; url=box-editor.html" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Admin Box Editor</title>
|
<title>Redirecting...</title>
|
||||||
<link href="css/style.css" rel="stylesheet" type="text/css" />
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="admin-page">
|
<p>Redirecting to <a href="box-editor.html">Box Data Editor</a>...</p>
|
||||||
<div class="admin-panel">
|
|
||||||
<div class="admin-header">
|
|
||||||
<div>
|
|
||||||
<h1>Administrator box editor</h1>
|
|
||||||
<p>Change the price, picture, and description for all 8 boxes.</p>
|
|
||||||
</div>
|
|
||||||
<a class="auth-tab admin-home-link" href="index.html">Back to home</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="admin-code-row">
|
|
||||||
<label for="admin-code">Admin code</label>
|
|
||||||
<input class="auth-input" id="admin-code" type="password" placeholder="Enter admin code" autocomplete="off" />
|
|
||||||
<button class="auth-submit admin-small-button" id="load-boxes" type="button">Load boxes</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p id="admin-message" class="auth-note">Enter the admin code to edit box details.</p>
|
|
||||||
|
|
||||||
<form id="admin-form" class="admin-grid" style="display:none;"></form>
|
|
||||||
|
|
||||||
<div class="admin-actions" id="admin-actions" style="display:none;">
|
|
||||||
<button class="auth-submit" id="save-boxes" type="button">Save all box changes</button>
|
|
||||||
<a class="auth-tab admin-home-link" href="order.html">View order page</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="js/admin.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
58
backend/data/boxes.json
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "box-1",
|
||||||
|
"name": "Small Shipping Box",
|
||||||
|
"price": 9.99,
|
||||||
|
"image": "images/boxes/box-1.svg",
|
||||||
|
"description": "Compact corrugated box for light parts, samples, and small product shipments."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "box-2",
|
||||||
|
"name": "Medium Mailer Box",
|
||||||
|
"price": 12.5,
|
||||||
|
"image": "images/boxes/box-2.svg",
|
||||||
|
"description": "Strong everyday mailer with extra room for inserts, labels, and protective padding."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "box-3",
|
||||||
|
"name": "Heavy Duty Carton",
|
||||||
|
"price": 15.25,
|
||||||
|
"image": "images/boxes/box-3.svg",
|
||||||
|
"description": "Durable double-wall carton built for heavier items and warehouse handling."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "box-4",
|
||||||
|
"name": "Retail Display Box",
|
||||||
|
"price": 18,
|
||||||
|
"image": "images/boxes/box-4.svg",
|
||||||
|
"description": "Clean presentation box designed for retail shelves, kits, and branded packaging."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "box-5",
|
||||||
|
"name": "Long Parts Box",
|
||||||
|
"price": 14.75,
|
||||||
|
"image": "images/boxes/box-5.svg",
|
||||||
|
"description": "Extended-length box for narrow components, rods, trims, and specialty parts."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "box-6",
|
||||||
|
"name": "Deep Storage Box",
|
||||||
|
"price": 16.5,
|
||||||
|
"image": "images/boxes/box-6.svg",
|
||||||
|
"description": "Deep format storage box for bulk inventory, stacked packing, and backroom use."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "box-7",
|
||||||
|
"name": "Flat Pack Box",
|
||||||
|
"price": 11.95,
|
||||||
|
"image": "images/boxes/box-7.svg",
|
||||||
|
"description": "Low-profile box for folded materials, manuals, labels, and flat-pack assemblies."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "box-8",
|
||||||
|
"name": "Large Bulk Box",
|
||||||
|
"price": 22,
|
||||||
|
"image": "images/boxes/box-8.svg",
|
||||||
|
"description": "Large-capacity box for bundled products, bulk orders, and industrial shipments."
|
||||||
|
}
|
||||||
|
]
|
||||||
62
backend/lib/boxesStore.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const BOXES_FILE = path.join(__dirname, '..', 'data', 'boxes.json');
|
||||||
|
|
||||||
|
const DEFAULT_BOXES = Array.from({ length: 8 }, (_, index) => ({
|
||||||
|
id: `box-${index + 1}`,
|
||||||
|
name: `Box ${index + 1}`,
|
||||||
|
price: 0,
|
||||||
|
image: '',
|
||||||
|
description: '',
|
||||||
|
}));
|
||||||
|
|
||||||
|
function ensureBoxesFile() {
|
||||||
|
const dir = path.dirname(BOXES_FILE);
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(BOXES_FILE)) {
|
||||||
|
fs.writeFileSync(BOXES_FILE, JSON.stringify(DEFAULT_BOXES, null, 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeBox(box = {}, index = 0) {
|
||||||
|
return {
|
||||||
|
id: box.id || `box-${index + 1}`,
|
||||||
|
name: String(box.name || `Box ${index + 1}`).trim(),
|
||||||
|
price: Number(box.price || 0),
|
||||||
|
image: String(box.image || '').trim(),
|
||||||
|
description: String(box.description || '').trim(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function readBoxes() {
|
||||||
|
ensureBoxesFile();
|
||||||
|
try {
|
||||||
|
const raw = fs.readFileSync(BOXES_FILE, 'utf8');
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
if (!Array.isArray(parsed)) {
|
||||||
|
return DEFAULT_BOXES.map(normalizeBox);
|
||||||
|
}
|
||||||
|
const padded = Array.from({ length: 8 }, (_, index) => normalizeBox(parsed[index] || {}, index));
|
||||||
|
return padded;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error reading boxes file:', error);
|
||||||
|
return DEFAULT_BOXES.map(normalizeBox);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeBoxes(boxes = []) {
|
||||||
|
ensureBoxesFile();
|
||||||
|
const normalized = Array.from({ length: 8 }, (_, index) => normalizeBox(boxes[index] || {}, index));
|
||||||
|
fs.writeFileSync(BOXES_FILE, JSON.stringify(normalized, null, 2));
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
BOXES_FILE,
|
||||||
|
readBoxes,
|
||||||
|
writeBoxes,
|
||||||
|
};
|
||||||
38
backend/routes/boxRoutes.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const { readBoxes, writeBoxes } = require('../lib/boxesStore');
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
const EDITOR_CODE = process.env.BOX_EDITOR_CODE || process.env.ADMIN_CODE || '1234';
|
||||||
|
|
||||||
|
function requireEditorCode(req, res, next) {
|
||||||
|
const code = req.headers['x-editor-code'] || req.body.editorCode || req.query.editorCode;
|
||||||
|
if (code !== EDITOR_CODE) {
|
||||||
|
return res.status(401).json({ success: false, message: 'Invalid editor code' });
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
router.get('/', (req, res) => {
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
boxes: readBoxes(),
|
||||||
|
file: 'backend/data/boxes.json',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.put('/', requireEditorCode, (req, res) => {
|
||||||
|
const boxes = Array.isArray(req.body.boxes) ? req.body.boxes : [];
|
||||||
|
if (boxes.length !== 8) {
|
||||||
|
return res.status(400).json({ success: false, message: 'Exactly 8 boxes are required.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const savedBoxes = writeBoxes(boxes);
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Box data saved successfully.',
|
||||||
|
boxes: savedBoxes,
|
||||||
|
file: 'backend/data/boxes.json',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@ -7,7 +7,6 @@ require('dotenv').config();
|
|||||||
const app = express();
|
const app = express();
|
||||||
app.set('trust proxy', true);
|
app.set('trust proxy', true);
|
||||||
|
|
||||||
// Middleware
|
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(express.urlencoded({ extended: true }));
|
app.use(express.urlencoded({ extended: true }));
|
||||||
@ -58,39 +57,28 @@ app.post(['/.wf_graphql/apollo', '/.wf_graphql/usys/apollo'], (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Serve static files from the root directory
|
app.use('/api/boxes', require('./routes/boxRoutes'));
|
||||||
app.use(express.static(path.join(__dirname, '..')));
|
app.use('/api/auth', require('./routes/authRoutes'));
|
||||||
|
app.use('/api/users', require('./routes/userRoutes'));
|
||||||
|
app.use('/api/products', require('./routes/productRoutes'));
|
||||||
|
app.use('/api/cart', require('./routes/cartRoutes'));
|
||||||
|
app.use('/api/orders', require('./routes/orderRoutes'));
|
||||||
|
|
||||||
// Basic route
|
|
||||||
app.get('/', (req, res) => {
|
|
||||||
res.json({ message: 'Welcome to Mom\'s Web API' });
|
|
||||||
});
|
|
||||||
|
|
||||||
// Health check
|
|
||||||
app.get('/api/health', (req, res) => {
|
app.get('/api/health', (req, res) => {
|
||||||
res.json({ status: 'Server is running' });
|
res.json({ status: 'Server is running' });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Routes
|
app.use(express.static(path.join(__dirname, '..')));
|
||||||
app.use('/api/auth', require('./routes/authRoutes'));
|
|
||||||
app.use('/api/users', require('./routes/userRoutes'));
|
|
||||||
app.use('/api/products', require('./routes/productRoutes'));
|
|
||||||
app.use('/api/admin', require('./routes/adminRoutes'));
|
|
||||||
app.use('/api/cart', require('./routes/cartRoutes'));
|
|
||||||
app.use('/api/orders', require('./routes/orderRoutes'));
|
|
||||||
|
|
||||||
// Error handling middleware
|
|
||||||
app.use((err, req, res, next) => {
|
app.use((err, req, res, next) => {
|
||||||
console.error(err.stack);
|
console.error(err.stack);
|
||||||
res.status(500).json({ message: 'Internal Server Error' });
|
res.status(500).json({ message: 'Internal Server Error' });
|
||||||
});
|
});
|
||||||
|
|
||||||
// 404 handler
|
|
||||||
app.use((req, res) => {
|
app.use((req, res) => {
|
||||||
res.status(404).json({ message: 'Route not found' });
|
res.status(404).json({ message: 'Route not found' });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start server
|
|
||||||
const PORT = process.env.PORT || 5000;
|
const PORT = process.env.PORT || 5000;
|
||||||
const HOST = process.env.HOST || '0.0.0.0';
|
const HOST = process.env.HOST || '0.0.0.0';
|
||||||
app.listen(PORT, HOST, () => {
|
app.listen(PORT, HOST, () => {
|
||||||
|
|||||||
39
box-editor.html
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Box Data Editor</title>
|
||||||
|
<link href="css/style.css" rel="stylesheet" type="text/css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="admin-page">
|
||||||
|
<div class="admin-panel">
|
||||||
|
<div class="admin-header">
|
||||||
|
<div>
|
||||||
|
<h1>Box data editor</h1>
|
||||||
|
<p>Update the name, price, picture, and description for all 8 boxes.</p>
|
||||||
|
</div>
|
||||||
|
<a class="auth-tab admin-home-link" href="order.html">View boxes</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="admin-code-row">
|
||||||
|
<label for="editor-code">Editor code</label>
|
||||||
|
<input class="auth-input" id="editor-code" type="password" placeholder="Enter editor code" autocomplete="off" />
|
||||||
|
<button class="auth-submit admin-small-button" id="load-boxes" type="button">Load boxes</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p id="editor-message" class="auth-note">Tip: you can also edit <strong>backend/data/boxes.json</strong> directly in the codebase.</p>
|
||||||
|
|
||||||
|
<form id="editor-form" class="admin-grid" style="display:none;"></form>
|
||||||
|
|
||||||
|
<div class="admin-actions" id="editor-actions" style="display:none;">
|
||||||
|
<button class="auth-submit" id="save-boxes" type="button">Save all box changes</button>
|
||||||
|
<a class="auth-tab admin-home-link" href="index.html">Back to home</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="js/box-editor.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -65,13 +65,7 @@
|
|||||||
<a class="nav-link w-nav-link" href="index.html#call-store">
|
<a class="nav-link w-nav-link" href="index.html#call-store">
|
||||||
Contact
|
Contact
|
||||||
</a>
|
</a>
|
||||||
<a class="nav-link w-nav-link" href="admin.html">
|
|
||||||
Admin
|
|
||||||
</a>
|
|
||||||
</nav>
|
</nav>
|
||||||
<a class="auth-link" href="login.html">
|
|
||||||
Login / Register
|
|
||||||
</a>
|
|
||||||
<div class="w-commerce-commercecartwrapper" data-node-type="commerce-cart-wrapper" data-open-product="" data-wf-cart-query="query Dynamo3 {
|
<div class="w-commerce-commercecartwrapper" data-node-type="commerce-cart-wrapper" data-open-product="" data-wf-cart-query="query Dynamo3 {
|
||||||
database {
|
database {
|
||||||
id
|
id
|
||||||
|
|||||||
9
images/boxes/box-1.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="640" height="480" viewBox="0 0 640 480" fill="none">
|
||||||
|
<rect width="640" height="480" rx="36" fill="#fff7ed"/>
|
||||||
|
<rect x="110" y="150" width="420" height="210" rx="24" fill="#f59e0b" opacity="0.18"/>
|
||||||
|
<path d="M170 170L320 110L470 170L320 230L170 170Z" fill="#f59e0b"/>
|
||||||
|
<path d="M170 170V310L320 370V230L170 170Z" fill="#d97706" opacity="0.78"/>
|
||||||
|
<path d="M470 170V310L320 370V230L470 170Z" fill="#92400e" opacity="0.88"/>
|
||||||
|
<path d="M245 140L395 200" stroke="#fff" stroke-width="10" stroke-linecap="round" opacity="0.55"/>
|
||||||
|
<text x="320" y="430" text-anchor="middle" fill="#7c2d12" font-size="34" font-family="Arial, Helvetica, sans-serif" font-weight="700">BOX 1</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 730 B |
9
images/boxes/box-2.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="640" height="480" viewBox="0 0 640 480" fill="none">
|
||||||
|
<rect width="640" height="480" rx="36" fill="#fff7ed"/>
|
||||||
|
<rect x="110" y="150" width="420" height="210" rx="24" fill="#fb7185" opacity="0.18"/>
|
||||||
|
<path d="M170 170L320 110L470 170L320 230L170 170Z" fill="#fb7185"/>
|
||||||
|
<path d="M170 170V310L320 370V230L170 170Z" fill="#d97706" opacity="0.78"/>
|
||||||
|
<path d="M470 170V310L320 370V230L470 170Z" fill="#92400e" opacity="0.88"/>
|
||||||
|
<path d="M245 140L395 200" stroke="#fff" stroke-width="10" stroke-linecap="round" opacity="0.55"/>
|
||||||
|
<text x="320" y="430" text-anchor="middle" fill="#7c2d12" font-size="34" font-family="Arial, Helvetica, sans-serif" font-weight="700">BOX 2</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 730 B |
9
images/boxes/box-3.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="640" height="480" viewBox="0 0 640 480" fill="none">
|
||||||
|
<rect width="640" height="480" rx="36" fill="#fff7ed"/>
|
||||||
|
<rect x="110" y="150" width="420" height="210" rx="24" fill="#60a5fa" opacity="0.18"/>
|
||||||
|
<path d="M170 170L320 110L470 170L320 230L170 170Z" fill="#60a5fa"/>
|
||||||
|
<path d="M170 170V310L320 370V230L170 170Z" fill="#d97706" opacity="0.78"/>
|
||||||
|
<path d="M470 170V310L320 370V230L470 170Z" fill="#92400e" opacity="0.88"/>
|
||||||
|
<path d="M245 140L395 200" stroke="#fff" stroke-width="10" stroke-linecap="round" opacity="0.55"/>
|
||||||
|
<text x="320" y="430" text-anchor="middle" fill="#7c2d12" font-size="34" font-family="Arial, Helvetica, sans-serif" font-weight="700">BOX 3</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 730 B |
9
images/boxes/box-4.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="640" height="480" viewBox="0 0 640 480" fill="none">
|
||||||
|
<rect width="640" height="480" rx="36" fill="#fff7ed"/>
|
||||||
|
<rect x="110" y="150" width="420" height="210" rx="24" fill="#34d399" opacity="0.18"/>
|
||||||
|
<path d="M170 170L320 110L470 170L320 230L170 170Z" fill="#34d399"/>
|
||||||
|
<path d="M170 170V310L320 370V230L170 170Z" fill="#d97706" opacity="0.78"/>
|
||||||
|
<path d="M470 170V310L320 370V230L470 170Z" fill="#92400e" opacity="0.88"/>
|
||||||
|
<path d="M245 140L395 200" stroke="#fff" stroke-width="10" stroke-linecap="round" opacity="0.55"/>
|
||||||
|
<text x="320" y="430" text-anchor="middle" fill="#7c2d12" font-size="34" font-family="Arial, Helvetica, sans-serif" font-weight="700">BOX 4</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 730 B |
9
images/boxes/box-5.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="640" height="480" viewBox="0 0 640 480" fill="none">
|
||||||
|
<rect width="640" height="480" rx="36" fill="#fff7ed"/>
|
||||||
|
<rect x="110" y="150" width="420" height="210" rx="24" fill="#a78bfa" opacity="0.18"/>
|
||||||
|
<path d="M170 170L320 110L470 170L320 230L170 170Z" fill="#a78bfa"/>
|
||||||
|
<path d="M170 170V310L320 370V230L170 170Z" fill="#d97706" opacity="0.78"/>
|
||||||
|
<path d="M470 170V310L320 370V230L470 170Z" fill="#92400e" opacity="0.88"/>
|
||||||
|
<path d="M245 140L395 200" stroke="#fff" stroke-width="10" stroke-linecap="round" opacity="0.55"/>
|
||||||
|
<text x="320" y="430" text-anchor="middle" fill="#7c2d12" font-size="34" font-family="Arial, Helvetica, sans-serif" font-weight="700">BOX 5</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 730 B |
9
images/boxes/box-6.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="640" height="480" viewBox="0 0 640 480" fill="none">
|
||||||
|
<rect width="640" height="480" rx="36" fill="#fff7ed"/>
|
||||||
|
<rect x="110" y="150" width="420" height="210" rx="24" fill="#f97316" opacity="0.18"/>
|
||||||
|
<path d="M170 170L320 110L470 170L320 230L170 170Z" fill="#f97316"/>
|
||||||
|
<path d="M170 170V310L320 370V230L170 170Z" fill="#d97706" opacity="0.78"/>
|
||||||
|
<path d="M470 170V310L320 370V230L470 170Z" fill="#92400e" opacity="0.88"/>
|
||||||
|
<path d="M245 140L395 200" stroke="#fff" stroke-width="10" stroke-linecap="round" opacity="0.55"/>
|
||||||
|
<text x="320" y="430" text-anchor="middle" fill="#7c2d12" font-size="34" font-family="Arial, Helvetica, sans-serif" font-weight="700">BOX 6</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 730 B |
9
images/boxes/box-7.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="640" height="480" viewBox="0 0 640 480" fill="none">
|
||||||
|
<rect width="640" height="480" rx="36" fill="#fff7ed"/>
|
||||||
|
<rect x="110" y="150" width="420" height="210" rx="24" fill="#22c55e" opacity="0.18"/>
|
||||||
|
<path d="M170 170L320 110L470 170L320 230L170 170Z" fill="#22c55e"/>
|
||||||
|
<path d="M170 170V310L320 370V230L170 170Z" fill="#d97706" opacity="0.78"/>
|
||||||
|
<path d="M470 170V310L320 370V230L470 170Z" fill="#92400e" opacity="0.88"/>
|
||||||
|
<path d="M245 140L395 200" stroke="#fff" stroke-width="10" stroke-linecap="round" opacity="0.55"/>
|
||||||
|
<text x="320" y="430" text-anchor="middle" fill="#7c2d12" font-size="34" font-family="Arial, Helvetica, sans-serif" font-weight="700">BOX 7</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 730 B |
9
images/boxes/box-8.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="640" height="480" viewBox="0 0 640 480" fill="none">
|
||||||
|
<rect width="640" height="480" rx="36" fill="#fff7ed"/>
|
||||||
|
<rect x="110" y="150" width="420" height="210" rx="24" fill="#06b6d4" opacity="0.18"/>
|
||||||
|
<path d="M170 170L320 110L470 170L320 230L170 170Z" fill="#06b6d4"/>
|
||||||
|
<path d="M170 170V310L320 370V230L170 170Z" fill="#d97706" opacity="0.78"/>
|
||||||
|
<path d="M470 170V310L320 370V230L470 170Z" fill="#92400e" opacity="0.88"/>
|
||||||
|
<path d="M245 140L395 200" stroke="#fff" stroke-width="10" stroke-linecap="round" opacity="0.55"/>
|
||||||
|
<text x="320" y="430" text-anchor="middle" fill="#7c2d12" font-size="34" font-family="Arial, Helvetica, sans-serif" font-weight="700">BOX 8</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 730 B |
@ -70,13 +70,7 @@
|
|||||||
<a class="nav-link w-nav-link" href="#call-store">
|
<a class="nav-link w-nav-link" href="#call-store">
|
||||||
Contact
|
Contact
|
||||||
</a>
|
</a>
|
||||||
<a class="nav-link w-nav-link" href="admin.html">
|
|
||||||
Admin
|
|
||||||
</a>
|
|
||||||
</nav>
|
</nav>
|
||||||
<a class="auth-link" href="login.html">
|
|
||||||
Login / Register
|
|
||||||
</a>
|
|
||||||
<div class="w-commerce-commercecartwrapper" data-node-type="commerce-cart-wrapper" data-open-product="" data-wf-cart-query="query Dynamo3 {
|
<div class="w-commerce-commercecartwrapper" data-node-type="commerce-cart-wrapper" data-open-product="" data-wf-cart-query="query Dynamo3 {
|
||||||
database {
|
database {
|
||||||
id
|
id
|
||||||
@ -1160,8 +1154,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<script crossorigin="anonymous" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" src="js/jquery-3.5.1.min.dc5e7f18c8.js" type="text/javascript">
|
<script crossorigin="anonymous" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" src="js/jquery-3.5.1.min.dc5e7f18c8.js" type="text/javascript">
|
||||||
</script>
|
</script>
|
||||||
<script src="js/api.js" type="text/javascript">
|
|
||||||
</script>
|
|
||||||
<script crossorigin="anonymous" integrity="sha384-O3WhxmHD5kHQSrCGxUx9O9aqDChbj1vVrhT3htBjpSnAOHYYJ9ESiPlOvc9TRncW" src="js/webflow.schunk.b5641674105b3a84.js" type="text/javascript">
|
<script crossorigin="anonymous" integrity="sha384-O3WhxmHD5kHQSrCGxUx9O9aqDChbj1vVrhT3htBjpSnAOHYYJ9ESiPlOvc9TRncW" src="js/webflow.schunk.b5641674105b3a84.js" type="text/javascript">
|
||||||
</script>
|
</script>
|
||||||
<script crossorigin="anonymous" integrity="sha384-4AhZ6V15MqQcYDdfPXvfst2Y4s5C3Rtj7m/IcGSRXAN4ocxqL/Fag0UYpfUlMcst" src="js/webflow.schunk.8635f867f0e7bd37.js" type="text/javascript">
|
<script crossorigin="anonymous" integrity="sha384-4AhZ6V15MqQcYDdfPXvfst2Y4s5C3Rtj7m/IcGSRXAN4ocxqL/Fag0UYpfUlMcst" src="js/webflow.schunk.8635f867f0e7bd37.js" type="text/javascript">
|
||||||
|
|||||||
138
js/box-editor.js
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
const EDITOR_CODE_KEY = 'boxEditorCode';
|
||||||
|
const editorCodeInput = document.getElementById('editor-code');
|
||||||
|
const loadButton = document.getElementById('load-boxes');
|
||||||
|
const saveButton = document.getElementById('save-boxes');
|
||||||
|
const editorForm = document.getElementById('editor-form');
|
||||||
|
const editorActions = document.getElementById('editor-actions');
|
||||||
|
const editorMessage = document.getElementById('editor-message');
|
||||||
|
|
||||||
|
editorCodeInput.value = localStorage.getItem(EDITOR_CODE_KEY) || '';
|
||||||
|
|
||||||
|
function setEditorMessage(message) {
|
||||||
|
editorMessage.innerHTML = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
function apiBase() {
|
||||||
|
return `${window.location.protocol}//${window.location.host}/api/boxes`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function boxCard(box, index) {
|
||||||
|
const imageValue = box.image || '';
|
||||||
|
const preview = imageValue
|
||||||
|
? `<img class="admin-image-preview" src="${imageValue}" alt="${box.name || `Box ${index + 1}`} preview" />`
|
||||||
|
: '<div class="admin-image-placeholder">No picture</div>';
|
||||||
|
|
||||||
|
return `
|
||||||
|
<section class="admin-box-card" data-index="${index}" data-id="${box.id || `box-${index + 1}`}">
|
||||||
|
<div class="admin-box-title">
|
||||||
|
<h2>${box.name || `Box ${index + 1}`}</h2>
|
||||||
|
<span>${box.id || `box-${index + 1}`}</span>
|
||||||
|
</div>
|
||||||
|
${preview}
|
||||||
|
<label>
|
||||||
|
Box name
|
||||||
|
<input class="auth-input editor-name" type="text" value="${box.name || ''}" />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Price
|
||||||
|
<input class="auth-input editor-price" type="number" min="0" step="0.01" value="${Number(box.price || 0).toFixed(2)}" />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Picture URL or relative path
|
||||||
|
<input class="auth-input editor-image" type="text" placeholder="images/boxes/box-1.svg or https://..." value="${imageValue}" />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Description
|
||||||
|
<textarea class="auth-input editor-description" rows="4" placeholder="Describe this box">${box.description || ''}</textarea>
|
||||||
|
</label>
|
||||||
|
</section>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindPreviewListeners() {
|
||||||
|
editorForm.querySelectorAll('.editor-image').forEach(input => {
|
||||||
|
input.addEventListener('input', () => {
|
||||||
|
const card = input.closest('.admin-box-card');
|
||||||
|
const oldPreview = card.querySelector('.admin-image-preview, .admin-image-placeholder');
|
||||||
|
const value = input.value.trim();
|
||||||
|
const replacement = document.createElement(value ? 'img' : 'div');
|
||||||
|
if (value) {
|
||||||
|
replacement.className = 'admin-image-preview';
|
||||||
|
replacement.src = value;
|
||||||
|
replacement.alt = `${card.querySelector('.editor-name').value || 'Box'} preview`;
|
||||||
|
} else {
|
||||||
|
replacement.className = 'admin-image-placeholder';
|
||||||
|
replacement.textContent = 'No picture';
|
||||||
|
}
|
||||||
|
oldPreview.replaceWith(replacement);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderBoxes(boxes) {
|
||||||
|
editorForm.innerHTML = boxes.map(boxCard).join('');
|
||||||
|
editorForm.style.display = 'grid';
|
||||||
|
editorActions.style.display = 'flex';
|
||||||
|
bindPreviewListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadBoxes() {
|
||||||
|
const code = editorCodeInput.value.trim();
|
||||||
|
localStorage.setItem(EDITOR_CODE_KEY, code);
|
||||||
|
setEditorMessage('Loading boxes...');
|
||||||
|
|
||||||
|
const response = await fetch(apiBase());
|
||||||
|
const data = await response.json();
|
||||||
|
if (!data.success) {
|
||||||
|
setEditorMessage(data.message || 'Could not load boxes.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderBoxes(data.boxes);
|
||||||
|
setEditorMessage(`Boxes loaded. You can also edit <strong>${data.file}</strong> directly in the backend code.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectBoxes() {
|
||||||
|
return Array.from(editorForm.querySelectorAll('.admin-box-card')).map((card, index) => ({
|
||||||
|
id: card.dataset.id || `box-${index + 1}`,
|
||||||
|
name: card.querySelector('.editor-name').value.trim() || `Box ${index + 1}`,
|
||||||
|
price: Number(card.querySelector('.editor-price').value || 0),
|
||||||
|
image: card.querySelector('.editor-image').value.trim(),
|
||||||
|
description: card.querySelector('.editor-description').value.trim(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveBoxes() {
|
||||||
|
const code = editorCodeInput.value.trim();
|
||||||
|
if (!code) {
|
||||||
|
setEditorMessage('Enter the editor code before saving.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem(EDITOR_CODE_KEY, code);
|
||||||
|
setEditorMessage('Saving boxes...');
|
||||||
|
|
||||||
|
const response = await fetch(apiBase(), {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-editor-code': code,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ boxes: collectBoxes() }),
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
renderBoxes(data.boxes);
|
||||||
|
setEditorMessage(`Saved. The storefront is now reading from <strong>${data.file}</strong>.`);
|
||||||
|
} else {
|
||||||
|
setEditorMessage(data.message || 'Save failed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadButton.addEventListener('click', loadBoxes);
|
||||||
|
saveButton.addEventListener('click', saveBoxes);
|
||||||
|
|
||||||
|
if (editorCodeInput.value) {
|
||||||
|
loadBoxes();
|
||||||
|
}
|
||||||
@ -13,30 +13,88 @@
|
|||||||
|
|
||||||
async function getBoxes() {
|
async function getBoxes() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/admin/boxes');
|
const response = await fetch('/api/boxes');
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return data && data.success && Array.isArray(data.boxes) ? data.boxes : [];
|
return data && data.success && Array.isArray(data.boxes) ? data.boxes : [];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('Could not load box data:', error);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cleanPublicNavigation() {
|
||||||
|
document.querySelectorAll('a[href="admin.html"], a[href="login.html"]').forEach(link => link.remove());
|
||||||
|
document.querySelectorAll('.auth-link, .w-commerce-commercecartwrapper').forEach(element => element.remove());
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePublicCopy(isOrderPage) {
|
||||||
|
const header = document.querySelector('.header-h1');
|
||||||
|
if (header && isOrderPage) {
|
||||||
|
header.innerHTML = 'Browse our <span class="brand-span">box catalog</span> and request the right packaging for your products.';
|
||||||
|
}
|
||||||
|
|
||||||
|
const titleWrap = document.querySelector('.content-section .title-wrap-centre');
|
||||||
|
if (titleWrap) {
|
||||||
|
const title = titleWrap.querySelector('h2');
|
||||||
|
const text = titleWrap.querySelector('p');
|
||||||
|
if (title) {
|
||||||
|
title.textContent = isOrderPage ? 'Available Boxes' : 'Featured Boxes';
|
||||||
|
}
|
||||||
|
if (text) {
|
||||||
|
text.textContent = isOrderPage
|
||||||
|
? 'All box cards below are loaded from backend/data/boxes.json, so prices, pictures, and descriptions can be updated in one place.'
|
||||||
|
: 'Your featured box cards on this page are also loaded from the backend box data file.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyBoxDataToItems(items, boxes) {
|
||||||
|
items.forEach((item, index) => {
|
||||||
|
const box = boxes[index] || {};
|
||||||
|
const title = item.querySelector('h6');
|
||||||
|
const price = item.querySelector('.price');
|
||||||
|
const description = item.querySelector('.paragraph');
|
||||||
|
const imageWrap = item.querySelector('.food-image-square');
|
||||||
|
const image = item.querySelector('.food-image');
|
||||||
|
const addToCart = item.querySelector('.add-to-cart');
|
||||||
|
|
||||||
|
if (title) title.textContent = box.name || `Box ${index + 1}`;
|
||||||
|
if (price) price.textContent = `$${Number(box.price || 0).toFixed(2)}`;
|
||||||
|
if (description) description.textContent = box.description || 'Update this description in backend/data/boxes.json.';
|
||||||
|
|
||||||
|
if (imageWrap && image && box.image) {
|
||||||
|
image.src = box.image;
|
||||||
|
image.alt = box.name || `Box ${index + 1}`;
|
||||||
|
imageWrap.removeAttribute('href');
|
||||||
|
}
|
||||||
|
|
||||||
|
item.querySelectorAll('a[href]').forEach(link => link.removeAttribute('href'));
|
||||||
|
item.querySelectorAll('.quantity, .w-commerce-commerceaddtocarterror, .w-commerce-commerceaddtocartoutofstock').forEach(el => el.remove());
|
||||||
|
|
||||||
|
if (addToCart) {
|
||||||
|
addToCart.innerHTML = '<div class="button button-space" style="display:inline-block;">Update in Box Editor</div>';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function simplifyMenuBoxes() {
|
async function simplifyMenuBoxes() {
|
||||||
const path = window.location.pathname;
|
const path = window.location.pathname;
|
||||||
const isMainPage = path === '/' || path.endsWith('/index.html');
|
const isMainPage = path === '/' || path.endsWith('/index.html');
|
||||||
const isOrderPage = path.endsWith('/order.html');
|
const isOrderPage = path.endsWith('/order.html');
|
||||||
if (!isMainPage && !isOrderPage) return;
|
if (!isMainPage && !isOrderPage) return;
|
||||||
|
|
||||||
const limit = isOrderPage ? 8 : 4;
|
|
||||||
const boxes = await getBoxes();
|
const boxes = await getBoxes();
|
||||||
|
if (!boxes.length) return;
|
||||||
|
|
||||||
|
cleanPublicNavigation();
|
||||||
|
updatePublicCopy(isOrderPage);
|
||||||
|
|
||||||
const tabs = document.querySelector('.w-tabs');
|
const tabs = document.querySelector('.w-tabs');
|
||||||
if (!tabs) return;
|
if (!tabs) return;
|
||||||
|
|
||||||
const tabMenu = tabs.querySelector('.tab-menu-round');
|
const tabMenu = tabs.querySelector('.tab-menu-round');
|
||||||
if (tabMenu) tabMenu.remove();
|
if (tabMenu) tabMenu.remove();
|
||||||
|
|
||||||
const items = Array.from(tabs.querySelectorAll('.menu-item'));
|
|
||||||
const selectedItems = items.slice(0, limit);
|
|
||||||
const firstPane = tabs.querySelector('.w-tab-pane');
|
const firstPane = tabs.querySelector('.w-tab-pane');
|
||||||
const firstList = firstPane && firstPane.querySelector('.w-dyn-items');
|
const firstList = firstPane && firstPane.querySelector('.w-dyn-items');
|
||||||
if (!firstPane || !firstList) return;
|
if (!firstPane || !firstList) return;
|
||||||
@ -50,41 +108,29 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const itemTemplate = Array.from(firstList.querySelectorAll('.menu-item'));
|
||||||
|
const limit = isOrderPage ? 8 : 4;
|
||||||
|
const selectedBoxes = boxes.slice(0, limit);
|
||||||
|
const template = itemTemplate[0];
|
||||||
|
if (!template) return;
|
||||||
|
|
||||||
firstList.innerHTML = '';
|
firstList.innerHTML = '';
|
||||||
selectedItems.forEach((item, index) => {
|
selectedBoxes.forEach((box, index) => {
|
||||||
const box = boxes[index] || {};
|
const clone = template.cloneNode(true);
|
||||||
const imageWrap = item.querySelector('.food-image-square');
|
applyBoxDataToItems([clone], [box]);
|
||||||
const image = item.querySelector('.food-image');
|
firstList.appendChild(clone);
|
||||||
if (imageWrap && image && box.image) {
|
|
||||||
image.src = box.image;
|
|
||||||
image.alt = `box${index + 1}`;
|
|
||||||
imageWrap.removeAttribute('href');
|
|
||||||
} else if (imageWrap) {
|
|
||||||
imageWrap.remove();
|
|
||||||
}
|
|
||||||
const paragraph = item.querySelector('.paragraph');
|
|
||||||
if (paragraph && box.description) {
|
|
||||||
paragraph.textContent = box.description;
|
|
||||||
} else if (paragraph) {
|
|
||||||
paragraph.remove();
|
|
||||||
}
|
|
||||||
item.querySelectorAll('.quantity').forEach(element => {
|
|
||||||
element.type = 'hidden';
|
|
||||||
});
|
|
||||||
const title = item.querySelector('h6');
|
|
||||||
if (title) title.textContent = `box${index + 1}`;
|
|
||||||
const price = item.querySelector('.price');
|
|
||||||
if (price) price.textContent = `$${Number(box.price || 0).toFixed(2)} USD`;
|
|
||||||
item.querySelectorAll('a[href]').forEach(link => link.removeAttribute('href'));
|
|
||||||
firstList.appendChild(item);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
firstPane.querySelectorAll('.pagination').forEach(element => element.remove());
|
firstPane.querySelectorAll('.pagination').forEach(element => element.remove());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
document.addEventListener('DOMContentLoaded', simplifyMenuBoxes);
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
cleanPublicNavigation();
|
||||||
|
simplifyMenuBoxes();
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
|
cleanPublicNavigation();
|
||||||
simplifyMenuBoxes();
|
simplifyMenuBoxes();
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
130
login.html
@ -2,135 +2,11 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
<meta http-equiv="refresh" content="0; url=index.html" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Login / Register</title>
|
<title>Redirecting...</title>
|
||||||
<link href="css/style.css" rel="stylesheet" type="text/css" />
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="auth-page">
|
<p>This page is no longer used. Return to <a href="index.html">Home</a>.</p>
|
||||||
<div class="auth-card">
|
|
||||||
<h1>Login or register</h1>
|
|
||||||
<p>Access your account and start ordering with a clean, secure experience.</p>
|
|
||||||
|
|
||||||
<div class="auth-tabs">
|
|
||||||
<button class="auth-tab active" type="button" data-target="login">Login</button>
|
|
||||||
<button class="auth-tab" type="button" data-target="register">Register</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form id="login-form" class="auth-form">
|
|
||||||
<div>
|
|
||||||
<div class="auth-field">
|
|
||||||
<label for="login-email">Email</label>
|
|
||||||
<input class="auth-input" id="login-email" type="email" placeholder="you@example.com" autocomplete="email" required />
|
|
||||||
</div>
|
|
||||||
<div class="auth-field">
|
|
||||||
<label for="login-password">Password</label>
|
|
||||||
<input class="auth-input" id="login-password" type="password" placeholder="••••••••" autocomplete="current-password" required />
|
|
||||||
</div>
|
|
||||||
<button class="auth-submit" type="submit">Login</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<form id="register-form" class="auth-form" style="display:none;">
|
|
||||||
<div>
|
|
||||||
<div class="auth-field">
|
|
||||||
<label for="register-firstname">First name</label>
|
|
||||||
<input class="auth-input" id="register-firstname" type="text" placeholder="First name" autocomplete="given-name" required />
|
|
||||||
</div>
|
|
||||||
<div class="auth-field">
|
|
||||||
<label for="register-lastname">Last name</label>
|
|
||||||
<input class="auth-input" id="register-lastname" type="text" placeholder="Last name" autocomplete="family-name" required />
|
|
||||||
</div>
|
|
||||||
<div class="auth-field">
|
|
||||||
<label for="register-email">Email</label>
|
|
||||||
<input class="auth-input" id="register-email" type="email" placeholder="you@example.com" autocomplete="email" required />
|
|
||||||
</div>
|
|
||||||
<div class="auth-field">
|
|
||||||
<label for="register-password">Password</label>
|
|
||||||
<input class="auth-input" id="register-password" type="password" placeholder="••••••••" autocomplete="new-password" required />
|
|
||||||
</div>
|
|
||||||
<div class="auth-field">
|
|
||||||
<label for="register-confirm">Confirm password</label>
|
|
||||||
<input class="auth-input" id="register-confirm" type="password" placeholder="••••••••" autocomplete="new-password" required />
|
|
||||||
</div>
|
|
||||||
<button class="auth-submit" type="submit">Register</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<p id="auth-message" class="auth-note">Use your email and password to access your account.</p>
|
|
||||||
<p class="auth-note"><a href="index.html">Back to home</a></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="js/api.js"></script>
|
|
||||||
<script>
|
|
||||||
const tabs = document.querySelectorAll('.auth-tab');
|
|
||||||
const loginForm = document.getElementById('login-form');
|
|
||||||
const registerForm = document.getElementById('register-form');
|
|
||||||
const authMessage = document.getElementById('auth-message');
|
|
||||||
|
|
||||||
function setMessage(message) {
|
|
||||||
authMessage.textContent = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
tabs.forEach(tab => {
|
|
||||||
tab.addEventListener('click', () => {
|
|
||||||
tabs.forEach(btn => btn.classList.remove('active'));
|
|
||||||
tab.classList.add('active');
|
|
||||||
const target = tab.dataset.target;
|
|
||||||
if (target === 'login') {
|
|
||||||
loginForm.style.display = 'block';
|
|
||||||
registerForm.style.display = 'none';
|
|
||||||
setMessage('Use your email and password to access your account.');
|
|
||||||
} else {
|
|
||||||
loginForm.style.display = 'none';
|
|
||||||
registerForm.style.display = 'block';
|
|
||||||
setMessage('Create an account with a password of at least 6 characters.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
loginForm.addEventListener('submit', async (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
setMessage('Logging in...');
|
|
||||||
const email = document.getElementById('login-email').value.trim();
|
|
||||||
const password = document.getElementById('login-password').value;
|
|
||||||
const result = await login(email, password);
|
|
||||||
if (result && result.success) {
|
|
||||||
setMessage('Login successful. Opening the order page...');
|
|
||||||
window.location.href = 'order.html';
|
|
||||||
} else {
|
|
||||||
setMessage('Login failed. Check your email and password.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
registerForm.addEventListener('submit', async (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
const firstName = document.getElementById('register-firstname').value.trim();
|
|
||||||
const lastName = document.getElementById('register-lastname').value.trim();
|
|
||||||
const email = document.getElementById('register-email').value.trim();
|
|
||||||
const password = document.getElementById('register-password').value;
|
|
||||||
const passwordConfirm = document.getElementById('register-confirm').value;
|
|
||||||
|
|
||||||
if (password !== passwordConfirm) {
|
|
||||||
setMessage('Passwords do not match.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (password.length < 6) {
|
|
||||||
setMessage('Password must be at least 6 characters.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setMessage('Creating your account...');
|
|
||||||
const result = await register(firstName, lastName, email, password, passwordConfirm);
|
|
||||||
if (result && result.success) {
|
|
||||||
setMessage('Registration successful. Opening the order page...');
|
|
||||||
window.location.href = 'order.html';
|
|
||||||
} else {
|
|
||||||
setMessage('Registration failed. Try a different email or password.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
133
order.html
@ -68,13 +68,7 @@
|
|||||||
<a class="nav-link w-nav-link" href="index.html#call-store">
|
<a class="nav-link w-nav-link" href="index.html#call-store">
|
||||||
Contact
|
Contact
|
||||||
</a>
|
</a>
|
||||||
<a class="nav-link w-nav-link" href="admin.html">
|
|
||||||
Admin
|
|
||||||
</a>
|
|
||||||
</nav>
|
</nav>
|
||||||
<a class="auth-link" href="login.html">
|
|
||||||
Login / Register
|
|
||||||
</a>
|
|
||||||
<div class="w-commerce-commercecartwrapper" data-node-type="commerce-cart-wrapper" data-open-product="" data-wf-cart-query="query Dynamo3 {
|
<div class="w-commerce-commercecartwrapper" data-node-type="commerce-cart-wrapper" data-open-product="" data-wf-cart-query="query Dynamo3 {
|
||||||
database {
|
database {
|
||||||
id
|
id
|
||||||
@ -1332,133 +1326,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<script crossorigin="anonymous" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" src="js/jquery-3.5.1.min.dc5e7f18c8.js" type="text/javascript">
|
<script crossorigin="anonymous" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" src="js/jquery-3.5.1.min.dc5e7f18c8.js" type="text/javascript">
|
||||||
</script>
|
</script>
|
||||||
<script src="js/api.js" type="text/javascript">
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
// Load products when page loads
|
|
||||||
document.addEventListener('DOMContentLoaded', async function() {
|
|
||||||
console.log('Loading products...');
|
|
||||||
try {
|
|
||||||
const productsData = await getAllProducts();
|
|
||||||
|
|
||||||
if (productsData && productsData.success && productsData.products) {
|
|
||||||
displayProducts(productsData.products);
|
|
||||||
} else {
|
|
||||||
console.error('Failed to load products:', productsData);
|
|
||||||
// Show fallback message
|
|
||||||
showFallbackProducts();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading products:', error);
|
|
||||||
// Show fallback for development
|
|
||||||
showFallbackProducts();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function showFallbackProducts() {
|
|
||||||
displayProducts([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function displayProducts(products) {
|
|
||||||
const productGrid = document.querySelector('.w-tab-pane.w--tab-active .order-collection');
|
|
||||||
if (!productGrid) {
|
|
||||||
console.error('Active tab product grid not found');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
productGrid.innerHTML = '';
|
|
||||||
|
|
||||||
const boxes = Array.from({ length: 8 }, (_, index) => {
|
|
||||||
const product = products[index] || {};
|
|
||||||
return {
|
|
||||||
_id: product._id || `box-${index + 1}`,
|
|
||||||
name: `box${index + 1}`,
|
|
||||||
price: Number(product.price || 0),
|
|
||||||
image: product.image || '',
|
|
||||||
description: product.description || ''
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
boxes.forEach(product => {
|
|
||||||
const imageHTML = product.image
|
|
||||||
? `<img class="box-card-image" src="${product.image}" alt="${product.name}" />`
|
|
||||||
: '';
|
|
||||||
const descriptionHTML = product.description
|
|
||||||
? `<p class="paragraph box-card-description">${product.description}</p>`
|
|
||||||
: '';
|
|
||||||
const productHTML = `
|
|
||||||
<div class="menu-item w-dyn-item w-col w-col-6" role="listitem">
|
|
||||||
<div class="food-card">
|
|
||||||
${imageHTML}
|
|
||||||
<div class="food-card-content">
|
|
||||||
<div class="food-title-wrap w-inline-block">
|
|
||||||
<h6>${product.name}</h6>
|
|
||||||
<div class="price">$${product.price.toFixed(2)} USD</div>
|
|
||||||
</div>
|
|
||||||
${descriptionHTML}
|
|
||||||
<div class="add-to-cart">
|
|
||||||
<input class="quantity" type="hidden" min="1" value="1" id="quantity-${product._id}" />
|
|
||||||
<button class="order-button w-button add-to-cart-btn"
|
|
||||||
data-product-id="${product._id}"
|
|
||||||
data-product-name="${product.name}">
|
|
||||||
Add to Cart
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
productGrid.insertAdjacentHTML('beforeend', productHTML);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add event listeners to Add to Cart buttons
|
|
||||||
document.querySelectorAll('.add-to-cart-btn').forEach(button => {
|
|
||||||
button.addEventListener('click', async function() {
|
|
||||||
const productId = this.getAttribute('data-product-id');
|
|
||||||
const productName = this.getAttribute('data-product-name');
|
|
||||||
const quantityInput = document.getElementById(`quantity-${productId}`);
|
|
||||||
const quantity = parseInt(quantityInput.value) || 1;
|
|
||||||
|
|
||||||
console.log(`Adding ${quantity} x ${productName} to cart`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await addToCart(productId, quantity);
|
|
||||||
if (result) {
|
|
||||||
alert(`${quantity} x ${productName} added to cart!`);
|
|
||||||
// Update cart count if needed
|
|
||||||
updateCartCount();
|
|
||||||
} else {
|
|
||||||
alert('Failed to add item to cart - API may be blocked by browser security');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Cart API error:', error);
|
|
||||||
alert('Cart functionality blocked by browser security. This is expected on HTTP sites.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to update cart count
|
|
||||||
async function updateCartCount() {
|
|
||||||
try {
|
|
||||||
const cart = await getCart();
|
|
||||||
if (cart && cart.items) {
|
|
||||||
const totalItems = cart.items.reduce((sum, item) => sum + item.quantity, 0);
|
|
||||||
const cartCountElement = document.querySelector('.cart-quantity');
|
|
||||||
if (cartCountElement) {
|
|
||||||
cartCountElement.textContent = totalItems;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error updating cart count:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load cart count on page load
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
updateCartCount();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<script crossorigin="anonymous" integrity="sha384-4AhZ6V15MqQcYDdfPXvfst2Y4s5C3Rtj7m/IcGSRXAN4ocxqL/Fag0UYpfUlMcst" src="js/webflow.schunk.8635f867f0e7bd37.js" type="text/javascript">
|
<script crossorigin="anonymous" integrity="sha384-4AhZ6V15MqQcYDdfPXvfst2Y4s5C3Rtj7m/IcGSRXAN4ocxqL/Fag0UYpfUlMcst" src="js/webflow.schunk.8635f867f0e7bd37.js" type="text/javascript">
|
||||||
</script>
|
</script>
|
||||||
<script crossorigin="anonymous" integrity="sha384-ID0YQsmVv1RvR08bKK9/r3E6ht8sEBszDbo80TPdSDneZNcUQIGiXgNQMe2H2WQl" src="js/webflow.61cfa502.51ab170f9bd4cb2e.js" type="text/javascript">
|
<script crossorigin="anonymous" integrity="sha384-ID0YQsmVv1RvR08bKK9/r3E6ht8sEBszDbo80TPdSDneZNcUQIGiXgNQMe2H2WQl" src="js/webflow.61cfa502.51ab170f9bd4cb2e.js" type="text/javascript">
|
||||||
|
|||||||