@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.
Files changed (116) hide show
  1. package/README.md +425 -0
  2. package/assets/Logo_o2vend.png +0 -0
  3. package/assets/favicon.png +0 -0
  4. package/assets/logo-white.png +0 -0
  5. package/bin/o2vend +42 -0
  6. package/config/widget-map.json +50 -0
  7. package/lib/commands/check.js +201 -0
  8. package/lib/commands/generate.js +33 -0
  9. package/lib/commands/init.js +214 -0
  10. package/lib/commands/optimize.js +216 -0
  11. package/lib/commands/package.js +208 -0
  12. package/lib/commands/serve.js +105 -0
  13. package/lib/commands/validate.js +191 -0
  14. package/lib/lib/api-client.js +357 -0
  15. package/lib/lib/dev-server.js +2618 -0
  16. package/lib/lib/file-watcher.js +80 -0
  17. package/lib/lib/hot-reload.js +106 -0
  18. package/lib/lib/liquid-engine.js +822 -0
  19. package/lib/lib/liquid-filters.js +671 -0
  20. package/lib/lib/mock-api-server.js +989 -0
  21. package/lib/lib/mock-data.js +1468 -0
  22. package/lib/lib/widget-service.js +321 -0
  23. package/package.json +70 -0
  24. package/test-theme/README.md +27 -0
  25. package/test-theme/assets/async-sections.js +446 -0
  26. package/test-theme/assets/cart-drawer.js +463 -0
  27. package/test-theme/assets/cart-manager.js +223 -0
  28. package/test-theme/assets/checkout-price-handler.js +368 -0
  29. package/test-theme/assets/components.css +4629 -0
  30. package/test-theme/assets/delivery-zone.css +299 -0
  31. package/test-theme/assets/delivery-zone.js +396 -0
  32. package/test-theme/assets/logo.png +0 -0
  33. package/test-theme/assets/sections.css +48 -0
  34. package/test-theme/assets/theme.css +3500 -0
  35. package/test-theme/assets/theme.js +3745 -0
  36. package/test-theme/config/settings_data.json +292 -0
  37. package/test-theme/config/settings_schema.json +1050 -0
  38. package/test-theme/layout/theme.liquid +195 -0
  39. package/test-theme/locales/en.default.json +260 -0
  40. package/test-theme/sections/content-fallback.liquid +53 -0
  41. package/test-theme/sections/content.liquid +57 -0
  42. package/test-theme/sections/footer-fallback.liquid +328 -0
  43. package/test-theme/sections/footer.liquid +278 -0
  44. package/test-theme/sections/header-fallback.liquid +1805 -0
  45. package/test-theme/sections/header.liquid +1145 -0
  46. package/test-theme/sections/hero-fallback.liquid +212 -0
  47. package/test-theme/sections/hero.liquid +136 -0
  48. package/test-theme/snippets/account-sidebar.liquid +200 -0
  49. package/test-theme/snippets/add-to-cart-modal.liquid +484 -0
  50. package/test-theme/snippets/breadcrumbs.liquid +134 -0
  51. package/test-theme/snippets/cart-drawer.liquid +467 -0
  52. package/test-theme/snippets/delivery-zone-city-selector.liquid +79 -0
  53. package/test-theme/snippets/delivery-zone-modal.liquid +337 -0
  54. package/test-theme/snippets/delivery-zone-search.liquid +78 -0
  55. package/test-theme/snippets/icon.liquid +105 -0
  56. package/test-theme/snippets/login-modal.liquid +346 -0
  57. package/test-theme/snippets/mega-menu.liquid +812 -0
  58. package/test-theme/snippets/news-thumbnail.liquid +187 -0
  59. package/test-theme/snippets/pagination.liquid +120 -0
  60. package/test-theme/snippets/price.liquid +92 -0
  61. package/test-theme/snippets/product-card-related.liquid +78 -0
  62. package/test-theme/snippets/product-card-simple.liquid +41 -0
  63. package/test-theme/snippets/product-card.liquid +697 -0
  64. package/test-theme/snippets/rating.liquid +85 -0
  65. package/test-theme/snippets/skeleton-collection-grid.liquid +114 -0
  66. package/test-theme/snippets/skeleton-product-card.liquid +124 -0
  67. package/test-theme/snippets/skeleton-product-grid.liquid +34 -0
  68. package/test-theme/snippets/social-sharing.liquid +185 -0
  69. package/test-theme/templates/account/dashboard.liquid +401 -0
  70. package/test-theme/templates/account/loyalty-redemption.liquid +405 -0
  71. package/test-theme/templates/account/loyalty.liquid +588 -0
  72. package/test-theme/templates/account/order-detail.liquid +230 -0
  73. package/test-theme/templates/account/orders.liquid +349 -0
  74. package/test-theme/templates/account/profile.liquid +758 -0
  75. package/test-theme/templates/account/register.liquid +232 -0
  76. package/test-theme/templates/account/return-orders.liquid +348 -0
  77. package/test-theme/templates/account/store-credit.liquid +464 -0
  78. package/test-theme/templates/account/subscriptions.liquid +601 -0
  79. package/test-theme/templates/account/wishlist.liquid +419 -0
  80. package/test-theme/templates/address-book.liquid +1092 -0
  81. package/test-theme/templates/categories.liquid +452 -0
  82. package/test-theme/templates/checkout.liquid +4511 -0
  83. package/test-theme/templates/error.liquid +384 -0
  84. package/test-theme/templates/index.liquid +11 -0
  85. package/test-theme/templates/login.liquid +185 -0
  86. package/test-theme/templates/order-confirmation.liquid +720 -0
  87. package/test-theme/templates/page.liquid +297 -0
  88. package/test-theme/templates/product-detail.liquid +4363 -0
  89. package/test-theme/templates/products.liquid +518 -0
  90. package/test-theme/templates/search.liquid +922 -0
  91. package/test-theme/theme.json.example +19 -0
  92. package/test-theme/widgets/brand-carousel.liquid +676 -0
  93. package/test-theme/widgets/brand.liquid +245 -0
  94. package/test-theme/widgets/carousel.liquid +843 -0
  95. package/test-theme/widgets/category-list-carousel.liquid +656 -0
  96. package/test-theme/widgets/category-list.liquid +340 -0
  97. package/test-theme/widgets/category.liquid +475 -0
  98. package/test-theme/widgets/discount-time.liquid +176 -0
  99. package/test-theme/widgets/footer-menu.liquid +695 -0
  100. package/test-theme/widgets/footer.liquid +179 -0
  101. package/test-theme/widgets/gallery.liquid +271 -0
  102. package/test-theme/widgets/header-menu.liquid +932 -0
  103. package/test-theme/widgets/header.liquid +159 -0
  104. package/test-theme/widgets/html.liquid +214 -0
  105. package/test-theme/widgets/news.liquid +217 -0
  106. package/test-theme/widgets/product-canvas.liquid +235 -0
  107. package/test-theme/widgets/product-carousel.liquid +502 -0
  108. package/test-theme/widgets/product.liquid +45 -0
  109. package/test-theme/widgets/recently-viewed.liquid +26 -0
  110. package/test-theme/widgets/shared/product-grid.liquid +339 -0
  111. package/test-theme/widgets/simple-product.liquid +42 -0
  112. package/test-theme/widgets/single-product.liquid +610 -0
  113. package/test-theme/widgets/spacebar-carousel.liquid +663 -0
  114. package/test-theme/widgets/spacebar.liquid +279 -0
  115. package/test-theme/widgets/splash.liquid +378 -0
  116. 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;