diff --git a/admin.html b/admin.html
new file mode 100644
index 0000000..ecebe80
--- /dev/null
+++ b/admin.html
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Enter the admin code to edit box details.
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/controllers/productController.js b/backend/controllers/productController.js
index c7b6aa7..290e946 100644
--- a/backend/controllers/productController.js
+++ b/backend/controllers/productController.js
@@ -31,6 +31,8 @@ exports.getAllProducts = async (req, res) => {
products.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
} else if (sortBy === 'rating') {
products.sort((a, b) => b.rating - a.rating);
+ } else if (products.length && products.every(product => String(product.sku || '').startsWith('BOX-'))) {
+ products.sort((a, b) => String(a.sku).localeCompare(String(b.sku)));
} else {
products.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
}
diff --git a/backend/data/products.json b/backend/data/products.json
index b101357..6b69bfb 100644
--- a/backend/data/products.json
+++ b/backend/data/products.json
@@ -1,117 +1,146 @@
[
{
- "_id": "1775465493816",
- "name": "Classic Burger",
- "description": "Juicy beef patty with lettuce, tomato, onion, and our special sauce",
- "price": 12.99,
- "salePrice": 9.99,
- "category": "Food",
- "image": "https://images.unsplash.com/photo-1568901346375-23c9450c58cd?w=300",
- "images": [
- "https://images.unsplash.com/photo-1568901346375-23c9450c58cd?w=300"
- ],
- "stock": 50,
- "sku": "BURGER-001",
+ "_id": "box-1",
+ "name": "box1",
+ "description": "Description for box1",
+ "price": 9.2,
+ "salePrice": null,
+ "category": "Box",
+ "image": "",
+ "images": [],
+ "stock": 100,
+ "sku": "BOX-001",
"rating": 0,
"reviewCount": 0,
- "tags": [
- "popular",
- "beef"
- ],
+ "tags": [],
"isActive": true,
"createdAt": "2026-04-06T08:51:33.816Z",
- "updatedAt": "2026-04-06T08:51:33.816Z"
+ "updatedAt": "2026-04-15T00:00:00.000Z"
},
{
- "_id": "1775465493818",
- "name": "Margherita Pizza",
- "description": "Fresh mozzarella, tomato sauce, and basil on our signature crust",
- "price": 15.99,
+ "_id": "box-2",
+ "name": "box2",
+ "description": "Description for box2",
+ "price": 10,
"salePrice": null,
- "category": "Food",
- "image": "https://images.unsplash.com/photo-1513104890138-7c749659a591?w=300",
- "images": [
- "https://images.unsplash.com/photo-1513104890138-7c749659a591?w=300"
- ],
- "stock": 30,
- "sku": "PIZZA-001",
- "rating": 0,
- "reviewCount": 0,
- "tags": [
- "vegetarian",
- "classic"
- ],
- "isActive": true,
- "createdAt": "2026-04-06T08:51:33.818Z",
- "updatedAt": "2026-04-06T08:51:33.818Z"
- },
- {
- "_id": "1775465493818",
- "name": "Caesar Salad",
- "description": "Crisp romaine lettuce with parmesan cheese, croutons, and Caesar dressing",
- "price": 8.99,
- "salePrice": null,
- "category": "Salads",
- "image": "https://images.unsplash.com/photo-1550304943-4f24f54ddde9?w=300",
- "images": [
- "https://images.unsplash.com/photo-1550304943-4f24f54ddde9?w=300"
- ],
- "stock": 40,
- "sku": "SALAD-001",
- "rating": 0,
- "reviewCount": 0,
- "tags": [
- "healthy",
- "vegetarian"
- ],
- "isActive": true,
- "createdAt": "2026-04-06T08:51:33.818Z",
- "updatedAt": "2026-04-06T08:51:33.818Z"
- },
- {
- "_id": "1775465493819",
- "name": "French Fries",
- "description": "Golden crispy fries served with ketchup",
- "price": 4.99,
- "salePrice": null,
- "category": "Sides",
- "image": "https://images.unsplash.com/photo-1573080496219-bb080dd4f877?w=300",
- "images": [
- "https://images.unsplash.com/photo-1573080496219-bb080dd4f877?w=300"
- ],
+ "category": "Box",
+ "image": "",
+ "images": [],
"stock": 100,
- "sku": "FRIES-001",
+ "sku": "BOX-002",
"rating": 0,
"reviewCount": 0,
- "tags": [
- "side",
- "crispy"
- ],
+ "tags": [],
+ "isActive": true,
+ "createdAt": "2026-04-06T08:51:33.817Z",
+ "updatedAt": "2026-04-15T00:00:00.000Z"
+ },
+ {
+ "_id": "box-3",
+ "name": "box3",
+ "description": "Description for box3",
+ "price": 8.5,
+ "salePrice": null,
+ "category": "Box",
+ "image": "",
+ "images": [],
+ "stock": 100,
+ "sku": "BOX-003",
+ "rating": 0,
+ "reviewCount": 0,
+ "tags": [],
+ "isActive": true,
+ "createdAt": "2026-04-06T08:51:33.818Z",
+ "updatedAt": "2026-04-15T00:00:00.000Z"
+ },
+ {
+ "_id": "box-4",
+ "name": "box4",
+ "description": "Description for box4",
+ "price": 11,
+ "salePrice": null,
+ "category": "Box",
+ "image": "",
+ "images": [],
+ "stock": 100,
+ "sku": "BOX-004",
+ "rating": 0,
+ "reviewCount": 0,
+ "tags": [],
"isActive": true,
"createdAt": "2026-04-06T08:51:33.819Z",
- "updatedAt": "2026-04-06T08:51:33.819Z"
+ "updatedAt": "2026-04-15T00:00:00.000Z"
},
{
- "_id": "1775465493820",
- "name": "Chocolate Milkshake",
- "description": "Rich and creamy chocolate milkshake topped with whipped cream",
+ "_id": "box-5",
+ "name": "box5",
+ "description": "Description for box5",
"price": 6.99,
"salePrice": null,
- "category": "Drinks",
- "image": "https://images.unsplash.com/photo-1578985545062-69928b1d9587?w=300",
- "images": [
- "https://images.unsplash.com/photo-1578985545062-69928b1d9587?w=300"
- ],
- "stock": 25,
- "sku": "SHAKE-001",
+ "category": "Box",
+ "image": "",
+ "images": [],
+ "stock": 100,
+ "sku": "BOX-005",
"rating": 0,
"reviewCount": 0,
- "tags": [
- "sweet",
- "cold"
- ],
+ "tags": [],
"isActive": true,
"createdAt": "2026-04-06T08:51:33.820Z",
- "updatedAt": "2026-04-06T08:51:33.820Z"
+ "updatedAt": "2026-04-15T00:00:00.000Z"
+ },
+ {
+ "_id": "box-6",
+ "name": "box6",
+ "description": "Description for box6",
+ "price": 4.99,
+ "salePrice": null,
+ "category": "Box",
+ "image": "",
+ "images": [],
+ "stock": 100,
+ "sku": "BOX-006",
+ "rating": 0,
+ "reviewCount": 0,
+ "tags": [],
+ "isActive": true,
+ "createdAt": "2026-04-06T08:51:33.821Z",
+ "updatedAt": "2026-04-15T00:00:00.000Z"
+ },
+ {
+ "_id": "box-7",
+ "name": "box7",
+ "description": "Description for box7",
+ "price": 5.99,
+ "salePrice": null,
+ "category": "Box",
+ "image": "",
+ "images": [],
+ "stock": 100,
+ "sku": "BOX-007",
+ "rating": 0,
+ "reviewCount": 0,
+ "tags": [],
+ "isActive": true,
+ "createdAt": "2026-04-06T08:51:33.822Z",
+ "updatedAt": "2026-04-15T00:00:00.000Z"
+ },
+ {
+ "_id": "box-8",
+ "name": "box8",
+ "description": "Description for box8",
+ "price": 7.99,
+ "salePrice": null,
+ "category": "Box",
+ "image": "",
+ "images": [],
+ "stock": 100,
+ "sku": "BOX-008",
+ "rating": 0,
+ "reviewCount": 0,
+ "tags": [],
+ "isActive": true,
+ "createdAt": "2026-04-06T08:51:33.823Z",
+ "updatedAt": "2026-04-15T00:00:00.000Z"
}
]
\ No newline at end of file
diff --git a/backend/data/users.json b/backend/data/users.json
index a9a535c..77651a2 100644
--- a/backend/data/users.json
+++ b/backend/data/users.json
@@ -1,6 +1,7 @@
[
{
"_id": "1775634815531",
+ "id": "1775634815531",
"firstName": "Tornike",
"lastName": "Gerantia",
"email": "tornikegerantia@gmail.com",
@@ -10,8 +11,8 @@
"role": "user",
"isActive": true,
"profileImage": null,
- "lastLogin": null,
+ "lastLogin": "2026-04-15T00:22:28.632Z",
"createdAt": "2026-04-08T07:53:35.531Z",
- "updatedAt": "2026-04-08T07:53:35.613Z"
+ "updatedAt": "2026-04-15T00:22:28.632Z"
}
]
\ No newline at end of file
diff --git a/backend/models/productModel.js b/backend/models/productModel.js
index 6e1b6ee..109e4db 100644
--- a/backend/models/productModel.js
+++ b/backend/models/productModel.js
@@ -21,12 +21,12 @@ class Product {
this._id = data._id || Date.now().toString();
this.name = data.name;
this.description = data.description;
- this.price = data.price;
+ this.price = Number(data.price || 0);
this.salePrice = data.salePrice || null;
this.category = data.category;
this.image = data.image;
this.images = data.images || [data.image];
- this.stock = data.stock;
+ this.stock = Number(data.stock || 0);
this.sku = data.sku;
this.rating = data.rating || 0;
this.reviewCount = data.reviewCount || 0;
@@ -158,6 +158,17 @@ Product.findByIdAndUpdate = async (id, updateData) => {
}
};
+Product.replaceAll = async (productsData) => {
+ try {
+ const products = productsData.map(data => new Product(data));
+ fs.writeFileSync(PRODUCTS_FILE, JSON.stringify(products, null, 2));
+ return products;
+ } catch (error) {
+ console.error('Error replacing products:', error);
+ throw error;
+ }
+};
+
Product.findByIdAndDelete = async (id) => {
try {
const products = await Product.find();
diff --git a/backend/routes/adminRoutes.js b/backend/routes/adminRoutes.js
new file mode 100644
index 0000000..b3a8453
--- /dev/null
+++ b/backend/routes/adminRoutes.js
@@ -0,0 +1,71 @@
+const express = require('express');
+const Product = require('../models/productModel');
+
+const router = express.Router();
+
+const ADMIN_CODE = process.env.ADMIN_CODE || '1234';
+
+function requireAdminCode(req, res, next) {
+ const code = req.headers['x-admin-code'] || req.body.adminCode || req.query.adminCode;
+ if (code !== ADMIN_CODE) {
+ return res.status(401).json({ success: false, message: 'Invalid admin code' });
+ }
+ next();
+}
+
+function normalizeBox(product, index) {
+ const boxNumber = index + 1;
+ return {
+ _id: product._id || `box-${boxNumber}`,
+ name: `box${boxNumber}`,
+ description: product.description || '',
+ price: Number(product.price || 0),
+ salePrice: null,
+ category: 'Box',
+ image: product.image || '',
+ images: product.image ? [product.image] : [],
+ stock: Number(product.stock || 100),
+ sku: `BOX-${String(boxNumber).padStart(3, '0')}`,
+ rating: Number(product.rating || 0),
+ reviewCount: Number(product.reviewCount || 0),
+ tags: Array.isArray(product.tags) ? product.tags : [],
+ isActive: true,
+ createdAt: product.createdAt || new Date().toISOString(),
+ updatedAt: new Date().toISOString(),
+ };
+}
+
+router.get('/boxes', async (req, res) => {
+ try {
+ const products = await Product.find({ isActive: true });
+ const boxes = Array.from({ length: 8 }, (_, index) => {
+ const sku = `BOX-${String(index + 1).padStart(3, '0')}`;
+ const product = products.find(item => item.sku === sku) || products[index] || {};
+ return normalizeBox(product, index);
+ });
+
+ res.json({ success: true, boxes });
+ } catch (error) {
+ res.status(500).json({ success: false, message: error.message });
+ }
+});
+
+router.put('/boxes', requireAdminCode, async (req, res) => {
+ try {
+ 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 existingProducts = await Product.find();
+ const nonBoxProducts = existingProducts.filter(product => !String(product.sku || '').startsWith('BOX-'));
+ const normalizedBoxes = boxes.map((box, index) => normalizeBox(box, index));
+ await Product.replaceAll([...normalizedBoxes, ...nonBoxProducts]);
+
+ res.json({ success: true, message: 'Boxes saved successfully', boxes: normalizedBoxes });
+ } catch (error) {
+ res.status(500).json({ success: false, message: error.message });
+ }
+});
+
+module.exports = router;
\ No newline at end of file
diff --git a/backend/server.js b/backend/server.js
index d607982..d1b4daf 100644
--- a/backend/server.js
+++ b/backend/server.js
@@ -75,6 +75,7 @@ app.get('/api/health', (req, res) => {
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'));
diff --git a/company.html b/company.html
index 40296a3..914a756 100644
--- a/company.html
+++ b/company.html
@@ -65,6 +65,9 @@