@duongthiu/onex-core 0.1.0 → 0.1.1

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 (190) hide show
  1. package/README.md +44 -65
  2. package/THEME_API.md +36 -33
  3. package/dist/api.js +14 -0
  4. package/dist/api.js.map +1 -0
  5. package/dist/api.mjs +5 -0
  6. package/dist/api.mjs.map +1 -0
  7. package/dist/auth.js +23 -0
  8. package/dist/auth.js.map +1 -0
  9. package/dist/auth.mjs +6 -0
  10. package/dist/auth.mjs.map +1 -0
  11. package/dist/blog.js +22 -0
  12. package/dist/blog.js.map +1 -0
  13. package/dist/blog.mjs +5 -0
  14. package/dist/blog.mjs.map +1 -0
  15. package/dist/cart.js +27 -0
  16. package/dist/cart.js.map +1 -0
  17. package/dist/cart.mjs +6 -0
  18. package/dist/cart.mjs.map +1 -0
  19. package/dist/chunk-2CSWHI3L.js +210 -0
  20. package/dist/chunk-2CSWHI3L.js.map +1 -0
  21. package/dist/chunk-2FGHIDUV.js +99 -0
  22. package/dist/chunk-2FGHIDUV.js.map +1 -0
  23. package/dist/chunk-2NMEKWO5.js +40 -0
  24. package/dist/chunk-2NMEKWO5.js.map +1 -0
  25. package/dist/chunk-43BVHGDT.mjs +131 -0
  26. package/dist/chunk-43BVHGDT.mjs.map +1 -0
  27. package/dist/chunk-4JVQJI3I.mjs +290 -0
  28. package/dist/chunk-4JVQJI3I.mjs.map +1 -0
  29. package/dist/chunk-5N2EKK5O.js +9550 -0
  30. package/dist/chunk-5N2EKK5O.js.map +1 -0
  31. package/dist/chunk-5T6NDVSN.js +76 -0
  32. package/dist/chunk-5T6NDVSN.js.map +1 -0
  33. package/dist/chunk-73AINNCI.mjs +201 -0
  34. package/dist/chunk-73AINNCI.mjs.map +1 -0
  35. package/dist/chunk-AIXBDAVP.mjs +61 -0
  36. package/dist/chunk-AIXBDAVP.mjs.map +1 -0
  37. package/dist/chunk-ALYN5HAC.js +210 -0
  38. package/dist/chunk-ALYN5HAC.js.map +1 -0
  39. package/dist/chunk-AREMJR3Q.js +36 -0
  40. package/dist/chunk-AREMJR3Q.js.map +1 -0
  41. package/dist/chunk-DXAI6XOI.mjs +33 -0
  42. package/dist/chunk-DXAI6XOI.mjs.map +1 -0
  43. package/dist/chunk-F5TFNWFJ.mjs +257 -0
  44. package/dist/chunk-F5TFNWFJ.mjs.map +1 -0
  45. package/dist/chunk-FQ7FWUZN.js +265 -0
  46. package/dist/chunk-FQ7FWUZN.js.map +1 -0
  47. package/dist/{chunk-3SZX6LHT.js → chunk-J27VGXJH.js} +2 -24
  48. package/dist/chunk-J27VGXJH.js.map +1 -0
  49. package/dist/chunk-JZ46LLTZ.js +121 -0
  50. package/dist/chunk-JZ46LLTZ.js.map +1 -0
  51. package/dist/chunk-K24UHN6G.mjs +76 -0
  52. package/dist/chunk-K24UHN6G.mjs.map +1 -0
  53. package/dist/chunk-KCQGGU5R.mjs +344 -0
  54. package/dist/chunk-KCQGGU5R.mjs.map +1 -0
  55. package/dist/chunk-MT22NYKT.mjs +117 -0
  56. package/dist/chunk-MT22NYKT.mjs.map +1 -0
  57. package/dist/chunk-NDD472IZ.js +85 -0
  58. package/dist/chunk-NDD472IZ.js.map +1 -0
  59. package/dist/chunk-NHOIGGYU.mjs +833 -0
  60. package/dist/chunk-NHOIGGYU.mjs.map +1 -0
  61. package/dist/chunk-O3XR7TW3.mjs +12 -0
  62. package/dist/chunk-O3XR7TW3.mjs.map +1 -0
  63. package/dist/chunk-OAPYSC2X.mjs +206 -0
  64. package/dist/chunk-OAPYSC2X.mjs.map +1 -0
  65. package/dist/{chunk-XE4EOKS4.mjs → chunk-ONJREDYY.mjs} +3 -23
  66. package/dist/chunk-ONJREDYY.mjs.map +1 -0
  67. package/dist/chunk-OVT2LUAM.js +197 -0
  68. package/dist/chunk-OVT2LUAM.js.map +1 -0
  69. package/dist/chunk-OWNGNGKL.js +331 -0
  70. package/dist/chunk-OWNGNGKL.js.map +1 -0
  71. package/dist/chunk-P7SXNZSV.js +298 -0
  72. package/dist/chunk-P7SXNZSV.js.map +1 -0
  73. package/dist/chunk-PFJOL3HI.mjs +71 -0
  74. package/dist/chunk-PFJOL3HI.mjs.map +1 -0
  75. package/dist/chunk-PPULMWJ6.js +295 -0
  76. package/dist/chunk-PPULMWJ6.js.map +1 -0
  77. package/dist/chunk-RGIVKACG.js +359 -0
  78. package/dist/chunk-RGIVKACG.js.map +1 -0
  79. package/dist/chunk-RPP5K2LP.js +870 -0
  80. package/dist/chunk-RPP5K2LP.js.map +1 -0
  81. package/dist/{chunk-7EON6Q4L.mjs → chunk-RUCHWUD7.mjs} +7651 -6370
  82. package/dist/chunk-RUCHWUD7.mjs.map +1 -0
  83. package/dist/chunk-SEVUIX4H.js +137 -0
  84. package/dist/chunk-SEVUIX4H.js.map +1 -0
  85. package/dist/chunk-SK2FSHFM.mjs +208 -0
  86. package/dist/chunk-SK2FSHFM.mjs.map +1 -0
  87. package/dist/chunk-T6EJ2GAV.mjs +294 -0
  88. package/dist/chunk-T6EJ2GAV.mjs.map +1 -0
  89. package/dist/chunk-ULBDOFZI.mjs +302 -0
  90. package/dist/chunk-ULBDOFZI.mjs.map +1 -0
  91. package/dist/chunk-V3JIELNV.js +241 -0
  92. package/dist/chunk-V3JIELNV.js.map +1 -0
  93. package/dist/chunk-V5E2KWMA.mjs +238 -0
  94. package/dist/chunk-V5E2KWMA.mjs.map +1 -0
  95. package/dist/chunk-VJA3ER6A.js +14 -0
  96. package/dist/chunk-VJA3ER6A.js.map +1 -0
  97. package/dist/chunk-VLI7ULX5.js +66 -0
  98. package/dist/chunk-VLI7ULX5.js.map +1 -0
  99. package/dist/chunk-WFGS5OFH.mjs +189 -0
  100. package/dist/chunk-WFGS5OFH.mjs.map +1 -0
  101. package/dist/chunk-WVC5GP24.mjs +96 -0
  102. package/dist/chunk-WVC5GP24.mjs.map +1 -0
  103. package/dist/chunk-YOSPWY5K.mjs +36 -0
  104. package/dist/chunk-YOSPWY5K.mjs.map +1 -0
  105. package/dist/chunk-ZFFXXLX7.js +205 -0
  106. package/dist/chunk-ZFFXXLX7.js.map +1 -0
  107. package/dist/client.js +512 -249
  108. package/dist/client.mjs +21 -2
  109. package/dist/components.js +393 -0
  110. package/dist/components.js.map +1 -0
  111. package/dist/components.mjs +8 -0
  112. package/dist/components.mjs.map +1 -0
  113. package/dist/config.js +17 -0
  114. package/dist/config.js.map +1 -0
  115. package/dist/config.mjs +4 -0
  116. package/dist/config.mjs.map +1 -0
  117. package/dist/contact.js +22 -0
  118. package/dist/contact.js.map +1 -0
  119. package/dist/contact.mjs +5 -0
  120. package/dist/contact.mjs.map +1 -0
  121. package/dist/contexts.js +51 -0
  122. package/dist/contexts.js.map +1 -0
  123. package/dist/contexts.mjs +6 -0
  124. package/dist/contexts.mjs.map +1 -0
  125. package/dist/finance.js +26 -0
  126. package/dist/finance.js.map +1 -0
  127. package/dist/finance.mjs +5 -0
  128. package/dist/finance.mjs.map +1 -0
  129. package/dist/icons.js +15 -0
  130. package/dist/icons.js.map +1 -0
  131. package/dist/icons.mjs +4 -0
  132. package/dist/icons.mjs.map +1 -0
  133. package/dist/index.js +512 -249
  134. package/dist/index.js.map +1 -1
  135. package/dist/index.mjs +21 -2
  136. package/dist/index.mjs.map +1 -1
  137. package/dist/internal.js +1437 -0
  138. package/dist/internal.js.map +1 -0
  139. package/dist/internal.mjs +1404 -0
  140. package/dist/internal.mjs.map +1 -0
  141. package/dist/motion.js +51 -0
  142. package/dist/motion.js.map +1 -0
  143. package/dist/motion.mjs +38 -0
  144. package/dist/motion.mjs.map +1 -0
  145. package/dist/orders.js +22 -0
  146. package/dist/orders.js.map +1 -0
  147. package/dist/orders.mjs +5 -0
  148. package/dist/orders.mjs.map +1 -0
  149. package/dist/products.js +27 -0
  150. package/dist/products.js.map +1 -0
  151. package/dist/products.mjs +6 -0
  152. package/dist/products.mjs.map +1 -0
  153. package/dist/registry.js +44 -0
  154. package/dist/registry.js.map +1 -0
  155. package/dist/registry.mjs +7 -0
  156. package/dist/registry.mjs.map +1 -0
  157. package/dist/renderers.js +51 -0
  158. package/dist/renderers.js.map +1 -0
  159. package/dist/renderers.mjs +6 -0
  160. package/dist/renderers.mjs.map +1 -0
  161. package/dist/server.js +11 -189
  162. package/dist/server.js.map +1 -1
  163. package/dist/server.mjs +3 -186
  164. package/dist/server.mjs.map +1 -1
  165. package/dist/types.js +37 -0
  166. package/dist/types.js.map +1 -0
  167. package/dist/types.mjs +4 -0
  168. package/dist/types.mjs.map +1 -0
  169. package/dist/utils.js +160 -0
  170. package/dist/utils.js.map +1 -0
  171. package/dist/utils.mjs +7 -0
  172. package/dist/utils.mjs.map +1 -0
  173. package/dist/wrappers.js +104 -0
  174. package/dist/wrappers.js.map +1 -0
  175. package/dist/wrappers.mjs +96 -0
  176. package/dist/wrappers.mjs.map +1 -0
  177. package/package.json +112 -9
  178. package/dist/api-vuL1Eg5L.d.mts +0 -2961
  179. package/dist/api-vuL1Eg5L.d.ts +0 -2961
  180. package/dist/chunk-3SZX6LHT.js.map +0 -1
  181. package/dist/chunk-7EON6Q4L.mjs.map +0 -1
  182. package/dist/chunk-WDY773GJ.js +0 -8308
  183. package/dist/chunk-WDY773GJ.js.map +0 -1
  184. package/dist/chunk-XE4EOKS4.mjs.map +0 -1
  185. package/dist/client.d.mts +0 -1461
  186. package/dist/client.d.ts +0 -1461
  187. package/dist/index.d.mts +0 -125
  188. package/dist/index.d.ts +0 -125
  189. package/dist/server.d.mts +0 -70
  190. package/dist/server.d.ts +0 -70
