@o2vend/theme-cli 1.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +425 -0
- package/assets/Logo_o2vend.png +0 -0
- package/assets/favicon.png +0 -0
- package/assets/logo-white.png +0 -0
- package/bin/o2vend +42 -0
- package/config/widget-map.json +50 -0
- package/lib/commands/check.js +201 -0
- package/lib/commands/generate.js +33 -0
- package/lib/commands/init.js +214 -0
- package/lib/commands/optimize.js +216 -0
- package/lib/commands/package.js +208 -0
- package/lib/commands/serve.js +105 -0
- package/lib/commands/validate.js +191 -0
- package/lib/lib/api-client.js +357 -0
- package/lib/lib/dev-server.js +2618 -0
- package/lib/lib/file-watcher.js +80 -0
- package/lib/lib/hot-reload.js +106 -0
- package/lib/lib/liquid-engine.js +822 -0
- package/lib/lib/liquid-filters.js +671 -0
- package/lib/lib/mock-api-server.js +989 -0
- package/lib/lib/mock-data.js +1468 -0
- package/lib/lib/widget-service.js +321 -0
- package/package.json +70 -0
- package/test-theme/README.md +27 -0
- package/test-theme/assets/async-sections.js +446 -0
- package/test-theme/assets/cart-drawer.js +463 -0
- package/test-theme/assets/cart-manager.js +223 -0
- package/test-theme/assets/checkout-price-handler.js +368 -0
- package/test-theme/assets/components.css +4629 -0
- package/test-theme/assets/delivery-zone.css +299 -0
- package/test-theme/assets/delivery-zone.js +396 -0
- package/test-theme/assets/logo.png +0 -0
- package/test-theme/assets/sections.css +48 -0
- package/test-theme/assets/theme.css +3500 -0
- package/test-theme/assets/theme.js +3745 -0
- package/test-theme/config/settings_data.json +292 -0
- package/test-theme/config/settings_schema.json +1050 -0
- package/test-theme/layout/theme.liquid +195 -0
- package/test-theme/locales/en.default.json +260 -0
- package/test-theme/sections/content-fallback.liquid +53 -0
- package/test-theme/sections/content.liquid +57 -0
- package/test-theme/sections/footer-fallback.liquid +328 -0
- package/test-theme/sections/footer.liquid +278 -0
- package/test-theme/sections/header-fallback.liquid +1805 -0
- package/test-theme/sections/header.liquid +1145 -0
- package/test-theme/sections/hero-fallback.liquid +212 -0
- package/test-theme/sections/hero.liquid +136 -0
- package/test-theme/snippets/account-sidebar.liquid +200 -0
- package/test-theme/snippets/add-to-cart-modal.liquid +484 -0
- package/test-theme/snippets/breadcrumbs.liquid +134 -0
- package/test-theme/snippets/cart-drawer.liquid +467 -0
- package/test-theme/snippets/delivery-zone-city-selector.liquid +79 -0
- package/test-theme/snippets/delivery-zone-modal.liquid +337 -0
- package/test-theme/snippets/delivery-zone-search.liquid +78 -0
- package/test-theme/snippets/icon.liquid +105 -0
- package/test-theme/snippets/login-modal.liquid +346 -0
- package/test-theme/snippets/mega-menu.liquid +812 -0
- package/test-theme/snippets/news-thumbnail.liquid +187 -0
- package/test-theme/snippets/pagination.liquid +120 -0
- package/test-theme/snippets/price.liquid +92 -0
- package/test-theme/snippets/product-card-related.liquid +78 -0
- package/test-theme/snippets/product-card-simple.liquid +41 -0
- package/test-theme/snippets/product-card.liquid +697 -0
- package/test-theme/snippets/rating.liquid +85 -0
- package/test-theme/snippets/skeleton-collection-grid.liquid +114 -0
- package/test-theme/snippets/skeleton-product-card.liquid +124 -0
- package/test-theme/snippets/skeleton-product-grid.liquid +34 -0
- package/test-theme/snippets/social-sharing.liquid +185 -0
- package/test-theme/templates/account/dashboard.liquid +401 -0
- package/test-theme/templates/account/loyalty-redemption.liquid +405 -0
- package/test-theme/templates/account/loyalty.liquid +588 -0
- package/test-theme/templates/account/order-detail.liquid +230 -0
- package/test-theme/templates/account/orders.liquid +349 -0
- package/test-theme/templates/account/profile.liquid +758 -0
- package/test-theme/templates/account/register.liquid +232 -0
- package/test-theme/templates/account/return-orders.liquid +348 -0
- package/test-theme/templates/account/store-credit.liquid +464 -0
- package/test-theme/templates/account/subscriptions.liquid +601 -0
- package/test-theme/templates/account/wishlist.liquid +419 -0
- package/test-theme/templates/address-book.liquid +1092 -0
- package/test-theme/templates/categories.liquid +452 -0
- package/test-theme/templates/checkout.liquid +4511 -0
- package/test-theme/templates/error.liquid +384 -0
- package/test-theme/templates/index.liquid +11 -0
- package/test-theme/templates/login.liquid +185 -0
- package/test-theme/templates/order-confirmation.liquid +720 -0
- package/test-theme/templates/page.liquid +297 -0
- package/test-theme/templates/product-detail.liquid +4363 -0
- package/test-theme/templates/products.liquid +518 -0
- package/test-theme/templates/search.liquid +922 -0
- package/test-theme/theme.json.example +19 -0
- package/test-theme/widgets/brand-carousel.liquid +676 -0
- package/test-theme/widgets/brand.liquid +245 -0
- package/test-theme/widgets/carousel.liquid +843 -0
- package/test-theme/widgets/category-list-carousel.liquid +656 -0
- package/test-theme/widgets/category-list.liquid +340 -0
- package/test-theme/widgets/category.liquid +475 -0
- package/test-theme/widgets/discount-time.liquid +176 -0
- package/test-theme/widgets/footer-menu.liquid +695 -0
- package/test-theme/widgets/footer.liquid +179 -0
- package/test-theme/widgets/gallery.liquid +271 -0
- package/test-theme/widgets/header-menu.liquid +932 -0
- package/test-theme/widgets/header.liquid +159 -0
- package/test-theme/widgets/html.liquid +214 -0
- package/test-theme/widgets/news.liquid +217 -0
- package/test-theme/widgets/product-canvas.liquid +235 -0
- package/test-theme/widgets/product-carousel.liquid +502 -0
- package/test-theme/widgets/product.liquid +45 -0
- package/test-theme/widgets/recently-viewed.liquid +26 -0
- package/test-theme/widgets/shared/product-grid.liquid +339 -0
- package/test-theme/widgets/simple-product.liquid +42 -0
- package/test-theme/widgets/single-product.liquid +610 -0
- package/test-theme/widgets/spacebar-carousel.liquid +663 -0
- package/test-theme/widgets/spacebar.liquid +279 -0
- package/test-theme/widgets/splash.liquid +378 -0
- package/test-theme/widgets/testimonial-carousel.liquid +709 -0
|
@@ -0,0 +1,989 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock O2VEND Storefront API Server
|
|
3
|
+
* Provides realistic mock data for local development
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const express = require('express');
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const mockDataGenerator = require('./mock-data');
|
|
9
|
+
|
|
10
|
+
class MockApiServer {
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this.port = options.port || 3001;
|
|
13
|
+
this.app = express();
|
|
14
|
+
this.server = null;
|
|
15
|
+
console.log(chalk.cyan('[MOCK API] Generating mock data...'));
|
|
16
|
+
this.mockData = mockDataGenerator.generateMockData();
|
|
17
|
+
console.log(chalk.green(`[MOCK API] ✅ Mock data generated:`));
|
|
18
|
+
console.log(chalk.gray(` - Products: ${this.mockData.products?.length || 0}`));
|
|
19
|
+
console.log(chalk.gray(` - Categories: ${this.mockData.categories?.length || 0}`));
|
|
20
|
+
console.log(chalk.gray(` - Brands: ${this.mockData.brands?.length || 0}`));
|
|
21
|
+
console.log(chalk.gray(` - Widgets: ${this.mockData.widgets?.length || 0}`));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Setup routes
|
|
26
|
+
*/
|
|
27
|
+
setupRoutes() {
|
|
28
|
+
// Body parser
|
|
29
|
+
this.app.use(express.json());
|
|
30
|
+
this.app.use(express.urlencoded({ extended: true }));
|
|
31
|
+
|
|
32
|
+
// Store Info
|
|
33
|
+
this.app.get('/shopfront/api/v2/storeinfo', (req, res) => {
|
|
34
|
+
res.json(this.mockData.store);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Store Settings
|
|
38
|
+
this.app.get('/shopfront/api/v2/settings', (req, res) => {
|
|
39
|
+
res.json(this.mockData.settings);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Products
|
|
43
|
+
this.app.post('/shopfront/api/v2/products', (req, res) => {
|
|
44
|
+
const { limit = 20, offset = 0, categoryId, brandId, search } = req.body;
|
|
45
|
+
let products = [...this.mockData.products];
|
|
46
|
+
|
|
47
|
+
// Filter by category
|
|
48
|
+
if (categoryId) {
|
|
49
|
+
products = products.filter(p => p.categoryId === categoryId);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Filter by brand
|
|
53
|
+
if (brandId) {
|
|
54
|
+
products = products.filter(p => p.brandId === brandId);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Search
|
|
58
|
+
if (search) {
|
|
59
|
+
const searchLower = search.toLowerCase();
|
|
60
|
+
products = products.filter(p =>
|
|
61
|
+
p.title.toLowerCase().includes(searchLower) ||
|
|
62
|
+
p.description?.toLowerCase().includes(searchLower)
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Paginate
|
|
67
|
+
const paginated = products.slice(offset, offset + limit);
|
|
68
|
+
|
|
69
|
+
// DEBUG: Verify products have stock/quantity before sending
|
|
70
|
+
if (paginated.length > 0) {
|
|
71
|
+
const sampleProduct = paginated[0];
|
|
72
|
+
const hasStock = 'stock' in sampleProduct;
|
|
73
|
+
// Use nullish coalescing to show 0 values correctly
|
|
74
|
+
const stockValue = sampleProduct.stock ?? 'N/A';
|
|
75
|
+
console.log(`[MOCK API] Returning ${paginated.length} products (total: ${products.length})`);
|
|
76
|
+
console.log(`[MOCK API DEBUG] Sample product - Title: ${sampleProduct.title}, Has stock field: ${hasStock}, Stock value: ${stockValue}`);
|
|
77
|
+
console.log(`[MOCK API DEBUG] Sample product stock details: stock=${sampleProduct.stock}, inStock=${sampleProduct.inStock}`);
|
|
78
|
+
if (sampleProduct.variants && sampleProduct.variants.length > 0) {
|
|
79
|
+
const sampleVariant = sampleProduct.variants[0];
|
|
80
|
+
const variantHasStock = 'stock' in sampleVariant;
|
|
81
|
+
const variantStockValue = sampleVariant.stock ?? 'N/A';
|
|
82
|
+
console.log(`[MOCK API DEBUG] Sample variant - Has stock field: ${variantHasStock}, Stock value: ${variantStockValue}`);
|
|
83
|
+
console.log(`[MOCK API DEBUG] Sample variant stock details: stock=${sampleVariant.stock}, inStock=${sampleVariant.inStock}, available=${sampleVariant.available}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
res.json({
|
|
88
|
+
products: paginated,
|
|
89
|
+
total: products.length,
|
|
90
|
+
limit,
|
|
91
|
+
offset
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Product by ID
|
|
96
|
+
this.app.get('/shopfront/api/v2/products/:id', (req, res) => {
|
|
97
|
+
const product = this.mockData.products.find(p => p.id === req.params.id);
|
|
98
|
+
if (product) {
|
|
99
|
+
res.json(product);
|
|
100
|
+
} else {
|
|
101
|
+
res.status(404).json({ error: 'Product not found' });
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Product Search
|
|
106
|
+
this.app.post('/shopfront/api/v2/products/search', (req, res) => {
|
|
107
|
+
const { query, limit = 20, offset = 0 } = req.body;
|
|
108
|
+
const searchLower = (query || '').toLowerCase();
|
|
109
|
+
|
|
110
|
+
let products = this.mockData.products.filter(p =>
|
|
111
|
+
p.title.toLowerCase().includes(searchLower) ||
|
|
112
|
+
p.description?.toLowerCase().includes(searchLower)
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const paginated = products.slice(offset, offset + limit);
|
|
116
|
+
|
|
117
|
+
res.json({
|
|
118
|
+
products: paginated,
|
|
119
|
+
total: products.length,
|
|
120
|
+
limit,
|
|
121
|
+
offset,
|
|
122
|
+
query
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Categories
|
|
127
|
+
this.app.post('/shopfront/api/v2/categories', (req, res) => {
|
|
128
|
+
const { limit = 50, offset = 0 } = req.body;
|
|
129
|
+
const paginated = this.mockData.categories.slice(offset, offset + limit);
|
|
130
|
+
|
|
131
|
+
res.json({
|
|
132
|
+
categories: paginated,
|
|
133
|
+
total: this.mockData.categories.length,
|
|
134
|
+
limit,
|
|
135
|
+
offset
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Category by ID
|
|
140
|
+
this.app.get('/shopfront/api/v2/categories/:id', (req, res) => {
|
|
141
|
+
const category = this.mockData.categories.find(c => c.id === req.params.id);
|
|
142
|
+
if (category) {
|
|
143
|
+
res.json(category);
|
|
144
|
+
} else {
|
|
145
|
+
res.status(404).json({ error: 'Category not found' });
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Brands
|
|
150
|
+
this.app.post('/shopfront/api/v2/brands', (req, res) => {
|
|
151
|
+
const { limit = 50, offset = 0 } = req.body;
|
|
152
|
+
const paginated = this.mockData.brands.slice(offset, offset + limit);
|
|
153
|
+
|
|
154
|
+
res.json({
|
|
155
|
+
brands: paginated,
|
|
156
|
+
total: this.mockData.brands.length,
|
|
157
|
+
limit,
|
|
158
|
+
offset
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Widgets - GET endpoint
|
|
163
|
+
this.app.get('/shopfront/api/v2/widgets', (req, res) => {
|
|
164
|
+
const { section, pageId, status } = req.query;
|
|
165
|
+
|
|
166
|
+
let widgets = [...this.mockData.widgets];
|
|
167
|
+
|
|
168
|
+
// Filter by section
|
|
169
|
+
if (section) {
|
|
170
|
+
widgets = widgets.filter(w => w.section === section || w.sectionName === section);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Filter by status (default to 'active' if not specified)
|
|
174
|
+
const filterStatus = status || 'active';
|
|
175
|
+
widgets = widgets.filter(w => w.status === filterStatus);
|
|
176
|
+
|
|
177
|
+
// Sort by position
|
|
178
|
+
widgets.sort((a, b) => (a.position || 0) - (b.position || 0));
|
|
179
|
+
|
|
180
|
+
res.json({
|
|
181
|
+
widgets: widgets,
|
|
182
|
+
total: widgets.length
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Widgets - POST endpoint
|
|
187
|
+
this.app.post('/shopfront/api/v2/widgets', (req, res) => {
|
|
188
|
+
const { section, pageId, status } = req.body;
|
|
189
|
+
|
|
190
|
+
let widgets = [...this.mockData.widgets];
|
|
191
|
+
|
|
192
|
+
// Filter by section
|
|
193
|
+
if (section) {
|
|
194
|
+
widgets = widgets.filter(w => w.section === section || w.sectionName === section);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Filter by status (default to 'active' if not specified)
|
|
198
|
+
const filterStatus = status || 'active';
|
|
199
|
+
widgets = widgets.filter(w => w.status === filterStatus);
|
|
200
|
+
|
|
201
|
+
// Sort by position
|
|
202
|
+
widgets.sort((a, b) => (a.position || 0) - (b.position || 0));
|
|
203
|
+
|
|
204
|
+
res.json({
|
|
205
|
+
widgets: widgets,
|
|
206
|
+
total: widgets.length
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Cart endpoints
|
|
211
|
+
this.app.get('/shopfront/api/v2/cart/:cartId', (req, res) => {
|
|
212
|
+
res.json(this.mockData.cart);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
this.app.post('/shopfront/api/v2/cart/:cartId/items', (req, res) => {
|
|
216
|
+
// Add item to cart
|
|
217
|
+
const newItem = {
|
|
218
|
+
id: Date.now().toString(),
|
|
219
|
+
productId: req.body.productId,
|
|
220
|
+
variantId: req.body.variantId,
|
|
221
|
+
quantity: req.body.quantity || 1,
|
|
222
|
+
...req.body
|
|
223
|
+
};
|
|
224
|
+
this.mockData.cart.items.push(newItem);
|
|
225
|
+
this.mockData.cart.itemCount = this.mockData.cart.items.length;
|
|
226
|
+
this.mockData.cart.total = this.mockData.cart.items.reduce((sum, item) =>
|
|
227
|
+
sum + (item.price * item.quantity), 0
|
|
228
|
+
);
|
|
229
|
+
res.json(this.mockData.cart);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Webstore API endpoints (for frontend JavaScript)
|
|
233
|
+
|
|
234
|
+
// Menus API
|
|
235
|
+
// Return array directly for frontend compatibility
|
|
236
|
+
// Enhanced with proper type field and nested child items
|
|
237
|
+
this.app.get('/webstoreapi/menus', (req, res) => {
|
|
238
|
+
const menus = [
|
|
239
|
+
{
|
|
240
|
+
id: 'main-menu',
|
|
241
|
+
name: 'Main Menu',
|
|
242
|
+
type: 'Main Menu', // Type field for filtering
|
|
243
|
+
items: [
|
|
244
|
+
{
|
|
245
|
+
id: '1',
|
|
246
|
+
name: 'Home',
|
|
247
|
+
title: 'Home',
|
|
248
|
+
link: '/',
|
|
249
|
+
url: '/',
|
|
250
|
+
displayOrder: 1,
|
|
251
|
+
childItems: []
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
id: '2',
|
|
255
|
+
name: 'Products',
|
|
256
|
+
title: 'Products',
|
|
257
|
+
link: '/products',
|
|
258
|
+
url: '/products',
|
|
259
|
+
displayOrder: 2,
|
|
260
|
+
childItems: [
|
|
261
|
+
{ id: '2-1', name: 'Electronics', title: 'Electronics', link: '/products?category=electronics', url: '/products?category=electronics', displayOrder: 1 },
|
|
262
|
+
{ id: '2-2', name: 'Clothing', title: 'Clothing', link: '/products?category=clothing', url: '/products?category=clothing', displayOrder: 2 },
|
|
263
|
+
{ id: '2-3', name: 'Accessories', title: 'Accessories', link: '/products?category=accessories', url: '/products?category=accessories', displayOrder: 3 },
|
|
264
|
+
{ id: '2-4', name: 'Home & Garden', title: 'Home & Garden', link: '/products?category=home-garden', url: '/products?category=home-garden', displayOrder: 4 }
|
|
265
|
+
]
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
id: '3',
|
|
269
|
+
name: 'Collections',
|
|
270
|
+
title: 'Collections',
|
|
271
|
+
link: '/collections',
|
|
272
|
+
url: '/collections',
|
|
273
|
+
displayOrder: 3,
|
|
274
|
+
childItems: [
|
|
275
|
+
{ id: '3-1', name: 'New Arrivals', title: 'New Arrivals', link: '/collections/new-arrivals', url: '/collections/new-arrivals', displayOrder: 1 },
|
|
276
|
+
{ id: '3-2', name: 'Best Sellers', title: 'Best Sellers', link: '/collections/best-sellers', url: '/collections/best-sellers', displayOrder: 2 },
|
|
277
|
+
{ id: '3-3', name: 'On Sale', title: 'On Sale', link: '/collections/sale', url: '/collections/sale', displayOrder: 3 }
|
|
278
|
+
]
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
id: '4',
|
|
282
|
+
name: 'About',
|
|
283
|
+
title: 'About',
|
|
284
|
+
link: '/page/about',
|
|
285
|
+
url: '/page/about',
|
|
286
|
+
displayOrder: 4,
|
|
287
|
+
childItems: []
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
id: '5',
|
|
291
|
+
name: 'Contact',
|
|
292
|
+
title: 'Contact',
|
|
293
|
+
link: '/page/contact',
|
|
294
|
+
url: '/page/contact',
|
|
295
|
+
displayOrder: 5,
|
|
296
|
+
childItems: []
|
|
297
|
+
}
|
|
298
|
+
]
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
id: 'footer-menu',
|
|
302
|
+
name: 'Footer Menu',
|
|
303
|
+
type: 'Footer Menu', // Type field for filtering
|
|
304
|
+
items: [
|
|
305
|
+
{ id: '6', name: 'Privacy Policy', title: 'Privacy Policy', link: '/pages/privacy', url: '/pages/privacy', displayOrder: 1 },
|
|
306
|
+
{ id: '7', name: 'Terms of Service', title: 'Terms of Service', link: '/pages/terms', url: '/pages/terms', displayOrder: 2 },
|
|
307
|
+
{ id: '8', name: 'Shipping', title: 'Shipping', link: '/pages/shipping', url: '/pages/shipping', displayOrder: 3 },
|
|
308
|
+
{ id: '9', name: 'Returns', title: 'Returns', link: '/pages/returns', url: '/pages/returns', displayOrder: 4 }
|
|
309
|
+
]
|
|
310
|
+
}
|
|
311
|
+
];
|
|
312
|
+
|
|
313
|
+
// Support both array format (for frontend) and object format (for backward compatibility)
|
|
314
|
+
if (req.query.format === 'object') {
|
|
315
|
+
res.json({
|
|
316
|
+
success: true,
|
|
317
|
+
data: { menus }
|
|
318
|
+
});
|
|
319
|
+
} else {
|
|
320
|
+
res.json(menus);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
this.app.get('/webstoreapi/menus/:menuId', (req, res) => {
|
|
325
|
+
const menuId = req.params.menuId;
|
|
326
|
+
const menus = {
|
|
327
|
+
'main-menu': {
|
|
328
|
+
id: 'main-menu',
|
|
329
|
+
name: 'Main Menu',
|
|
330
|
+
type: 'Main Menu',
|
|
331
|
+
items: [
|
|
332
|
+
{
|
|
333
|
+
id: '1',
|
|
334
|
+
name: 'Home',
|
|
335
|
+
title: 'Home',
|
|
336
|
+
link: '/',
|
|
337
|
+
url: '/',
|
|
338
|
+
displayOrder: 1,
|
|
339
|
+
childItems: []
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
id: '2',
|
|
343
|
+
name: 'Products',
|
|
344
|
+
title: 'Products',
|
|
345
|
+
link: '/products',
|
|
346
|
+
url: '/products',
|
|
347
|
+
displayOrder: 2,
|
|
348
|
+
childItems: [
|
|
349
|
+
{ id: '2-1', name: 'Electronics', title: 'Electronics', link: '/products?category=electronics', url: '/products?category=electronics', displayOrder: 1 },
|
|
350
|
+
{ id: '2-2', name: 'Clothing', title: 'Clothing', link: '/products?category=clothing', url: '/products?category=clothing', displayOrder: 2 },
|
|
351
|
+
{ id: '2-3', name: 'Accessories', title: 'Accessories', link: '/products?category=accessories', url: '/products?category=accessories', displayOrder: 3 },
|
|
352
|
+
{ id: '2-4', name: 'Home & Garden', title: 'Home & Garden', link: '/products?category=home-garden', url: '/products?category=home-garden', displayOrder: 4 }
|
|
353
|
+
]
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
id: '3',
|
|
357
|
+
name: 'Collections',
|
|
358
|
+
title: 'Collections',
|
|
359
|
+
link: '/collections',
|
|
360
|
+
url: '/collections',
|
|
361
|
+
displayOrder: 3,
|
|
362
|
+
childItems: [
|
|
363
|
+
{ id: '3-1', name: 'New Arrivals', title: 'New Arrivals', link: '/collections/new-arrivals', url: '/collections/new-arrivals', displayOrder: 1 },
|
|
364
|
+
{ id: '3-2', name: 'Best Sellers', title: 'Best Sellers', link: '/collections/best-sellers', url: '/collections/best-sellers', displayOrder: 2 },
|
|
365
|
+
{ id: '3-3', name: 'On Sale', title: 'On Sale', link: '/collections/sale', url: '/collections/sale', displayOrder: 3 }
|
|
366
|
+
]
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
id: '4',
|
|
370
|
+
name: 'About',
|
|
371
|
+
title: 'About',
|
|
372
|
+
link: '/page/about',
|
|
373
|
+
url: '/page/about',
|
|
374
|
+
displayOrder: 4,
|
|
375
|
+
childItems: []
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
id: '5',
|
|
379
|
+
name: 'Contact',
|
|
380
|
+
title: 'Contact',
|
|
381
|
+
link: '/page/contact',
|
|
382
|
+
url: '/page/contact',
|
|
383
|
+
displayOrder: 5,
|
|
384
|
+
childItems: []
|
|
385
|
+
}
|
|
386
|
+
]
|
|
387
|
+
},
|
|
388
|
+
'footer-menu': {
|
|
389
|
+
id: 'footer-menu',
|
|
390
|
+
name: 'Footer Menu',
|
|
391
|
+
type: 'Footer Menu',
|
|
392
|
+
items: [
|
|
393
|
+
{ id: '6', name: 'Privacy Policy', title: 'Privacy Policy', link: '/pages/privacy', url: '/pages/privacy', displayOrder: 1 },
|
|
394
|
+
{ id: '7', name: 'Terms of Service', title: 'Terms of Service', link: '/pages/terms', url: '/pages/terms', displayOrder: 2 },
|
|
395
|
+
{ id: '8', name: 'Shipping', title: 'Shipping', link: '/pages/shipping', url: '/pages/shipping', displayOrder: 3 },
|
|
396
|
+
{ id: '9', name: 'Returns', title: 'Returns', link: '/pages/returns', url: '/pages/returns', displayOrder: 4 }
|
|
397
|
+
]
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
const menu = menus[menuId];
|
|
402
|
+
if (menu) {
|
|
403
|
+
// Return menu directly (not wrapped in success/data) for frontend compatibility
|
|
404
|
+
res.json(menu);
|
|
405
|
+
} else {
|
|
406
|
+
// Return main-menu as fallback instead of 404
|
|
407
|
+
res.json(menus['main-menu']);
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// Cart quantity endpoint - matches webstore format
|
|
412
|
+
this.app.get('/webstoreapi/carts/quantity', (req, res) => {
|
|
413
|
+
res.json({
|
|
414
|
+
success: true,
|
|
415
|
+
data: {
|
|
416
|
+
cartQuantity: this.mockData.cart.itemCount || 0
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// Cart endpoint (for cart drawer/panel) - matches webstore format
|
|
422
|
+
this.app.get('/webstoreapi/carts', (req, res) => {
|
|
423
|
+
// Transform cart to match webstore response format
|
|
424
|
+
const cart = {
|
|
425
|
+
items: this.mockData.cart.items || [],
|
|
426
|
+
total: this.mockData.cart.total || 0,
|
|
427
|
+
itemCount: this.mockData.cart.itemCount || 0,
|
|
428
|
+
subTotal: this.mockData.cart.total || 0,
|
|
429
|
+
taxAmount: 0,
|
|
430
|
+
shippingAmount: 0
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
res.json({
|
|
434
|
+
success: true,
|
|
435
|
+
data: cart
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
this.app.get('/webstoreapi/carts/:cartId', (req, res) => {
|
|
440
|
+
// Return specific cart by ID
|
|
441
|
+
res.json(this.mockData.cart);
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// Add item to cart - simplified endpoint (matches webstore)
|
|
445
|
+
this.app.post('/webstoreapi/carts/add', (req, res) => {
|
|
446
|
+
if (!req.body.productId || !req.body.quantity) {
|
|
447
|
+
return res.status(400).json({
|
|
448
|
+
success: false,
|
|
449
|
+
error: 'Product ID and quantity are required'
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const product = this.mockData.products.find(p => p.id === req.body.productId || p.productId === parseInt(req.body.productId));
|
|
454
|
+
if (!product) {
|
|
455
|
+
return res.status(404).json({
|
|
456
|
+
success: false,
|
|
457
|
+
error: 'Product not found'
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const newItem = {
|
|
462
|
+
itemId: Date.now().toString(),
|
|
463
|
+
id: Date.now().toString(),
|
|
464
|
+
productId: parseInt(req.body.productId),
|
|
465
|
+
variantId: req.body.variantId || 0,
|
|
466
|
+
quantity: parseFloat(req.body.quantity) || 1,
|
|
467
|
+
title: product.title,
|
|
468
|
+
image: product.images?.[0]?.url || null,
|
|
469
|
+
price: product.price || 0,
|
|
470
|
+
linePrice: (product.price || 0) * (parseFloat(req.body.quantity) || 1),
|
|
471
|
+
productSlug: product.handle || product.id,
|
|
472
|
+
variantTitle: 'Default Title',
|
|
473
|
+
compareAtPrice: product.compareAtPrice || null,
|
|
474
|
+
sku: product.sku || null
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
// Check if item already exists in cart
|
|
478
|
+
const existingItem = this.mockData.cart.items.find(i =>
|
|
479
|
+
i.productId === newItem.productId && i.variantId === newItem.variantId
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
if (existingItem) {
|
|
483
|
+
existingItem.quantity += newItem.quantity;
|
|
484
|
+
existingItem.linePrice = existingItem.price * existingItem.quantity;
|
|
485
|
+
} else {
|
|
486
|
+
this.mockData.cart.items.push(newItem);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
this.mockData.cart.itemCount = this.mockData.cart.items.reduce((sum, item) => sum + (item.quantity || 0), 0);
|
|
490
|
+
this.mockData.cart.total = this.mockData.cart.items.reduce((sum, item) =>
|
|
491
|
+
sum + (item.price * item.quantity), 0
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
res.json({
|
|
495
|
+
success: true,
|
|
496
|
+
data: this.mockData.cart
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
// Add item to cart
|
|
501
|
+
this.app.post('/webstoreapi/carts/:cartId/items', (req, res) => {
|
|
502
|
+
if (!req.body.productId || !req.body.quantity) {
|
|
503
|
+
return res.status(400).json({
|
|
504
|
+
success: false,
|
|
505
|
+
error: 'Product ID and quantity are required'
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const product = this.mockData.products.find(p => p.id === req.body.productId || p.productId === parseInt(req.body.productId));
|
|
510
|
+
if (!product) {
|
|
511
|
+
return res.status(404).json({
|
|
512
|
+
success: false,
|
|
513
|
+
error: 'Product not found'
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const newItem = {
|
|
518
|
+
itemId: Date.now().toString(),
|
|
519
|
+
id: Date.now().toString(),
|
|
520
|
+
productId: parseInt(req.body.productId),
|
|
521
|
+
variantId: req.body.variantId || 0,
|
|
522
|
+
quantity: parseFloat(req.body.quantity) || 1,
|
|
523
|
+
title: product.title,
|
|
524
|
+
image: product.images?.[0]?.url || null,
|
|
525
|
+
price: product.price || 0,
|
|
526
|
+
linePrice: (product.price || 0) * (parseFloat(req.body.quantity) || 1),
|
|
527
|
+
productSlug: product.handle || product.id,
|
|
528
|
+
variantTitle: 'Default Title',
|
|
529
|
+
compareAtPrice: product.compareAtPrice || null,
|
|
530
|
+
sku: product.sku || null
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
this.mockData.cart.items.push(newItem);
|
|
534
|
+
this.mockData.cart.itemCount = this.mockData.cart.items.reduce((sum, item) => sum + (item.quantity || 0), 0);
|
|
535
|
+
this.mockData.cart.total = this.mockData.cart.items.reduce((sum, item) =>
|
|
536
|
+
sum + (item.price * item.quantity), 0
|
|
537
|
+
);
|
|
538
|
+
|
|
539
|
+
res.json(this.mockData.cart);
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
// Update cart item
|
|
543
|
+
this.app.put('/webstoreapi/carts/:cartId/items/:itemId', (req, res) => {
|
|
544
|
+
const item = this.mockData.cart.items.find(i => i.id === req.params.itemId);
|
|
545
|
+
if (item) {
|
|
546
|
+
if (req.body.quantity !== undefined) {
|
|
547
|
+
item.quantity = req.body.quantity;
|
|
548
|
+
}
|
|
549
|
+
if (req.body.price !== undefined) {
|
|
550
|
+
item.price = req.body.price;
|
|
551
|
+
}
|
|
552
|
+
this.mockData.cart.itemCount = this.mockData.cart.items.length;
|
|
553
|
+
this.mockData.cart.total = this.mockData.cart.items.reduce((sum, item) =>
|
|
554
|
+
sum + (item.price * item.quantity), 0
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
res.json(this.mockData.cart);
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
// Remove item from cart
|
|
561
|
+
this.app.delete('/webstoreapi/carts/:cartId/items/:itemId', (req, res) => {
|
|
562
|
+
this.mockData.cart.items = this.mockData.cart.items.filter(i =>
|
|
563
|
+
i.id !== req.params.itemId && i.itemId !== req.params.itemId
|
|
564
|
+
);
|
|
565
|
+
this.mockData.cart.itemCount = this.mockData.cart.items.reduce((sum, item) => sum + (item.quantity || 0), 0);
|
|
566
|
+
this.mockData.cart.total = this.mockData.cart.items.reduce((sum, item) =>
|
|
567
|
+
sum + (item.price * item.quantity), 0
|
|
568
|
+
);
|
|
569
|
+
res.json(this.mockData.cart);
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
// Products endpoint (GET) - matches webstore format
|
|
573
|
+
this.app.get('/webstoreapi/products', (req, res) => {
|
|
574
|
+
const { limit = 20, offset = 0, category, brand, search } = req.query;
|
|
575
|
+
let products = [...this.mockData.products];
|
|
576
|
+
|
|
577
|
+
// Filter by category
|
|
578
|
+
if (category) {
|
|
579
|
+
products = products.filter(p =>
|
|
580
|
+
p.categoryId === category ||
|
|
581
|
+
p.categoryId === `category-${category}` ||
|
|
582
|
+
String(p.categoryId).includes(String(category))
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Filter by brand
|
|
587
|
+
if (brand) {
|
|
588
|
+
products = products.filter(p =>
|
|
589
|
+
p.brandId === brand ||
|
|
590
|
+
p.brandId === `brand-${brand}` ||
|
|
591
|
+
String(p.brandId).includes(String(brand))
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Search
|
|
596
|
+
if (search) {
|
|
597
|
+
const searchLower = search.toLowerCase();
|
|
598
|
+
products = products.filter(p =>
|
|
599
|
+
p.title.toLowerCase().includes(searchLower) ||
|
|
600
|
+
p.description?.toLowerCase().includes(searchLower) ||
|
|
601
|
+
p.handle?.toLowerCase().includes(searchLower)
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Paginate
|
|
606
|
+
const paginated = products.slice(parseInt(offset), parseInt(offset) + parseInt(limit));
|
|
607
|
+
|
|
608
|
+
res.json({
|
|
609
|
+
success: true,
|
|
610
|
+
data: {
|
|
611
|
+
products: paginated,
|
|
612
|
+
total: products.length,
|
|
613
|
+
limit: parseInt(limit),
|
|
614
|
+
offset: parseInt(offset)
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
// Product by ID endpoint (GET) - matches webstore format
|
|
620
|
+
this.app.get('/webstoreapi/products/:productId', (req, res) => {
|
|
621
|
+
const productId = parseInt(req.params.productId);
|
|
622
|
+
const product = this.mockData.products.find(p =>
|
|
623
|
+
p.id === req.params.productId ||
|
|
624
|
+
p.productId === productId ||
|
|
625
|
+
parseInt(p.id) === productId
|
|
626
|
+
);
|
|
627
|
+
|
|
628
|
+
if (product) {
|
|
629
|
+
res.json({
|
|
630
|
+
success: true,
|
|
631
|
+
data: product
|
|
632
|
+
});
|
|
633
|
+
} else {
|
|
634
|
+
res.status(404).json({
|
|
635
|
+
success: false,
|
|
636
|
+
error: 'Product not found'
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
// Categories endpoint (GET) - matches webstore format
|
|
642
|
+
this.app.get('/webstoreapi/categories', (req, res) => {
|
|
643
|
+
const { limit = 50, offset = 0 } = req.query;
|
|
644
|
+
const paginated = this.mockData.categories.slice(parseInt(offset), parseInt(offset) + parseInt(limit));
|
|
645
|
+
|
|
646
|
+
res.json({
|
|
647
|
+
success: true,
|
|
648
|
+
data: {
|
|
649
|
+
categories: paginated,
|
|
650
|
+
total: this.mockData.categories.length,
|
|
651
|
+
limit: parseInt(limit),
|
|
652
|
+
offset: parseInt(offset)
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
// Brands endpoint (GET) - matches webstore format
|
|
658
|
+
this.app.get('/webstoreapi/brands', (req, res) => {
|
|
659
|
+
const { limit = 50, offset = 0 } = req.query;
|
|
660
|
+
const paginated = this.mockData.brands.slice(parseInt(offset), parseInt(offset) + parseInt(limit));
|
|
661
|
+
|
|
662
|
+
res.json({
|
|
663
|
+
success: true,
|
|
664
|
+
data: {
|
|
665
|
+
brands: paginated,
|
|
666
|
+
total: this.mockData.brands.length,
|
|
667
|
+
limit: parseInt(limit),
|
|
668
|
+
offset: parseInt(offset)
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
// Shopfront Page API - Landing pages from CMS (O2VEND format)
|
|
674
|
+
this.app.get('/shopfront/api/v2/page/:handle', (req, res) => {
|
|
675
|
+
const handle = req.params.handle;
|
|
676
|
+
const landingPages = {
|
|
677
|
+
'about': {
|
|
678
|
+
id: 'landing-about',
|
|
679
|
+
slug: 'about',
|
|
680
|
+
title: 'About Us',
|
|
681
|
+
metaTitle: 'About Us - My O2VEND Store',
|
|
682
|
+
metaDescription: 'Learn about our company and mission',
|
|
683
|
+
htmlContent: `
|
|
684
|
+
<div class="landing-page about-page">
|
|
685
|
+
<div class="container">
|
|
686
|
+
<div class="page-hero" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 60px 20px; text-align: center; color: white; border-radius: 12px; margin-bottom: 40px;">
|
|
687
|
+
<h1 style="font-size: 3rem; margin-bottom: 20px;">About Our Store</h1>
|
|
688
|
+
<p style="font-size: 1.25rem; opacity: 0.9;">Discover who we are and what drives us</p>
|
|
689
|
+
</div>
|
|
690
|
+
|
|
691
|
+
<div class="content-section" style="display: grid; grid-template-columns: 1fr 1fr; gap: 40px; margin-bottom: 40px;">
|
|
692
|
+
<div>
|
|
693
|
+
<h2 style="color: #333; margin-bottom: 20px;">Our Story</h2>
|
|
694
|
+
<p style="color: #666; line-height: 1.8;">We are a passionate team dedicated to bringing you the best products at competitive prices. Founded in 2020, we've grown from a small startup to a trusted online retailer serving thousands of happy customers worldwide.</p>
|
|
695
|
+
<p style="color: #666; line-height: 1.8; margin-top: 15px;">Our journey began with a simple idea: make quality products accessible to everyone. Today, we continue to uphold that vision while constantly improving our offerings.</p>
|
|
696
|
+
</div>
|
|
697
|
+
<div>
|
|
698
|
+
<img src="https://picsum.photos/seed/about1/600/400" alt="Our Team" style="width: 100%; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.1);">
|
|
699
|
+
</div>
|
|
700
|
+
</div>
|
|
701
|
+
|
|
702
|
+
<div class="values-section" style="background: #f8f9fa; padding: 40px; border-radius: 12px; margin-bottom: 40px;">
|
|
703
|
+
<h2 style="text-align: center; color: #333; margin-bottom: 30px;">Our Values</h2>
|
|
704
|
+
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 30px;">
|
|
705
|
+
<div style="text-align: center;">
|
|
706
|
+
<div style="width: 60px; height: 60px; background: #667eea; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 15px;">
|
|
707
|
+
<svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
|
|
708
|
+
</div>
|
|
709
|
+
<h3 style="color: #333; margin-bottom: 10px;">Quality</h3>
|
|
710
|
+
<p style="color: #666; font-size: 0.9rem;">Premium products curated for you</p>
|
|
711
|
+
</div>
|
|
712
|
+
<div style="text-align: center;">
|
|
713
|
+
<div style="width: 60px; height: 60px; background: #667eea; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 15px;">
|
|
714
|
+
<svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>
|
|
715
|
+
</div>
|
|
716
|
+
<h3 style="color: #333; margin-bottom: 10px;">Service</h3>
|
|
717
|
+
<p style="color: #666; font-size: 0.9rem;">Customer satisfaction first</p>
|
|
718
|
+
</div>
|
|
719
|
+
<div style="text-align: center;">
|
|
720
|
+
<div style="width: 60px; height: 60px; background: #667eea; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 15px;">
|
|
721
|
+
<svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
|
722
|
+
</div>
|
|
723
|
+
<h3 style="color: #333; margin-bottom: 10px;">Trust</h3>
|
|
724
|
+
<p style="color: #666; font-size: 0.9rem;">Transparent and honest</p>
|
|
725
|
+
</div>
|
|
726
|
+
<div style="text-align: center;">
|
|
727
|
+
<div style="width: 60px; height: 60px; background: #667eea; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 15px;">
|
|
728
|
+
<svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
|
|
729
|
+
</div>
|
|
730
|
+
<h3 style="color: #333; margin-bottom: 10px;">Innovation</h3>
|
|
731
|
+
<p style="color: #666; font-size: 0.9rem;">Always improving</p>
|
|
732
|
+
</div>
|
|
733
|
+
</div>
|
|
734
|
+
</div>
|
|
735
|
+
</div>
|
|
736
|
+
</div>
|
|
737
|
+
`,
|
|
738
|
+
content: 'About Us - Our Story and Values'
|
|
739
|
+
},
|
|
740
|
+
'contact': {
|
|
741
|
+
id: 'landing-contact',
|
|
742
|
+
slug: 'contact',
|
|
743
|
+
title: 'Contact Us',
|
|
744
|
+
metaTitle: 'Contact Us - My O2VEND Store',
|
|
745
|
+
metaDescription: 'Get in touch with our team',
|
|
746
|
+
htmlContent: `
|
|
747
|
+
<div class="landing-page contact-page">
|
|
748
|
+
<div class="container">
|
|
749
|
+
<div class="page-hero" style="background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); padding: 60px 20px; text-align: center; color: white; border-radius: 12px; margin-bottom: 40px;">
|
|
750
|
+
<h1 style="font-size: 3rem; margin-bottom: 20px;">Contact Us</h1>
|
|
751
|
+
<p style="font-size: 1.25rem; opacity: 0.9;">We'd love to hear from you</p>
|
|
752
|
+
</div>
|
|
753
|
+
|
|
754
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 40px;">
|
|
755
|
+
<div style="background: #fff; padding: 40px; border-radius: 12px; box-shadow: 0 5px 20px rgba(0,0,0,0.08);">
|
|
756
|
+
<h2 style="color: #333; margin-bottom: 25px;">Send us a message</h2>
|
|
757
|
+
<form style="display: flex; flex-direction: column; gap: 20px;">
|
|
758
|
+
<div>
|
|
759
|
+
<label style="display: block; margin-bottom: 8px; color: #333; font-weight: 500;">Name</label>
|
|
760
|
+
<input type="text" placeholder="Your name" style="width: 100%; padding: 12px 16px; border: 1px solid #ddd; border-radius: 8px; font-size: 1rem;">
|
|
761
|
+
</div>
|
|
762
|
+
<div>
|
|
763
|
+
<label style="display: block; margin-bottom: 8px; color: #333; font-weight: 500;">Email</label>
|
|
764
|
+
<input type="email" placeholder="your@email.com" style="width: 100%; padding: 12px 16px; border: 1px solid #ddd; border-radius: 8px; font-size: 1rem;">
|
|
765
|
+
</div>
|
|
766
|
+
<div>
|
|
767
|
+
<label style="display: block; margin-bottom: 8px; color: #333; font-weight: 500;">Message</label>
|
|
768
|
+
<textarea rows="5" placeholder="How can we help you?" style="width: 100%; padding: 12px 16px; border: 1px solid #ddd; border-radius: 8px; font-size: 1rem; resize: vertical;"></textarea>
|
|
769
|
+
</div>
|
|
770
|
+
<button type="submit" style="background: #11998e; color: white; padding: 14px 28px; border: none; border-radius: 8px; font-size: 1rem; cursor: pointer; font-weight: 500;">Send Message</button>
|
|
771
|
+
</form>
|
|
772
|
+
</div>
|
|
773
|
+
|
|
774
|
+
<div>
|
|
775
|
+
<div style="background: #f8f9fa; padding: 30px; border-radius: 12px; margin-bottom: 20px;">
|
|
776
|
+
<h3 style="color: #333; margin-bottom: 15px; display: flex; align-items: center; gap: 10px;">
|
|
777
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#11998e" stroke-width="2"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>
|
|
778
|
+
Address
|
|
779
|
+
</h3>
|
|
780
|
+
<p style="color: #666; line-height: 1.6;">123 Commerce Street<br>Business District<br>New York, NY 10001</p>
|
|
781
|
+
</div>
|
|
782
|
+
<div style="background: #f8f9fa; padding: 30px; border-radius: 12px; margin-bottom: 20px;">
|
|
783
|
+
<h3 style="color: #333; margin-bottom: 15px; display: flex; align-items: center; gap: 10px;">
|
|
784
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#11998e" stroke-width="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/></svg>
|
|
785
|
+
Phone
|
|
786
|
+
</h3>
|
|
787
|
+
<p style="color: #666;">+1 (555) 123-4567</p>
|
|
788
|
+
</div>
|
|
789
|
+
<div style="background: #f8f9fa; padding: 30px; border-radius: 12px;">
|
|
790
|
+
<h3 style="color: #333; margin-bottom: 15px; display: flex; align-items: center; gap: 10px;">
|
|
791
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#11998e" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
|
|
792
|
+
Email
|
|
793
|
+
</h3>
|
|
794
|
+
<p style="color: #666;">support@mystore.com</p>
|
|
795
|
+
</div>
|
|
796
|
+
</div>
|
|
797
|
+
</div>
|
|
798
|
+
</div>
|
|
799
|
+
</div>
|
|
800
|
+
`,
|
|
801
|
+
content: 'Contact Us - Get in Touch'
|
|
802
|
+
}
|
|
803
|
+
};
|
|
804
|
+
|
|
805
|
+
const page = landingPages[handle];
|
|
806
|
+
if (page) {
|
|
807
|
+
res.json(page);
|
|
808
|
+
} else {
|
|
809
|
+
res.json({
|
|
810
|
+
id: `landing-${handle}`,
|
|
811
|
+
slug: handle,
|
|
812
|
+
title: handle.charAt(0).toUpperCase() + handle.slice(1).replace(/-/g, ' '),
|
|
813
|
+
htmlContent: `<div class="landing-page"><div class="container"><h1>${handle.charAt(0).toUpperCase() + handle.slice(1).replace(/-/g, ' ')}</h1><p>Landing page content coming soon.</p></div></div>`,
|
|
814
|
+
content: `${handle} page`
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
// Pages API - mock pages for about, contact, etc.
|
|
820
|
+
this.app.get('/webstoreapi/pages/:handle', (req, res) => {
|
|
821
|
+
const handle = req.params.handle;
|
|
822
|
+
const pages = {
|
|
823
|
+
'about': {
|
|
824
|
+
id: 'page-about',
|
|
825
|
+
title: 'About Us',
|
|
826
|
+
handle: 'about',
|
|
827
|
+
content: `
|
|
828
|
+
<h2>Welcome to Our Store</h2>
|
|
829
|
+
<p>We are a passionate team dedicated to bringing you the best products at competitive prices. Founded in 2020, we've grown from a small startup to a trusted online retailer serving thousands of happy customers.</p>
|
|
830
|
+
<h3>Our Mission</h3>
|
|
831
|
+
<p>To provide exceptional shopping experiences with quality products, fast shipping, and outstanding customer service.</p>
|
|
832
|
+
<h3>Our Values</h3>
|
|
833
|
+
<ul>
|
|
834
|
+
<li><strong>Quality:</strong> We carefully curate every product in our catalog</li>
|
|
835
|
+
<li><strong>Service:</strong> Your satisfaction is our top priority</li>
|
|
836
|
+
<li><strong>Trust:</strong> Transparent pricing and honest communication</li>
|
|
837
|
+
<li><strong>Innovation:</strong> Constantly improving our offerings</li>
|
|
838
|
+
</ul>
|
|
839
|
+
`,
|
|
840
|
+
metaTitle: 'About Us - My O2VEND Store',
|
|
841
|
+
metaDescription: 'Learn about our company, mission, and values.'
|
|
842
|
+
},
|
|
843
|
+
'contact': {
|
|
844
|
+
id: 'page-contact',
|
|
845
|
+
title: 'Contact Us',
|
|
846
|
+
handle: 'contact',
|
|
847
|
+
content: `
|
|
848
|
+
<h2>Get in Touch</h2>
|
|
849
|
+
<p>We'd love to hear from you! Whether you have a question about our products, need help with an order, or just want to say hello, we're here to help.</p>
|
|
850
|
+
<h3>Contact Information</h3>
|
|
851
|
+
<ul>
|
|
852
|
+
<li><strong>Email:</strong> support@example.com</li>
|
|
853
|
+
<li><strong>Phone:</strong> +1 (555) 123-4567</li>
|
|
854
|
+
<li><strong>Hours:</strong> Monday - Friday, 9am - 5pm EST</li>
|
|
855
|
+
</ul>
|
|
856
|
+
<h3>Our Address</h3>
|
|
857
|
+
<p>123 Main Street<br>Suite 100<br>New York, NY 10001</p>
|
|
858
|
+
`,
|
|
859
|
+
metaTitle: 'Contact Us - My O2VEND Store',
|
|
860
|
+
metaDescription: 'Get in touch with our support team.'
|
|
861
|
+
},
|
|
862
|
+
'privacy': {
|
|
863
|
+
id: 'page-privacy',
|
|
864
|
+
title: 'Privacy Policy',
|
|
865
|
+
handle: 'privacy',
|
|
866
|
+
content: `
|
|
867
|
+
<h2>Privacy Policy</h2>
|
|
868
|
+
<p>Last updated: January 2026</p>
|
|
869
|
+
<p>Your privacy is important to us. This policy explains how we collect, use, and protect your personal information.</p>
|
|
870
|
+
<h3>Information We Collect</h3>
|
|
871
|
+
<p>We collect information you provide directly to us, such as your name, email address, shipping address, and payment information when you make a purchase.</p>
|
|
872
|
+
<h3>How We Use Your Information</h3>
|
|
873
|
+
<p>We use your information to process orders, communicate with you, improve our services, and send promotional emails (with your consent).</p>
|
|
874
|
+
`,
|
|
875
|
+
metaTitle: 'Privacy Policy - My O2VEND Store',
|
|
876
|
+
metaDescription: 'Our privacy policy and how we protect your data.'
|
|
877
|
+
},
|
|
878
|
+
'terms': {
|
|
879
|
+
id: 'page-terms',
|
|
880
|
+
title: 'Terms of Service',
|
|
881
|
+
handle: 'terms',
|
|
882
|
+
content: `
|
|
883
|
+
<h2>Terms of Service</h2>
|
|
884
|
+
<p>Last updated: January 2026</p>
|
|
885
|
+
<p>By using our website and services, you agree to these terms and conditions.</p>
|
|
886
|
+
<h3>Use of Website</h3>
|
|
887
|
+
<p>You may use our website for lawful purposes only. You agree not to use our site for any fraudulent or illegal activity.</p>
|
|
888
|
+
<h3>Orders and Payments</h3>
|
|
889
|
+
<p>All orders are subject to availability and confirmation. Prices may change without notice.</p>
|
|
890
|
+
`,
|
|
891
|
+
metaTitle: 'Terms of Service - My O2VEND Store',
|
|
892
|
+
metaDescription: 'Our terms and conditions of service.'
|
|
893
|
+
},
|
|
894
|
+
'shipping': {
|
|
895
|
+
id: 'page-shipping',
|
|
896
|
+
title: 'Shipping Information',
|
|
897
|
+
handle: 'shipping',
|
|
898
|
+
content: `
|
|
899
|
+
<h2>Shipping Information</h2>
|
|
900
|
+
<h3>Shipping Methods</h3>
|
|
901
|
+
<ul>
|
|
902
|
+
<li><strong>Standard Shipping:</strong> 5-7 business days - Free on orders over $50</li>
|
|
903
|
+
<li><strong>Express Shipping:</strong> 2-3 business days - $9.99</li>
|
|
904
|
+
<li><strong>Next Day Delivery:</strong> 1 business day - $19.99</li>
|
|
905
|
+
</ul>
|
|
906
|
+
<h3>Shipping Policy</h3>
|
|
907
|
+
<p>Orders placed before 2pm EST are processed the same day. Tracking information is provided via email once your order ships.</p>
|
|
908
|
+
`,
|
|
909
|
+
metaTitle: 'Shipping Information - My O2VEND Store',
|
|
910
|
+
metaDescription: 'Learn about our shipping options and delivery times.'
|
|
911
|
+
},
|
|
912
|
+
'returns': {
|
|
913
|
+
id: 'page-returns',
|
|
914
|
+
title: 'Returns & Refunds',
|
|
915
|
+
handle: 'returns',
|
|
916
|
+
content: `
|
|
917
|
+
<h2>Returns & Refunds</h2>
|
|
918
|
+
<h3>Return Policy</h3>
|
|
919
|
+
<p>We offer a 30-day return policy for most items. Products must be in original condition with tags attached.</p>
|
|
920
|
+
<h3>How to Return</h3>
|
|
921
|
+
<ol>
|
|
922
|
+
<li>Contact our customer service to initiate a return</li>
|
|
923
|
+
<li>Receive a prepaid shipping label via email</li>
|
|
924
|
+
<li>Pack items securely and ship within 14 days</li>
|
|
925
|
+
<li>Refund processed within 5-7 business days of receipt</li>
|
|
926
|
+
</ol>
|
|
927
|
+
`,
|
|
928
|
+
metaTitle: 'Returns & Refunds - My O2VEND Store',
|
|
929
|
+
metaDescription: 'Our return and refund policy.'
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
|
|
933
|
+
const page = pages[handle] || {
|
|
934
|
+
id: `page-${handle}`,
|
|
935
|
+
title: handle.charAt(0).toUpperCase() + handle.slice(1).replace(/-/g, ' '),
|
|
936
|
+
handle: handle,
|
|
937
|
+
content: `<h2>${handle.charAt(0).toUpperCase() + handle.slice(1).replace(/-/g, ' ')}</h2><p>This page content is coming soon.</p>`,
|
|
938
|
+
metaTitle: `${handle} - My O2VEND Store`,
|
|
939
|
+
metaDescription: `Information about ${handle}`
|
|
940
|
+
};
|
|
941
|
+
|
|
942
|
+
res.json(page);
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
// Default route
|
|
946
|
+
this.app.use((req, res) => {
|
|
947
|
+
res.status(404).json({
|
|
948
|
+
error: 'Not found',
|
|
949
|
+
message: `Mock API endpoint not found: ${req.method} ${req.path}`
|
|
950
|
+
});
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
/**
|
|
955
|
+
* Start mock API server
|
|
956
|
+
*/
|
|
957
|
+
async start() {
|
|
958
|
+
return new Promise((resolve, reject) => {
|
|
959
|
+
this.setupRoutes();
|
|
960
|
+
|
|
961
|
+
this.server = this.app.listen(this.port, (error) => {
|
|
962
|
+
if (error) {
|
|
963
|
+
reject(error);
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
console.log(`✅ Mock API server running on http://localhost:${this.port}`);
|
|
968
|
+
resolve(this.server);
|
|
969
|
+
});
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/**
|
|
974
|
+
* Stop mock API server
|
|
975
|
+
*/
|
|
976
|
+
async stop() {
|
|
977
|
+
return new Promise((resolve) => {
|
|
978
|
+
if (this.server) {
|
|
979
|
+
this.server.close(() => {
|
|
980
|
+
resolve();
|
|
981
|
+
});
|
|
982
|
+
} else {
|
|
983
|
+
resolve();
|
|
984
|
+
}
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
module.exports = MockApiServer;
|