@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.
- package/README.md +44 -65
- package/THEME_API.md +36 -33
- package/dist/api.js +14 -0
- package/dist/api.js.map +1 -0
- package/dist/api.mjs +5 -0
- package/dist/api.mjs.map +1 -0
- package/dist/auth.js +23 -0
- package/dist/auth.js.map +1 -0
- package/dist/auth.mjs +6 -0
- package/dist/auth.mjs.map +1 -0
- package/dist/blog.js +22 -0
- package/dist/blog.js.map +1 -0
- package/dist/blog.mjs +5 -0
- package/dist/blog.mjs.map +1 -0
- package/dist/cart.js +27 -0
- package/dist/cart.js.map +1 -0
- package/dist/cart.mjs +6 -0
- package/dist/cart.mjs.map +1 -0
- package/dist/chunk-2CSWHI3L.js +210 -0
- package/dist/chunk-2CSWHI3L.js.map +1 -0
- package/dist/chunk-2FGHIDUV.js +99 -0
- package/dist/chunk-2FGHIDUV.js.map +1 -0
- package/dist/chunk-2NMEKWO5.js +40 -0
- package/dist/chunk-2NMEKWO5.js.map +1 -0
- package/dist/chunk-43BVHGDT.mjs +131 -0
- package/dist/chunk-43BVHGDT.mjs.map +1 -0
- package/dist/chunk-4JVQJI3I.mjs +290 -0
- package/dist/chunk-4JVQJI3I.mjs.map +1 -0
- package/dist/chunk-5N2EKK5O.js +9550 -0
- package/dist/chunk-5N2EKK5O.js.map +1 -0
- package/dist/chunk-5T6NDVSN.js +76 -0
- package/dist/chunk-5T6NDVSN.js.map +1 -0
- package/dist/chunk-73AINNCI.mjs +201 -0
- package/dist/chunk-73AINNCI.mjs.map +1 -0
- package/dist/chunk-AIXBDAVP.mjs +61 -0
- package/dist/chunk-AIXBDAVP.mjs.map +1 -0
- package/dist/chunk-ALYN5HAC.js +210 -0
- package/dist/chunk-ALYN5HAC.js.map +1 -0
- package/dist/chunk-AREMJR3Q.js +36 -0
- package/dist/chunk-AREMJR3Q.js.map +1 -0
- package/dist/chunk-DXAI6XOI.mjs +33 -0
- package/dist/chunk-DXAI6XOI.mjs.map +1 -0
- package/dist/chunk-F5TFNWFJ.mjs +257 -0
- package/dist/chunk-F5TFNWFJ.mjs.map +1 -0
- package/dist/chunk-FQ7FWUZN.js +265 -0
- package/dist/chunk-FQ7FWUZN.js.map +1 -0
- package/dist/{chunk-3SZX6LHT.js → chunk-J27VGXJH.js} +2 -24
- package/dist/chunk-J27VGXJH.js.map +1 -0
- package/dist/chunk-JZ46LLTZ.js +121 -0
- package/dist/chunk-JZ46LLTZ.js.map +1 -0
- package/dist/chunk-K24UHN6G.mjs +76 -0
- package/dist/chunk-K24UHN6G.mjs.map +1 -0
- package/dist/chunk-KCQGGU5R.mjs +344 -0
- package/dist/chunk-KCQGGU5R.mjs.map +1 -0
- package/dist/chunk-MT22NYKT.mjs +117 -0
- package/dist/chunk-MT22NYKT.mjs.map +1 -0
- package/dist/chunk-NDD472IZ.js +85 -0
- package/dist/chunk-NDD472IZ.js.map +1 -0
- package/dist/chunk-NHOIGGYU.mjs +833 -0
- package/dist/chunk-NHOIGGYU.mjs.map +1 -0
- package/dist/chunk-O3XR7TW3.mjs +12 -0
- package/dist/chunk-O3XR7TW3.mjs.map +1 -0
- package/dist/chunk-OAPYSC2X.mjs +206 -0
- package/dist/chunk-OAPYSC2X.mjs.map +1 -0
- package/dist/{chunk-XE4EOKS4.mjs → chunk-ONJREDYY.mjs} +3 -23
- package/dist/chunk-ONJREDYY.mjs.map +1 -0
- package/dist/chunk-OVT2LUAM.js +197 -0
- package/dist/chunk-OVT2LUAM.js.map +1 -0
- package/dist/chunk-OWNGNGKL.js +331 -0
- package/dist/chunk-OWNGNGKL.js.map +1 -0
- package/dist/chunk-P7SXNZSV.js +298 -0
- package/dist/chunk-P7SXNZSV.js.map +1 -0
- package/dist/chunk-PFJOL3HI.mjs +71 -0
- package/dist/chunk-PFJOL3HI.mjs.map +1 -0
- package/dist/chunk-PPULMWJ6.js +295 -0
- package/dist/chunk-PPULMWJ6.js.map +1 -0
- package/dist/chunk-RGIVKACG.js +359 -0
- package/dist/chunk-RGIVKACG.js.map +1 -0
- package/dist/chunk-RPP5K2LP.js +870 -0
- package/dist/chunk-RPP5K2LP.js.map +1 -0
- package/dist/{chunk-7EON6Q4L.mjs → chunk-RUCHWUD7.mjs} +7651 -6370
- package/dist/chunk-RUCHWUD7.mjs.map +1 -0
- package/dist/chunk-SEVUIX4H.js +137 -0
- package/dist/chunk-SEVUIX4H.js.map +1 -0
- package/dist/chunk-SK2FSHFM.mjs +208 -0
- package/dist/chunk-SK2FSHFM.mjs.map +1 -0
- package/dist/chunk-T6EJ2GAV.mjs +294 -0
- package/dist/chunk-T6EJ2GAV.mjs.map +1 -0
- package/dist/chunk-ULBDOFZI.mjs +302 -0
- package/dist/chunk-ULBDOFZI.mjs.map +1 -0
- package/dist/chunk-V3JIELNV.js +241 -0
- package/dist/chunk-V3JIELNV.js.map +1 -0
- package/dist/chunk-V5E2KWMA.mjs +238 -0
- package/dist/chunk-V5E2KWMA.mjs.map +1 -0
- package/dist/chunk-VJA3ER6A.js +14 -0
- package/dist/chunk-VJA3ER6A.js.map +1 -0
- package/dist/chunk-VLI7ULX5.js +66 -0
- package/dist/chunk-VLI7ULX5.js.map +1 -0
- package/dist/chunk-WFGS5OFH.mjs +189 -0
- package/dist/chunk-WFGS5OFH.mjs.map +1 -0
- package/dist/chunk-WVC5GP24.mjs +96 -0
- package/dist/chunk-WVC5GP24.mjs.map +1 -0
- package/dist/chunk-YOSPWY5K.mjs +36 -0
- package/dist/chunk-YOSPWY5K.mjs.map +1 -0
- package/dist/chunk-ZFFXXLX7.js +205 -0
- package/dist/chunk-ZFFXXLX7.js.map +1 -0
- package/dist/client.js +512 -249
- package/dist/client.mjs +21 -2
- package/dist/components.js +393 -0
- package/dist/components.js.map +1 -0
- package/dist/components.mjs +8 -0
- package/dist/components.mjs.map +1 -0
- package/dist/config.js +17 -0
- package/dist/config.js.map +1 -0
- package/dist/config.mjs +4 -0
- package/dist/config.mjs.map +1 -0
- package/dist/contact.js +22 -0
- package/dist/contact.js.map +1 -0
- package/dist/contact.mjs +5 -0
- package/dist/contact.mjs.map +1 -0
- package/dist/contexts.js +51 -0
- package/dist/contexts.js.map +1 -0
- package/dist/contexts.mjs +6 -0
- package/dist/contexts.mjs.map +1 -0
- package/dist/finance.js +26 -0
- package/dist/finance.js.map +1 -0
- package/dist/finance.mjs +5 -0
- package/dist/finance.mjs.map +1 -0
- package/dist/icons.js +15 -0
- package/dist/icons.js.map +1 -0
- package/dist/icons.mjs +4 -0
- package/dist/icons.mjs.map +1 -0
- package/dist/index.js +512 -249
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +21 -2
- package/dist/index.mjs.map +1 -1
- package/dist/internal.js +1437 -0
- package/dist/internal.js.map +1 -0
- package/dist/internal.mjs +1404 -0
- package/dist/internal.mjs.map +1 -0
- package/dist/motion.js +51 -0
- package/dist/motion.js.map +1 -0
- package/dist/motion.mjs +38 -0
- package/dist/motion.mjs.map +1 -0
- package/dist/orders.js +22 -0
- package/dist/orders.js.map +1 -0
- package/dist/orders.mjs +5 -0
- package/dist/orders.mjs.map +1 -0
- package/dist/products.js +27 -0
- package/dist/products.js.map +1 -0
- package/dist/products.mjs +6 -0
- package/dist/products.mjs.map +1 -0
- package/dist/registry.js +44 -0
- package/dist/registry.js.map +1 -0
- package/dist/registry.mjs +7 -0
- package/dist/registry.mjs.map +1 -0
- package/dist/renderers.js +51 -0
- package/dist/renderers.js.map +1 -0
- package/dist/renderers.mjs +6 -0
- package/dist/renderers.mjs.map +1 -0
- package/dist/server.js +11 -189
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +3 -186
- package/dist/server.mjs.map +1 -1
- package/dist/types.js +37 -0
- package/dist/types.js.map +1 -0
- package/dist/types.mjs +4 -0
- package/dist/types.mjs.map +1 -0
- package/dist/utils.js +160 -0
- package/dist/utils.js.map +1 -0
- package/dist/utils.mjs +7 -0
- package/dist/utils.mjs.map +1 -0
- package/dist/wrappers.js +104 -0
- package/dist/wrappers.js.map +1 -0
- package/dist/wrappers.mjs +96 -0
- package/dist/wrappers.mjs.map +1 -0
- package/package.json +112 -9
- package/dist/api-vuL1Eg5L.d.mts +0 -2961
- package/dist/api-vuL1Eg5L.d.ts +0 -2961
- package/dist/chunk-3SZX6LHT.js.map +0 -1
- package/dist/chunk-7EON6Q4L.mjs.map +0 -1
- package/dist/chunk-WDY773GJ.js +0 -8308
- package/dist/chunk-WDY773GJ.js.map +0 -1
- package/dist/chunk-XE4EOKS4.mjs.map +0 -1
- package/dist/client.d.mts +0 -1461
- package/dist/client.d.ts +0 -1461
- package/dist/index.d.mts +0 -125
- package/dist/index.d.ts +0 -125
- package/dist/server.d.mts +0 -70
- 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":"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","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"]}
|