@@ -0,0 +1,197 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var chunkJ27VGXJH_js = require('./chunk-J27VGXJH.js');
5
+ require('server-only');
6
+ var fs = require('fs/promises');
7
+ var path = require('path');
8
+
9
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
10
+
11
+ var fs__default = /*#__PURE__*/_interopDefault(fs);
12
+ var path__default = /*#__PURE__*/_interopDefault(path);
13
+
14
+ function toCamelCase(str) {
15
+ return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
16
+ }
17
+ var ThemeRegistryManager = class {
18
+ constructor(debug = process.env.NODE_ENV === "development") {
19
+ this.registries = /* @__PURE__ */ new Map();
20
+ this.initialized = /* @__PURE__ */ new Set();
21
+ this.debug = debug;
22
+ }
23
+ /**
24
+ * Auto-discover sections from theme directory
25
+ * Reads filesystem to find all section directories
26
+ */
27
+ async discoverThemeSections(themeId) {
28
+ const sectionsPath = path__default.default.join(
29
+ process.cwd(),
30
+ `src/themes/${themeId}/sections`
31
+ );
32
+ try {
33
+ const entries = await fs__default.default.readdir(sectionsPath, { withFileTypes: true });
34
+ const sectionNames = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
35
+ if (this.debug) {
36
+ }
37
+ return sectionNames;
38
+ } catch (error) {
39
+ if (this.debug) {
40
+ console.warn(
41
+ `[ThemeRegistry] No sections found for theme "${themeId}" at ${sectionsPath}`
42
+ );
43
+ }
44
+ return [];
45
+ }
46
+ }
47
+ /**
48
+ * Initialize a theme's sections
49
+ * Auto-discovers and registers all sections for a theme
50
+ */
51
+ async initializeTheme(themeId) {
52
+ var _a, _b;
53
+ if (this.initialized.has(themeId)) {
54
+ if (this.debug) ;
55
+ return;
56
+ }
57
+ const registry = this.getThemeRegistry(themeId);
58
+ const sectionNames = await this.discoverThemeSections(themeId);
59
+ if (sectionNames.length === 0) {
60
+ console.warn(`[ThemeRegistry] No sections found for theme "${themeId}"`);
61
+ this.initialized.add(themeId);
62
+ return;
63
+ }
64
+ let _successCount = 0;
65
+ let _failureCount = 0;
66
+ for (const sectionName of sectionNames) {
67
+ try {
68
+ const sectionModule = await import(`@/themes/${themeId}/sections/${sectionName}`);
69
+ const camelName = toCamelCase(sectionName);
70
+ const schema = sectionModule[`${camelName}Schema`] || ((_a = sectionModule.default) == null ? void 0 : _a.schema) || sectionModule.schema;
71
+ const components = sectionModule[`${camelName}Components`] || ((_b = sectionModule.default) == null ? void 0 : _b.components) || sectionModule.components;
72
+ if (!schema || !components) {
73
+ console.error(
74
+ `[ThemeRegistry] Section "${sectionName}" in theme "${themeId}" missing schema or components export.`,
75
+ `Expected: "${camelName}Schema" and "${camelName}Components"`
76
+ );
77
+ _failureCount++;
78
+ continue;
79
+ }
80
+ const defaultComponent = components.default || Object.values(components)[0];
81
+ if (!defaultComponent) {
82
+ console.error(
83
+ `[ThemeRegistry] Section "${sectionName}" has no default component`
84
+ );
85
+ _failureCount++;
86
+ continue;
87
+ }
88
+ const templates = Object.entries(components).map(
89
+ ([templateId, component]) => ({
90
+ definition: schema.templates.find((t) => t.id === templateId) || {
91
+ id: templateId,
92
+ name: templateId,
93
+ description: `${templateId} template`,
94
+ isDefault: templateId === "default",
95
+ settings: schema.settings,
96
+ defaults: schema.defaults
97
+ },
98
+ component
99
+ })
100
+ );
101
+ registry.register(schema.type, schema, defaultComponent, {
102
+ templates,
103
+ category: schema.category,
104
+ tags: schema.tags
105
+ });
106
+ _successCount++;
107
+ if (this.debug) {
108
+ }
109
+ } catch (error) {
110
+ console.error(
111
+ `[ThemeRegistry] Failed to load section "${themeId}/${sectionName}":`,
112
+ error
113
+ );
114
+ _failureCount++;
115
+ }
116
+ }
117
+ this.initialized.add(themeId);
118
+ }
119
+ /**
120
+ * Get or create theme-specific registry
121
+ */
122
+ getThemeRegistry(themeId) {
123
+ if (!this.registries.has(themeId)) {
124
+ const registry = chunkJ27VGXJH_js.createSectionRegistry({
125
+ debug: this.debug,
126
+ allowOverride: this.debug
127
+ });
128
+ this.registries.set(themeId, registry);
129
+ if (this.debug) ;
130
+ }
131
+ return this.registries.get(themeId);
132
+ }
133
+ /**
134
+ * Check if theme has been initialized
135
+ */
136
+ isThemeInitialized(themeId) {
137
+ return this.initialized.has(themeId);
138
+ }
139
+ /**
140
+ * Resolve a section component for a specific theme
141
+ * Returns null if not found (no fallback - strict validation)
142
+ */
143
+ resolveSection(themeId, sectionType, templateId = "default") {
144
+ const registry = this.getThemeRegistry(themeId);
145
+ return registry.getTemplateComponent(sectionType, templateId) || null;
146
+ }
147
+ /**
148
+ * Get section schema for a specific theme
149
+ */
150
+ getSectionSchema(themeId, sectionType) {
151
+ const registry = this.getThemeRegistry(themeId);
152
+ return registry.getSchema(sectionType) || null;
153
+ }
154
+ /**
155
+ * Get all available themes
156
+ */
157
+ getAvailableThemes() {
158
+ return Array.from(this.registries.keys());
159
+ }
160
+ /**
161
+ * Get all section types for a theme
162
+ */
163
+ getThemeSections(themeId) {
164
+ const registry = this.registries.get(themeId);
165
+ if (!registry) {
166
+ return [];
167
+ }
168
+ const all = registry.getAll();
169
+ return Array.from(all.keys());
170
+ }
171
+ /**
172
+ * Clear a specific theme's registry
173
+ */
174
+ clearTheme(themeId) {
175
+ const registry = this.registries.get(themeId);
176
+ if (registry) {
177
+ registry.clear();
178
+ this.initialized.delete(themeId);
179
+ if (this.debug) ;
180
+ }
181
+ }
182
+ /**
183
+ * Clear all theme registries
184
+ */
185
+ clearAll() {
186
+ this.registries.forEach((registry) => registry.clear());
187
+ this.registries.clear();
188
+ this.initialized.clear();
189
+ if (this.debug) ;
190
+ }
191
+ };
192
+ var themeRegistryManager = new ThemeRegistryManager();
193
+
194
+ exports.ThemeRegistryManager = ThemeRegistryManager;
195
+ exports.themeRegistryManager = themeRegistryManager;
196
+ //# sourceMappingURL=chunk-OVT2LUAM.js.map
197
+ //# sourceMappingURL=chunk-OVT2LUAM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/registry/theme-registry-manager.ts"],"names":["path","fs","createSectionRegistry"],"mappings":";;;;;;;;;;;;AAiBA,SAAS,YAAY,GAAA,EAAqB;AACxC,EAAA,OAAO,GAAA,CAAI,QAAQ,WAAA,EAAa,CAAC,MAAM,CAAA,CAAE,CAAC,CAAA,CAAE,WAAA,EAAa,CAAA;AAC3D;AAMO,IAAM,uBAAN,MAA2B;AAAA,EAKhC,WAAA,CAAY,KAAA,GAAiB,OAAA,CAAQ,GAAA,CAAI,aAAa,aAAA,EAAe;AACnE,IAAA,IAAA,CAAK,UAAA,uBAAiB,GAAA,EAAI;AAC1B,IAAA,IAAA,CAAK,WAAA,uBAAkB,GAAA,EAAI;AAC3B,IAAA,IAAA,CAAK,KAAA,GAAQ,KAAA;AAAA,EACf;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,sBAAsB,OAAA,EAAoC;AAC9D,IAAA,MAAM,eAAeA,qBAAA,CAAK,IAAA;AAAA,MACxB,QAAQ,GAAA,EAAI;AAAA,MACZ,cAAc,OAAO,CAAA,SAAA;AAAA,KACvB;AAEA,IAAA,IAAI;AACF,MAAA,MAAM,OAAA,GAAU,MAAMC,mBAAA,CAAG,OAAA,CAAQ,cAAc,EAAE,aAAA,EAAe,MAAM,CAAA;AAGtE,MAAA,MAAM,YAAA,GAAe,OAAA,CAClB,MAAA,CAAO,CAAC,KAAA,KAAU,KAAA,CAAM,WAAA,EAAa,CAAA,CACrC,GAAA,CAAI,CAAC,KAAA,KAAU,MAAM,IAAI,CAAA;AAE5B,MAAA,IAAI,KAAK,KAAA,EAAO;AAAA,MAChB;AAEA,MAAA,OAAO,YAAA;AAAA,IACT,SAAS,KAAA,EAAO;AACd,MAAA,IAAI,KAAK,KAAA,EAAO;AACd,QAAA,OAAA,CAAQ,IAAA;AAAA,UACN,CAAA,6CAAA,EAAgD,OAAO,CAAA,KAAA,EAAQ,YAAY,CAAA;AAAA,SAC7E;AAAA,MACF;AACA,MAAA,OAAO,EAAC;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,gBAAgB,OAAA,EAAgC;AAxExD,IAAA,IAAA,EAAA,EAAA,EAAA;AA0EI,IAAA,IAAI,IAAA,CAAK,WAAA,CAAY,GAAA,CAAI,OAAO,CAAA,EAAG;AACjC,MAAA,IAAI,KAAK,KAAA,EAAO;AAEhB,MAAA;AAAA,IACF;AAGA,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,gBAAA,CAAiB,OAAO,CAAA;AAG9C,IAAA,MAAM,YAAA,GAAe,MAAM,IAAA,CAAK,qBAAA,CAAsB,OAAO,CAAA;AAE7D,IAAA,IAAI,YAAA,CAAa,WAAW,CAAA,EAAG;AAC7B,MAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,6CAAA,EAAgD,OAAO,CAAA,CAAA,CAAG,CAAA;AACvE,MAAA,IAAA,CAAK,WAAA,CAAY,IAAI,OAAO,CAAA;AAC5B,MAAA;AAAA,IACF;AAIA,IAAA,IAAI,aAAA,GAAgB,CAAA;AAEpB,IAAA,IAAI,aAAA,GAAgB,CAAA;AAEpB,IAAA,KAAA,MAAW,eAAe,YAAA,EAAc;AACtC,MAAA,IAAI;AAEF,QAAA,MAAM,gBAAgB,MAAM,OAC1B,CAAA,SAAA,EAAY,OAAO,aAAa,WAAW,CAAA,CAAA,CAAA;AAK7C,QAAA,MAAM,SAAA,GAAY,YAAY,WAAW,CAAA;AACzC,QAAA,MAAM,MAAA,GACJ,aAAA,CAAc,CAAA,EAAG,SAAS,CAAA,MAAA,CAAQ,OAClC,EAAA,GAAA,aAAA,CAAc,OAAA,KAAd,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAuB,MAAA,CAAA,IACvB,aAAA,CAAc,MAAA;AAEhB,QAAA,MAAM,UAAA,GAGF,aAAA,CAAc,CAAA,EAAG,SAAS,CAAA,UAAA,CAAY,OAC1C,EAAA,GAAA,aAAA,CAAc,OAAA,KAAd,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAuB,UAAA,CAAA,IACvB,aAAA,CAAc,UAAA;AAEd,QAAA,IAAI,CAAC,MAAA,IAAU,CAAC,UAAA,EAAY;AAC1B,UAAA,OAAA,CAAQ,KAAA;AAAA,YACN,CAAA,yBAAA,EAA4B,WAAW,CAAA,YAAA,EAAe,OAAO,CAAA,sCAAA,CAAA;AAAA,YAC7D,CAAA,WAAA,EAAc,SAAS,CAAA,aAAA,EAAgB,SAAS,CAAA,WAAA;AAAA,WAClD;AACA,UAAA,aAAA,EAAA;AACA,UAAA;AAAA,QACF;AAIA,QAAA,MAAM,mBACJ,UAAA,CAAW,OAAA,IAAW,OAAO,MAAA,CAAO,UAAU,EAAE,CAAC,CAAA;AAEnD,QAAA,IAAI,CAAC,gBAAA,EAAkB;AACrB,UAAA,OAAA,CAAQ,KAAA;AAAA,YACN,4BAA4B,WAAW,CAAA,0BAAA;AAAA,WACzC;AACA,UAAA,aAAA,EAAA;AACA,UAAA;AAAA,QACF;AAGA,QAAA,MAAM,SAAA,GAAY,MAAA,CAAO,OAAA,CAAQ,UAAU,CAAA,CAAE,GAAA;AAAA,UAC3C,CAAC,CAAC,UAAA,EAAY,SAAS,CAAA,MAAO;AAAA,YAC5B,UAAA,EAAY,OAAO,SAAA,CAAU,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,EAAA,KAAO,UAAU,CAAA,IAAK;AAAA,cAC/D,EAAA,EAAI,UAAA;AAAA,cACJ,IAAA,EAAM,UAAA;AAAA,cACN,WAAA,EAAa,GAAG,UAAU,CAAA,SAAA,CAAA;AAAA,cAC1B,WAAW,UAAA,KAAe,SAAA;AAAA,cAC1B,UAAU,MAAA,CAAO,QAAA;AAAA,cACjB,UAAU,MAAA,CAAO;AAAA,aACnB;AAAA,YACA;AAAA,WACF;AAAA,SACF;AAEA,QAAA,QAAA,CAAS,QAAA,CAAS,MAAA,CAAO,IAAA,EAAM,MAAA,EAAQ,gBAAA,EAAkB;AAAA,UACvD,SAAA;AAAA,UACA,UAAU,MAAA,CAAO,QAAA;AAAA,UACjB,MAAM,MAAA,CAAO;AAAA,SACd,CAAA;AAED,QAAA,aAAA,EAAA;AAEA,QAAA,IAAI,KAAK,KAAA,EAAO;AAAA,QAChB;AAAA,MACF,SAAS,KAAA,EAAO;AACd,QAAA,OAAA,CAAQ,KAAA;AAAA,UACN,CAAA,wCAAA,EAA2C,OAAO,CAAA,CAAA,EAAI,WAAW,CAAA,EAAA,CAAA;AAAA,UACjE;AAAA,SACF;AACA,QAAA,aAAA,EAAA;AAAA,MACF;AAAA,IACF;AAGA,IAAA,IAAA,CAAK,WAAA,CAAY,IAAI,OAAO,CAAA;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiB,OAAA,EAAkC;AACjD,IAAA,IAAI,CAAC,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,OAAO,CAAA,EAAG;AAEjC,MAAA,MAAM,WAAWC,sCAAA,CAAsB;AAAA,QACrC,OAAO,IAAA,CAAK,KAAA;AAAA,QACZ,eAAe,IAAA,CAAK;AAAA,OACrB,CAAA;AAED,MAAA,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,OAAA,EAAS,QAAQ,CAAA;AAErC,MAAA,IAAI,KAAK,KAAA,EAAO;AAChB,IACF;AAEA,IAAA,OAAO,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,OAAO,CAAA;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAmB,OAAA,EAA0B;AAC3C,IAAA,OAAO,IAAA,CAAK,WAAA,CAAY,GAAA,CAAI,OAAO,CAAA;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,cAAA,CACE,OAAA,EACA,WAAA,EACA,UAAA,GAAqB,SAAA,EAC8B;AACnD,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,gBAAA,CAAiB,OAAO,CAAA;AAC9C,IAAA,OAAO,QAAA,CAAS,oBAAA,CAAqB,WAAA,EAAa,UAAU,CAAA,IAAK,IAAA;AAAA,EACnE;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAA,CAAiB,SAAiB,WAAA,EAA2C;AAC3E,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,gBAAA,CAAiB,OAAO,CAAA;AAC9C,IAAA,OAAO,QAAA,CAAS,SAAA,CAAU,WAAW,CAAA,IAAK,IAAA;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAA,GAA+B;AAC7B,IAAA,OAAO,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,UAAA,CAAW,MAAM,CAAA;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiB,OAAA,EAA2B;AAC1C,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,OAAO,CAAA;AAC5C,IAAA,IAAI,CAAC,QAAA,EAAU;AACb,MAAA,OAAO,EAAC;AAAA,IACV;AAEA,IAAA,MAAM,GAAA,GAAM,SAAS,MAAA,EAAO;AAC5B,IAAA,OAAO,KAAA,CAAM,IAAA,CAAK,GAAA,CAAI,IAAA,EAAM,CAAA;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,OAAA,EAAuB;AAChC,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,OAAO,CAAA;AAC5C,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,QAAA,CAAS,KAAA,EAAM;AACf,MAAA,IAAA,CAAK,WAAA,CAAY,OAAO,OAAO,CAAA;AAE/B,MAAA,IAAI,KAAK,KAAA,EAAO;AAChB,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,QAAA,GAAiB;AACf,IAAA,IAAA,CAAK,WAAW,OAAA,CAAQ,CAAC,QAAA,KAAa,QAAA,CAAS,OAAO,CAAA;AACtD,IAAA,IAAA,CAAK,WAAW,KAAA,EAAM;AACtB,IAAA,IAAA,CAAK,YAAY,KAAA,EAAM;AAEvB,IAAA,IAAI,KAAK,KAAA,EAAO;AAChB,EACF;AACF;AAKO,IAAM,oBAAA,GAAuB,IAAI,oBAAA","file":"chunk-OVT2LUAM.js","sourcesContent":["/**\n * Theme Registry Manager\n * Manages theme-specific section registries with auto-discovery\n *\n * SERVER-ONLY: Uses filesystem operations, cannot run in browser\n */\n\nimport \"server-only\";\nimport fs from \"fs/promises\";\nimport path from \"path\";\nimport type { SectionRegistry } from \"../types/registry\";\nimport type { SectionSchema, SectionComponentProps } from \"../types/section\";\nimport { createSectionRegistry } from \"./section-registry\";\n\n/**\n * Convert kebab-case to camelCase\n */\nfunction toCamelCase(str: string): string {\n return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());\n}\n\n/**\n * Theme Registry Manager\n * Auto-discovers and manages theme-specific section registries\n */\nexport class ThemeRegistryManager {\n private registries: Map<string, SectionRegistry>;\n private initialized: Set<string>;\n private debug: boolean;\n\n constructor(debug: boolean = process.env.NODE_ENV === \"development\") {\n this.registries = new Map();\n this.initialized = new Set();\n this.debug = debug;\n }\n\n /**\n * Auto-discover sections from theme directory\n * Reads filesystem to find all section directories\n */\n async discoverThemeSections(themeId: string): Promise<string[]> {\n const sectionsPath = path.join(\n process.cwd(),\n `src/themes/${themeId}/sections`\n );\n\n try {\n const entries = await fs.readdir(sectionsPath, { withFileTypes: true });\n\n // Return only directories (each directory = one section)\n const sectionNames = entries\n .filter((entry) => entry.isDirectory())\n .map((entry) => entry.name);\n\n if (this.debug) {\n }\n\n return sectionNames;\n } catch (error) {\n if (this.debug) {\n console.warn(\n `[ThemeRegistry] No sections found for theme \"${themeId}\" at ${sectionsPath}`\n );\n }\n return [];\n }\n }\n\n /**\n * Initialize a theme's sections\n * Auto-discovers and registers all sections for a theme\n */\n async initializeTheme(themeId: string): Promise<void> {\n // Check if already initialized\n if (this.initialized.has(themeId)) {\n if (this.debug) {\n }\n return;\n }\n\n // Get or create registry for this theme\n const registry = this.getThemeRegistry(themeId);\n\n // Discover sections\n const sectionNames = await this.discoverThemeSections(themeId);\n\n if (sectionNames.length === 0) {\n console.warn(`[ThemeRegistry] No sections found for theme \"${themeId}\"`);\n this.initialized.add(themeId);\n return;\n }\n\n // Import and register each section\n\n let _successCount = 0;\n\n let _failureCount = 0;\n\n for (const sectionName of sectionNames) {\n try {\n // Dynamic import of section module\n const sectionModule = await import(\n `@/themes/${themeId}/sections/${sectionName}`\n );\n\n // Extract schema and components from module\n // Try different naming conventions\n const camelName = toCamelCase(sectionName);\n const schema: SectionSchema =\n sectionModule[`${camelName}Schema`] ||\n sectionModule.default?.schema ||\n sectionModule.schema;\n\n const components: Record<\n string,\n React.ComponentType<SectionComponentProps>\n > = sectionModule[`${camelName}Components`] ||\n sectionModule.default?.components ||\n sectionModule.components;\n\n if (!schema || !components) {\n console.error(\n `[ThemeRegistry] Section \"${sectionName}\" in theme \"${themeId}\" missing schema or components export.`,\n `Expected: \"${camelName}Schema\" and \"${camelName}Components\"`\n );\n _failureCount++;\n continue;\n }\n\n // Register section to theme-specific registry\n // Get the default component for backward compatibility\n const defaultComponent =\n components.default || Object.values(components)[0];\n\n if (!defaultComponent) {\n console.error(\n `[ThemeRegistry] Section \"${sectionName}\" has no default component`\n );\n _failureCount++;\n continue;\n }\n\n // Register with templates\n const templates = Object.entries(components).map(\n ([templateId, component]) => ({\n definition: schema.templates.find((t) => t.id === templateId) || {\n id: templateId,\n name: templateId,\n description: `${templateId} template`,\n isDefault: templateId === \"default\",\n settings: schema.settings,\n defaults: schema.defaults,\n },\n component,\n })\n );\n\n registry.register(schema.type, schema, defaultComponent, {\n templates,\n category: schema.category,\n tags: schema.tags,\n });\n\n _successCount++;\n\n if (this.debug) {\n }\n } catch (error) {\n console.error(\n `[ThemeRegistry] Failed to load section \"${themeId}/${sectionName}\":`,\n error\n );\n _failureCount++;\n }\n }\n\n // Mark as initialized\n this.initialized.add(themeId);\n }\n\n /**\n * Get or create theme-specific registry\n */\n getThemeRegistry(themeId: string): SectionRegistry {\n if (!this.registries.has(themeId)) {\n // Create new registry for this theme\n const registry = createSectionRegistry({\n debug: this.debug,\n allowOverride: this.debug,\n });\n\n this.registries.set(themeId, registry);\n\n if (this.debug) {\n }\n }\n\n return this.registries.get(themeId)!;\n }\n\n /**\n * Check if theme has been initialized\n */\n isThemeInitialized(themeId: string): boolean {\n return this.initialized.has(themeId);\n }\n\n /**\n * Resolve a section component for a specific theme\n * Returns null if not found (no fallback - strict validation)\n */\n resolveSection(\n themeId: string,\n sectionType: string,\n templateId: string = \"default\"\n ): React.ComponentType<SectionComponentProps> | null {\n const registry = this.getThemeRegistry(themeId);\n return registry.getTemplateComponent(sectionType, templateId) || null;\n }\n\n /**\n * Get section schema for a specific theme\n */\n getSectionSchema(themeId: string, sectionType: string): SectionSchema | null {\n const registry = this.getThemeRegistry(themeId);\n return registry.getSchema(sectionType) || null;\n }\n\n /**\n * Get all available themes\n */\n getAvailableThemes(): string[] {\n return Array.from(this.registries.keys());\n }\n\n /**\n * Get all section types for a theme\n */\n getThemeSections(themeId: string): string[] {\n const registry = this.registries.get(themeId);\n if (!registry) {\n return [];\n }\n\n const all = registry.getAll();\n return Array.from(all.keys());\n }\n\n /**\n * Clear a specific theme's registry\n */\n clearTheme(themeId: string): void {\n const registry = this.registries.get(themeId);\n if (registry) {\n registry.clear();\n this.initialized.delete(themeId);\n\n if (this.debug) {\n }\n }\n }\n\n /**\n * Clear all theme registries\n */\n clearAll(): void {\n this.registries.forEach((registry) => registry.clear());\n this.registries.clear();\n this.initialized.clear();\n\n if (this.debug) {\n }\n }\n}\n\n/**\n * Global theme registry manager instance\n */\nexport const themeRegistryManager = new ThemeRegistryManager();\n"]}
@@ -0,0 +1,331 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var chunkVJA3ER6A_js = require('./chunk-VJA3ER6A.js');
5
+ var zustand = require('zustand');
6
+ var React = require('react');
7
+ var Image = require('next/image');
8
+ var Link = require('next/link');
9
+ var lucideReact = require('lucide-react');
10
+ var jsxRuntime = require('react/jsx-runtime');
11
+
12
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
13
+
14
+ function _interopNamespace(e) {
15
+ if (e && e.__esModule) return e;
16
+ var n = Object.create(null);
17
+ if (e) {
18
+ Object.keys(e).forEach(function (k) {
19
+ if (k !== 'default') {
20
+ var d = Object.getOwnPropertyDescriptor(e, k);
21
+ Object.defineProperty(n, k, d.get ? d : {
22
+ enumerable: true,
23
+ get: function () { return e[k]; }
24
+ });
25
+ }
26
+ });
27
+ }
28
+ n.default = e;
29
+ return Object.freeze(n);
30
+ }
31
+
32
+ var React__namespace = /*#__PURE__*/_interopNamespace(React);
33
+ var Image__default = /*#__PURE__*/_interopDefault(Image);
34
+ var Link__default = /*#__PURE__*/_interopDefault(Link);
35
+
36
+ // src/features/products/products-service.ts
37
+ var ProductsService = class {
38
+ constructor(api) {
39
+ this.api = api;
40
+ }
41
+ /**
42
+ * Get products list
43
+ */
44
+ async getProducts(params) {
45
+ const response = await this.api.get(
46
+ "/products",
47
+ params
48
+ );
49
+ return response;
50
+ }
51
+ /**
52
+ * Get product by ID
53
+ */
54
+ async getProductById(productId) {
55
+ return await this.api.get(`/products/${productId}`);
56
+ }
57
+ /**
58
+ * Get product by slug
59
+ */
60
+ async getProductBySlug(productSlug) {
61
+ return await this.api.get(`/products/slug/${productSlug}`);
62
+ }
63
+ /**
64
+ * Get best sellers
65
+ */
66
+ async getBestSellers(limit) {
67
+ const response = await this.api.get("/products", {
68
+ filter: "best-sellers",
69
+ limit: limit || 8
70
+ });
71
+ return response.items;
72
+ }
73
+ /**
74
+ * Get featured products
75
+ */
76
+ async getFeaturedProducts(limit) {
77
+ const response = await this.api.get("/products", {
78
+ filter: "featured",
79
+ limit: limit || 8
80
+ });
81
+ return response.items;
82
+ }
83
+ /**
84
+ * Search products
85
+ */
86
+ async searchProducts(query, limit) {
87
+ const response = await this.api.get("/products", {
88
+ search: query,
89
+ limit: limit || 20
90
+ });
91
+ return response.items;
92
+ }
93
+ };
94
+ var productsService = null;
95
+ var useProducts = zustand.create((set) => ({
96
+ // Initial state
97
+ products: [],
98
+ currentProduct: null,
99
+ isLoading: false,
100
+ error: null,
101
+ total: 0,
102
+ totalPages: 0,
103
+ currentPage: 1,
104
+ /**
105
+ * Fetch products list
106
+ */
107
+ fetchProducts: async (params) => {
108
+ if (!productsService) {
109
+ throw new Error(
110
+ "Products service not initialized. Call initializeOnex() first."
111
+ );
112
+ }
113
+ set({ isLoading: true, error: null });
114
+ try {
115
+ const response = await productsService.getProducts(params);
116
+ set({
117
+ products: response.items,
118
+ total: response.total,
119
+ totalPages: response.totalPages,
120
+ currentPage: (params == null ? void 0 : params.page) || 1,
121
+ isLoading: false
122
+ });
123
+ } catch (error) {
124
+ set({ error: error.message, isLoading: false });
125
+ }
126
+ },
127
+ /**
128
+ * Fetch product by ID
129
+ */
130
+ fetchProductById: async (productId) => {
131
+ if (!productsService) {
132
+ throw new Error(
133
+ "Products service not initialized. Call initializeOnex() first."
134
+ );
135
+ }
136
+ set({ isLoading: true, error: null });
137
+ try {
138
+ const product = await productsService.getProductById(productId);
139
+ set({ currentProduct: product, isLoading: false });
140
+ } catch (error) {
141
+ set({ error: error.message, isLoading: false });
142
+ }
143
+ },
144
+ /**
145
+ * Fetch product by slug
146
+ */
147
+ fetchProductBySlug: async (productSlug) => {
148
+ if (!productsService) {
149
+ throw new Error(
150
+ "Products service not initialized. Call initializeOnex() first."
151
+ );
152
+ }
153
+ set({ isLoading: true, error: null });
154
+ try {
155
+ const product = await productsService.getProductBySlug(productSlug);
156
+ set({ currentProduct: product, isLoading: false });
157
+ } catch (error) {
158
+ set({ error: error.message, isLoading: false });
159
+ }
160
+ },
161
+ /**
162
+ * Fetch best sellers
163
+ */
164
+ fetchBestSellers: async (limit) => {
165
+ if (!productsService) {
166
+ throw new Error(
167
+ "Products service not initialized. Call initializeOnex() first."
168
+ );
169
+ }
170
+ set({ isLoading: true, error: null });
171
+ try {
172
+ const products = await productsService.getBestSellers(limit);
173
+ set({ products, isLoading: false });
174
+ } catch (error) {
175
+ set({ error: error.message, isLoading: false });
176
+ }
177
+ },
178
+ /**
179
+ * Fetch featured products
180
+ */
181
+ fetchFeaturedProducts: async (limit) => {
182
+ if (!productsService) {
183
+ throw new Error(
184
+ "Products service not initialized. Call initializeOnex() first."
185
+ );
186
+ }
187
+ set({ isLoading: true, error: null });
188
+ try {
189
+ const products = await productsService.getFeaturedProducts(limit);
190
+ set({ products, isLoading: false });
191
+ } catch (error) {
192
+ set({ error: error.message, isLoading: false });
193
+ }
194
+ },
195
+ /**
196
+ * Search products
197
+ */
198
+ searchProducts: async (query, limit) => {
199
+ if (!productsService) {
200
+ throw new Error(
201
+ "Products service not initialized. Call initializeOnex() first."
202
+ );
203
+ }
204
+ set({ isLoading: true, error: null });
205
+ try {
206
+ const products = await productsService.searchProducts(query, limit);
207
+ set({ products, isLoading: false });
208
+ } catch (error) {
209
+ set({ error: error.message, isLoading: false });
210
+ }
211
+ },
212
+ /**
213
+ * Clear error
214
+ */
215
+ clearError: () => {
216
+ set({ error: null });
217
+ },
218
+ /**
219
+ * Clear current product
220
+ */
221
+ clearCurrentProduct: () => {
222
+ set({ currentProduct: null });
223
+ }
224
+ }));
225
+ function initializeProductsService(service) {
226
+ productsService = service;
227
+ }
228
+ function formatPrice(price) {
229
+ return new Intl.NumberFormat("vi-VN").format(price) + " \u0111";
230
+ }
231
+ function ProductCard(props) {
232
+ const [imageError, setImageError] = React__namespace.useState(false);
233
+ const {
234
+ product: productObj,
235
+ slug: slugProp,
236
+ name: nameProp,
237
+ description: descriptionProp,
238
+ price: priceProp,
239
+ originalPrice: originalPriceProp,
240
+ discount: discountProp,
241
+ image: imageProp,
242
+ href,
243
+ className,
244
+ showAddToCart = true,
245
+ onAddToCart,
246
+ onClick
247
+ } = props;
248
+ const product = productObj || {
249
+ slug: slugProp,
250
+ title: nameProp || "",
251
+ description: descriptionProp,
252
+ salePrice: priceProp || 0,
253
+ originalPrice: originalPriceProp,
254
+ discount: discountProp,
255
+ image: imageProp,
256
+ id: slugProp || ""
257
+ };
258
+ const link = href || (product.slug ? `/products/${product.slug}` : `/products/${product.id}`);
259
+ const handleAddToCart = (e) => {
260
+ if (e) {
261
+ e.preventDefault();
262
+ e.stopPropagation();
263
+ }
264
+ onAddToCart == null ? void 0 : onAddToCart(product, e);
265
+ };
266
+ const handleClick = () => {
267
+ onClick == null ? void 0 : onClick();
268
+ };
269
+ const placeholderSvg = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='400' viewBox='0 0 400 400'%3E%3Crect fill='%232A2A2A' width='400' height='400'/%3E%3Cpath fill='%23444' d='M150 170h100v60H150z'/%3E%3Ccircle fill='%23444' cx='175' cy='145' r='20'/%3E%3Cpath fill='%23444' d='M140 250l50-40 30 25 40-60 50 75H140z'/%3E%3C/svg%3E";
270
+ const displayImage = product.image && !imageError ? product.image : placeholderSvg;
271
+ const isOutOfStock = product.inStock === false;
272
+ const discount = typeof product.discount === "number" ? product.discount : typeof product.discount === "string" ? parseInt(product.discount) : 0;
273
+ return /* @__PURE__ */ jsxRuntime.jsx(
274
+ Link__default.default,
275
+ {
276
+ href: link,
277
+ className: chunkVJA3ER6A_js.cn("group block", className),
278
+ onClick: handleClick,
279
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-card rounded-lg overflow-hidden hover:shadow-xl hover:shadow-black/20 transition-shadow h-full flex flex-col", children: [
280
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative aspect-square", children: [
281
+ /* @__PURE__ */ jsxRuntime.jsx(
282
+ Image__default.default,
283
+ {
284
+ src: displayImage,
285
+ alt: product.title,
286
+ fill: true,
287
+ className: chunkVJA3ER6A_js.cn(
288
+ "object-cover group-hover:scale-105 transition-transform duration-300",
289
+ isOutOfStock && "opacity-60 grayscale"
290
+ ),
291
+ sizes: "(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 25vw",
292
+ onError: () => setImageError(true)
293
+ }
294
+ ),
295
+ isOutOfStock && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-white/90 rounded-md px-3 py-1.5 text-xs font-semibold", children: "H\u1EBFt h\xE0ng" }) }),
296
+ discount > 0 && !isOutOfStock && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-2 right-2 bg-[#C41E3A] text-white text-xs font-semibold px-2 py-1 rounded", children: [
297
+ "-",
298
+ discount,
299
+ "%"
300
+ ] })
301
+ ] }),
302
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-3 md:p-4 flex flex-col flex-1", children: [
303
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold text-sm md:text-base mb-1 line-clamp-2 transition-colors", children: product.title }),
304
+ product.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-400 text-xs md:text-sm mb-2 line-clamp-2 flex-1", children: product.description }),
305
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mt-auto", children: [
306
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
307
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[#C41E3A] font-bold text-sm md:text-base", children: formatPrice(product.salePrice) }),
308
+ product.originalPrice && product.originalPrice > product.salePrice && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-500 text-xs md:text-sm line-through", children: formatPrice(product.originalPrice) })
309
+ ] }),
310
+ showAddToCart && onAddToCart && !isOutOfStock && /* @__PURE__ */ jsxRuntime.jsx(
311
+ "button",
312
+ {
313
+ onClick: handleAddToCart,
314
+ className: "w-8 h-8 md:w-9 md:h-9 bg-[#2A2A2A] hover:bg-[#3A3A3A] text-white rounded-lg flex items-center justify-center transition-colors shrink-0",
315
+ "aria-label": "Th\xEAm v\xE0o gi\u1ECF h\xE0ng",
316
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ShoppingCart, { className: "w-4 h-4" })
317
+ }
318
+ )
319
+ ] })
320
+ ] })
321
+ ] })
322
+ }
323
+ );
324
+ }
325
+
326
+ exports.ProductCard = ProductCard;
327
+ exports.ProductsService = ProductsService;
328
+ exports.initializeProductsService = initializeProductsService;
329
+ exports.useProducts = useProducts;
330
+ //# sourceMappingURL=chunk-OWNGNGKL.js.map
331
+ //# sourceMappingURL=chunk-OWNGNGKL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/features/products/products-service.ts","../src/features/products/use-products.ts","../src/components/product-card/product-card.tsx"],"names":["create","React","jsx","Link","cn","jsxs","Image","ShoppingCart"],"mappings":"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","file":"chunk-OWNGNGKL.js","sourcesContent":["/**\n * Products Service\n * Handles all product-related API calls\n */\n\nimport { ApiClient } from \"../../api/client\";\nimport type { Product } from \"../../types/product\";\n\nexport interface ProductsListResponse {\n items: Product[];\n total: number;\n totalPages: number;\n}\n\nexport interface ProductsListParams {\n page?: number;\n limit?: number;\n category?: string;\n search?: string;\n filter?: string;\n}\n\nexport class ProductsService {\n constructor(private api: ApiClient) {}\n\n /**\n * Get products list\n */\n async getProducts(\n params?: ProductsListParams\n ): Promise<ProductsListResponse> {\n const response = await this.api.get<ProductsListResponse>(\n \"/products\",\n params as Record<string, unknown>\n );\n return response;\n }\n\n /**\n * Get product by ID\n */\n async getProductById(productId: string): Promise<Product> {\n return await this.api.get<Product>(`/products/${productId}`);\n }\n\n /**\n * Get product by slug\n */\n async getProductBySlug(productSlug: string): Promise<Product> {\n return await this.api.get<Product>(`/products/slug/${productSlug}`);\n }\n\n /**\n * Get best sellers\n */\n async getBestSellers(limit?: number): Promise<Product[]> {\n const response = await this.api.get<ProductsListResponse>(\"/products\", {\n filter: \"best-sellers\",\n limit: limit || 8,\n });\n return response.items;\n }\n\n /**\n * Get featured products\n */\n async getFeaturedProducts(limit?: number): Promise<Product[]> {\n const response = await this.api.get<ProductsListResponse>(\"/products\", {\n filter: \"featured\",\n limit: limit || 8,\n });\n return response.items;\n }\n\n /**\n * Search products\n */\n async searchProducts(query: string, limit?: number): Promise<Product[]> {\n const response = await this.api.get<ProductsListResponse>(\"/products\", {\n search: query,\n limit: limit || 20,\n });\n return response.items;\n }\n}\n","/**\n * Products Hook\n * Zustand store for products state and actions\n */\n\nimport { create } from \"zustand\";\nimport { ProductsService } from \"./products-service\";\nimport type { Product } from \"../../types/product\";\nimport type { ProductsListParams } from \"./products-service\";\n\ninterface ProductsState {\n // State\n products: Product[];\n currentProduct: Product | null;\n isLoading: boolean;\n error: string | null;\n total: number;\n totalPages: number;\n currentPage: number;\n\n // Actions\n fetchProducts: (params?: ProductsListParams) => Promise<void>;\n fetchProductById: (productId: string) => Promise<void>;\n fetchProductBySlug: (productSlug: string) => Promise<void>;\n fetchBestSellers: (limit?: number) => Promise<void>;\n fetchFeaturedProducts: (limit?: number) => Promise<void>;\n searchProducts: (query: string, limit?: number) => Promise<void>;\n clearError: () => void;\n clearCurrentProduct: () => void;\n}\n\nlet productsService: ProductsService | null = null;\n\nexport const useProducts = create<ProductsState>((set) => ({\n // Initial state\n products: [],\n currentProduct: null,\n isLoading: false,\n error: null,\n total: 0,\n totalPages: 0,\n currentPage: 1,\n\n /**\n * Fetch products list\n */\n fetchProducts: async (params) => {\n if (!productsService) {\n throw new Error(\n \"Products service not initialized. Call initializeOnex() first.\"\n );\n }\n\n set({ isLoading: true, error: null });\n try {\n const response = await productsService.getProducts(params);\n set({\n products: response.items,\n total: response.total,\n totalPages: response.totalPages,\n currentPage: params?.page || 1,\n isLoading: false,\n });\n } catch (error) {\n set({ error: (error as Error).message, isLoading: false });\n }\n },\n\n /**\n * Fetch product by ID\n */\n fetchProductById: async (productId) => {\n if (!productsService) {\n throw new Error(\n \"Products service not initialized. Call initializeOnex() first.\"\n );\n }\n\n set({ isLoading: true, error: null });\n try {\n const product = await productsService.getProductById(productId);\n set({ currentProduct: product, isLoading: false });\n } catch (error) {\n set({ error: (error as Error).message, isLoading: false });\n }\n },\n\n /**\n * Fetch product by slug\n */\n fetchProductBySlug: async (productSlug) => {\n if (!productsService) {\n throw new Error(\n \"Products service not initialized. Call initializeOnex() first.\"\n );\n }\n\n set({ isLoading: true, error: null });\n try {\n const product = await productsService.getProductBySlug(productSlug);\n set({ currentProduct: product, isLoading: false });\n } catch (error) {\n set({ error: (error as Error).message, isLoading: false });\n }\n },\n\n /**\n * Fetch best sellers\n */\n fetchBestSellers: async (limit) => {\n if (!productsService) {\n throw new Error(\n \"Products service not initialized. Call initializeOnex() first.\"\n );\n }\n\n set({ isLoading: true, error: null });\n try {\n const products = await productsService.getBestSellers(limit);\n set({ products, isLoading: false });\n } catch (error) {\n set({ error: (error as Error).message, isLoading: false });\n }\n },\n\n /**\n * Fetch featured products\n */\n fetchFeaturedProducts: async (limit) => {\n if (!productsService) {\n throw new Error(\n \"Products service not initialized. Call initializeOnex() first.\"\n );\n }\n\n set({ isLoading: true, error: null });\n try {\n const products = await productsService.getFeaturedProducts(limit);\n set({ products, isLoading: false });\n } catch (error) {\n set({ error: (error as Error).message, isLoading: false });\n }\n },\n\n /**\n * Search products\n */\n searchProducts: async (query, limit) => {\n if (!productsService) {\n throw new Error(\n \"Products service not initialized. Call initializeOnex() first.\"\n );\n }\n\n set({ isLoading: true, error: null });\n try {\n const products = await productsService.searchProducts(query, limit);\n set({ products, isLoading: false });\n } catch (error) {\n set({ error: (error as Error).message, isLoading: false });\n }\n },\n\n /**\n * Clear error\n */\n clearError: () => {\n set({ error: null });\n },\n\n /**\n * Clear current product\n */\n clearCurrentProduct: () => {\n set({ currentProduct: null });\n },\n}));\n\n/**\n * Initialize products service\n * @internal Called by initializeOnex()\n */\nexport function initializeProductsService(service: ProductsService): void {\n productsService = service;\n}\n","/**\n * ProductCard Component\n * Generic product card for displaying products in themes\n */\n\n\"use client\";\n\nimport * as React from \"react\";\nimport Image from \"next/image\";\nimport Link from \"next/link\";\nimport { ShoppingCart } from \"lucide-react\";\nimport { cn } from \"../../utils/cn\";\nimport type { Product } from \"../../types/product\";\n\nexport interface ProductCardProps {\n /** Product data (full object) */\n product?: Product;\n\n // Alternative: Individual props\n slug?: string;\n name?: string;\n description?: string;\n price?: number;\n originalPrice?: number;\n discount?: number;\n image?: string;\n\n /** Custom href (overrides default /products/[slug]) */\n href?: string;\n\n /** Custom class name */\n className?: string;\n\n /** Show add to cart button */\n showAddToCart?: boolean;\n\n /** Add to cart handler */\n onAddToCart?: (product: Product, e?: React.MouseEvent) => void;\n\n /** Click handler for entire card */\n onClick?: () => void;\n}\n\n/**\n * Format price in Vietnamese dong\n */\nfunction formatPrice(price: number): string {\n return new Intl.NumberFormat(\"vi-VN\").format(price) + \" đ\";\n}\n\n/**\n * ProductCard Component\n */\nexport function ProductCard(props: ProductCardProps) {\n const [imageError, setImageError] = React.useState(false);\n\n // Normalize props - support both product object and individual props\n const {\n product: productObj,\n slug: slugProp,\n name: nameProp,\n description: descriptionProp,\n price: priceProp,\n originalPrice: originalPriceProp,\n discount: discountProp,\n image: imageProp,\n href,\n className,\n showAddToCart = true,\n onAddToCart,\n onClick,\n } = props;\n\n // Use product object if provided, otherwise use individual props\n const product =\n productObj ||\n ({\n slug: slugProp,\n title: nameProp || \"\",\n description: descriptionProp,\n salePrice: priceProp || 0,\n originalPrice: originalPriceProp,\n discount: discountProp,\n image: imageProp,\n id: slugProp || \"\",\n } as Product);\n\n // Generate slug-based URL\n const link =\n href ||\n (product.slug ? `/products/${product.slug}` : `/products/${product.id}`);\n\n const handleAddToCart = (e?: React.MouseEvent) => {\n if (e) {\n e.preventDefault();\n e.stopPropagation();\n }\n onAddToCart?.(product, e);\n };\n\n const handleClick = () => {\n onClick?.();\n };\n\n // Placeholder SVG for missing images\n const placeholderSvg =\n \"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='400' viewBox='0 0 400 400'%3E%3Crect fill='%232A2A2A' width='400' height='400'/%3E%3Cpath fill='%23444' d='M150 170h100v60H150z'/%3E%3Ccircle fill='%23444' cx='175' cy='145' r='20'/%3E%3Cpath fill='%23444' d='M140 250l50-40 30 25 40-60 50 75H140z'/%3E%3C/svg%3E\";\n\n const displayImage =\n product.image && !imageError ? product.image : placeholderSvg;\n\n const isOutOfStock = product.inStock === false;\n const discount =\n typeof product.discount === \"number\"\n ? product.discount\n : typeof product.discount === \"string\"\n ? parseInt(product.discount)\n : 0;\n\n return (\n <Link\n href={link}\n className={cn(\"group block\", className)}\n onClick={handleClick}\n >\n <div className=\"bg-card rounded-lg overflow-hidden hover:shadow-xl hover:shadow-black/20 transition-shadow h-full flex flex-col\">\n {/* Image Container */}\n <div className=\"relative aspect-square\">\n <Image\n src={displayImage}\n alt={product.title}\n fill\n className={cn(\n \"object-cover group-hover:scale-105 transition-transform duration-300\",\n isOutOfStock && \"opacity-60 grayscale\"\n )}\n sizes=\"(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 25vw\"\n onError={() => setImageError(true)}\n />\n\n {/* Out of Stock Overlay */}\n {isOutOfStock && (\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <div className=\"bg-white/90 rounded-md px-3 py-1.5 text-xs font-semibold\">\n Hết hàng\n </div>\n </div>\n )}\n\n {/* Discount Badge */}\n {discount > 0 && !isOutOfStock && (\n <div className=\"absolute top-2 right-2 bg-[#C41E3A] text-white text-xs font-semibold px-2 py-1 rounded\">\n -{discount}%\n </div>\n )}\n </div>\n\n {/* Product Info */}\n <div className=\"p-3 md:p-4 flex flex-col flex-1\">\n {/* Title */}\n <h3 className=\"font-semibold text-sm md:text-base mb-1 line-clamp-2 transition-colors\">\n {product.title}\n </h3>\n\n {/* Description */}\n {product.description && (\n <p className=\"text-gray-400 text-xs md:text-sm mb-2 line-clamp-2 flex-1\">\n {product.description}\n </p>\n )}\n\n {/* Price Row */}\n <div className=\"flex items-center justify-between mt-auto\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <span className=\"text-[#C41E3A] font-bold text-sm md:text-base\">\n {formatPrice(product.salePrice)}\n </span>\n {product.originalPrice &&\n product.originalPrice > product.salePrice && (\n <span className=\"text-gray-500 text-xs md:text-sm line-through\">\n {formatPrice(product.originalPrice)}\n </span>\n )}\n </div>\n\n {/* Add to Cart Button */}\n {showAddToCart && onAddToCart && !isOutOfStock && (\n <button\n onClick={handleAddToCart}\n className=\"w-8 h-8 md:w-9 md:h-9 bg-[#2A2A2A] hover:bg-[#3A3A3A] text-white rounded-lg flex items-center justify-center transition-colors shrink-0\"\n aria-label=\"Thêm vào giỏ hàng\"\n >\n <ShoppingCart className=\"w-4 h-4\" />\n </button>\n )}\n </div>\n </div>\n </div>\n </Link>\n );\n}\n"]}