@numueg/theme-sdk 0.6.1 → 0.10.0
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/CHANGELOG.md +144 -0
- package/dist/chunk-4TMO5Y5W.mjs +183 -0
- package/dist/chunk-4TMO5Y5W.mjs.map +1 -0
- package/dist/chunk-EB2ESBLD.mjs +397 -0
- package/dist/chunk-EB2ESBLD.mjs.map +1 -0
- package/dist/chunk-IBNAZRUT.cjs +207 -0
- package/dist/chunk-IBNAZRUT.cjs.map +1 -0
- package/dist/chunk-KWODVI7F.cjs +408 -0
- package/dist/chunk-KWODVI7F.cjs.map +1 -0
- package/dist/chunk-TBSNHHFH.cjs +96 -0
- package/dist/chunk-TBSNHHFH.cjs.map +1 -0
- package/dist/chunk-XF2FGIVS.mjs +94 -0
- package/dist/chunk-XF2FGIVS.mjs.map +1 -0
- package/dist/{entities-6MGANln7.d.mts → entities-B8378GKp.d.mts} +12 -0
- package/dist/{entities-6MGANln7.d.ts → entities-B8378GKp.d.ts} +12 -0
- package/dist/index.cjs +693 -968
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +286 -94
- package/dist/index.d.ts +286 -94
- package/dist/index.mjs +531 -911
- package/dist/index.mjs.map +1 -1
- package/dist/{mount-BGumg1JM.d.mts → mount-HbnOAYZ-.d.ts} +90 -14
- package/dist/{mount-Bt-4ken5.d.ts → mount-eAhGgGtZ.d.mts} +90 -14
- package/dist/normalize.cjs +7 -91
- package/dist/normalize.cjs.map +1 -1
- package/dist/normalize.d.mts +1 -1
- package/dist/normalize.d.ts +1 -1
- package/dist/normalize.mjs +1 -92
- package/dist/normalize.mjs.map +1 -1
- package/dist/{theme-D8MOopvi.d.mts → theme-CNTB4KnU.d.mts} +29 -3
- package/dist/{theme-D8MOopvi.d.ts → theme-CNTB4KnU.d.ts} +29 -3
- package/dist/types.d.mts +2 -2
- package/dist/types.d.ts +2 -2
- package/dist/v2-compat.cjs +6 -163
- package/dist/v2-compat.cjs.map +1 -1
- package/dist/v2-compat.d.mts +1 -1
- package/dist/v2-compat.d.ts +1 -1
- package/dist/v2-compat.mjs +2 -159
- package/dist/v2-compat.mjs.map +1 -1
- package/dist/validation.cjs +43 -403
- package/dist/validation.cjs.map +1 -1
- package/dist/validation.d.mts +1 -1
- package/dist/validation.d.ts +1 -1
- package/dist/validation.mjs +1 -395
- package/dist/validation.mjs.map +1 -1
- package/dist/verify.cjs +6 -21
- package/dist/verify.cjs.map +1 -1
- package/dist/verify.d.mts +3 -3
- package/dist/verify.d.ts +3 -3
- package/dist/verify.mjs +3 -20
- package/dist/verify.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,530 +1,37 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var chunkTBSNHHFH_cjs = require('./chunk-TBSNHHFH.cjs');
|
|
4
|
+
var chunkKWODVI7F_cjs = require('./chunk-KWODVI7F.cjs');
|
|
5
|
+
var chunkIBNAZRUT_cjs = require('./chunk-IBNAZRUT.cjs');
|
|
3
6
|
var react = require('react');
|
|
4
7
|
var client = require('react-dom/client');
|
|
5
8
|
var jsxRuntime = require('react/jsx-runtime');
|
|
6
9
|
|
|
7
10
|
// src/types/theme.ts
|
|
8
11
|
var MAX_BLOCK_DEPTH = 5;
|
|
9
|
-
|
|
10
|
-
// src/validation/index.ts
|
|
11
|
-
var THEME_CONTRACT_VERSION = 1;
|
|
12
|
-
var SDK_VERSION = "0.6.1" ;
|
|
13
|
-
var REQUIRED_TEMPLATES = [
|
|
14
|
-
"home",
|
|
15
|
-
"product",
|
|
16
|
-
"collection",
|
|
17
|
-
"cart",
|
|
18
|
-
"page",
|
|
19
|
-
"search",
|
|
20
|
-
"404"
|
|
21
|
-
];
|
|
22
|
-
var KNOWN_TEMPLATES = /* @__PURE__ */ new Set([
|
|
23
|
-
...REQUIRED_TEMPLATES,
|
|
24
|
-
"blog",
|
|
25
|
-
"article",
|
|
26
|
-
"policies",
|
|
27
|
-
"password",
|
|
28
|
-
"account",
|
|
29
|
-
"checkout"
|
|
30
|
-
]);
|
|
31
|
-
var KNOWN_SETTING_TYPES = /* @__PURE__ */ new Set([
|
|
32
|
-
"text",
|
|
33
|
-
"textarea",
|
|
34
|
-
"richtext",
|
|
35
|
-
"number",
|
|
36
|
-
"range",
|
|
37
|
-
"color",
|
|
38
|
-
"checkbox",
|
|
39
|
-
"select",
|
|
40
|
-
"radio",
|
|
41
|
-
"font",
|
|
42
|
-
"image_picker",
|
|
43
|
-
"url",
|
|
44
|
-
"product",
|
|
45
|
-
"product_list",
|
|
46
|
-
"collection",
|
|
47
|
-
"collection_list",
|
|
48
|
-
"header",
|
|
49
|
-
"paragraph",
|
|
50
|
-
"html",
|
|
51
|
-
"date",
|
|
52
|
-
"time",
|
|
53
|
-
"video_picker",
|
|
54
|
-
"color_scheme",
|
|
55
|
-
"page_picker",
|
|
56
|
-
"blog_picker",
|
|
57
|
-
"link_list_picker",
|
|
58
|
-
"variant_picker",
|
|
59
|
-
"file_upload",
|
|
60
|
-
"icon_picker",
|
|
61
|
-
"icon"
|
|
62
|
-
]);
|
|
63
|
-
var PRESENTATIONAL_SETTING_TYPES = /* @__PURE__ */ new Set([
|
|
64
|
-
"header",
|
|
65
|
-
"paragraph",
|
|
66
|
-
"html"
|
|
67
|
-
]);
|
|
68
|
-
var SECTION_TYPE_RE = /^[a-z][a-z0-9_-]*$/;
|
|
69
|
-
var THEME_ID_RE = /^[a-z0-9][a-z0-9_-]*[a-z0-9]$/;
|
|
70
|
-
var SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?(?:\+[0-9A-Za-z-.]+)?$/;
|
|
71
|
-
function isObject(v) {
|
|
72
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
73
|
-
}
|
|
74
|
-
function isNonEmptyString(v) {
|
|
75
|
-
return typeof v === "string" && v.trim().length > 0;
|
|
76
|
-
}
|
|
77
|
-
var IssueBag = class {
|
|
78
|
-
constructor() {
|
|
79
|
-
this.issues = [];
|
|
80
|
-
}
|
|
81
|
-
err(code, message, path) {
|
|
82
|
-
this.issues.push({ level: "error", code, message, path });
|
|
83
|
-
}
|
|
84
|
-
warn(code, message, path) {
|
|
85
|
-
this.issues.push({ level: "warning", code, message, path });
|
|
86
|
-
}
|
|
87
|
-
result() {
|
|
88
|
-
return {
|
|
89
|
-
valid: !this.issues.some((i) => i.level === "error"),
|
|
90
|
-
issues: this.issues
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
function collectPresetSectionTypes(manifest) {
|
|
95
|
-
const types = /* @__PURE__ */ new Set();
|
|
96
|
-
const presets = manifest.presets;
|
|
97
|
-
if (!isObject(presets)) return types;
|
|
98
|
-
const buckets = [presets.templates, presets.section_groups];
|
|
99
|
-
for (const bucket of buckets) {
|
|
100
|
-
if (!isObject(bucket)) continue;
|
|
101
|
-
for (const entry of Object.values(bucket)) {
|
|
102
|
-
if (!isObject(entry)) continue;
|
|
103
|
-
const sections = entry.sections;
|
|
104
|
-
const instances = Array.isArray(sections) ? sections : isObject(sections) ? Object.values(sections) : [];
|
|
105
|
-
for (const inst of instances) {
|
|
106
|
-
if (isObject(inst) && isNonEmptyString(inst.type)) types.add(inst.type);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return types;
|
|
111
|
-
}
|
|
112
|
-
function validateSettingInto(bag, setting, pathPrefix, seenIds) {
|
|
113
|
-
if (!isObject(setting)) {
|
|
114
|
-
bag.err("schema.setting.invalid", "Setting must be an object.", pathPrefix);
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
const type = setting.type;
|
|
118
|
-
if (!isNonEmptyString(type)) {
|
|
119
|
-
bag.err("schema.setting.type.missing", "Setting is missing a `type`.", pathPrefix);
|
|
120
|
-
} else if (!KNOWN_SETTING_TYPES.has(type)) {
|
|
121
|
-
bag.warn(
|
|
122
|
-
"schema.setting.type.unknown",
|
|
123
|
-
`Unknown setting type "${type}" \u2014 the customizer will fall back to a text input.`,
|
|
124
|
-
pathPrefix
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
const presentational = isNonEmptyString(type) && PRESENTATIONAL_SETTING_TYPES.has(type);
|
|
128
|
-
if (!presentational) {
|
|
129
|
-
if (!isNonEmptyString(setting.id)) {
|
|
130
|
-
bag.err("schema.setting.id.missing", "Setting is missing an `id`.", pathPrefix);
|
|
131
|
-
} else {
|
|
132
|
-
if (seenIds.has(setting.id)) {
|
|
133
|
-
bag.err(
|
|
134
|
-
"schema.setting.id.duplicate",
|
|
135
|
-
`Duplicate setting id "${setting.id}".`,
|
|
136
|
-
pathPrefix
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
seenIds.add(setting.id);
|
|
140
|
-
}
|
|
141
|
-
if (!isNonEmptyString(setting.label)) {
|
|
142
|
-
bag.warn("schema.setting.label.missing", "Setting has no `label`.", pathPrefix);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
if (type === "select" || type === "radio") {
|
|
146
|
-
const options = setting.options;
|
|
147
|
-
if (!Array.isArray(options) || options.length === 0) {
|
|
148
|
-
bag.err(
|
|
149
|
-
"schema.setting.options.missing",
|
|
150
|
-
`"${type}" setting "${String(setting.id)}" must declare a non-empty \`options\` array.`,
|
|
151
|
-
pathPrefix
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
if (type === "range") {
|
|
156
|
-
if (typeof setting.min !== "number" || typeof setting.max !== "number") {
|
|
157
|
-
bag.err(
|
|
158
|
-
"schema.setting.range.bounds",
|
|
159
|
-
`"range" setting "${String(setting.id)}" must declare numeric \`min\` and \`max\`.`,
|
|
160
|
-
pathPrefix
|
|
161
|
-
);
|
|
162
|
-
} else if (setting.min >= setting.max) {
|
|
163
|
-
bag.err(
|
|
164
|
-
"schema.setting.range.order",
|
|
165
|
-
`"range" setting "${String(setting.id)}" has min >= max.`,
|
|
166
|
-
pathPrefix
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
function validateSectionSchema(schema, opts = {}) {
|
|
172
|
-
const bag = new IssueBag();
|
|
173
|
-
const where = opts.filenameType ? `${opts.filenameType}.json` : "section schema";
|
|
174
|
-
if (!isObject(schema)) {
|
|
175
|
-
bag.err("schema.invalid", "Section schema must be a JSON object.", where);
|
|
176
|
-
return bag.result();
|
|
177
|
-
}
|
|
178
|
-
const type = schema.type;
|
|
179
|
-
if (!isNonEmptyString(type)) {
|
|
180
|
-
bag.err("schema.type.missing", "Section schema is missing a `type`.", where);
|
|
181
|
-
} else {
|
|
182
|
-
if (!SECTION_TYPE_RE.test(type)) {
|
|
183
|
-
bag.err(
|
|
184
|
-
"schema.type.format",
|
|
185
|
-
`Section type "${type}" must match ${SECTION_TYPE_RE} (lowercase, start with a letter).`,
|
|
186
|
-
where
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
if (opts.filenameType && type !== opts.filenameType) {
|
|
190
|
-
bag.err(
|
|
191
|
-
"schema.type.filename_mismatch",
|
|
192
|
-
`Schema type "${type}" must equal its filename "${opts.filenameType}" (component-filename = schema-type convention).`,
|
|
193
|
-
where
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
if (!isNonEmptyString(schema.name)) {
|
|
198
|
-
bag.err("schema.name.missing", "Section schema is missing a `name`.", where);
|
|
199
|
-
}
|
|
200
|
-
if (schema.settings === void 0) {
|
|
201
|
-
bag.warn("schema.settings.missing", "Section schema has no `settings`.", where);
|
|
202
|
-
} else if (!Array.isArray(schema.settings)) {
|
|
203
|
-
bag.err("schema.settings.invalid", "`settings` must be an array.", where);
|
|
204
|
-
} else {
|
|
205
|
-
const seen = /* @__PURE__ */ new Set();
|
|
206
|
-
schema.settings.forEach(
|
|
207
|
-
(s, i) => validateSettingInto(bag, s, `${where}.settings[${i}]`, seen)
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
if (schema.blocks !== void 0) {
|
|
211
|
-
if (!Array.isArray(schema.blocks)) {
|
|
212
|
-
bag.err("schema.blocks.invalid", "`blocks` must be an array.", where);
|
|
213
|
-
} else {
|
|
214
|
-
schema.blocks.forEach((b, i) => {
|
|
215
|
-
if (!isObject(b) || !isNonEmptyString(b.type)) {
|
|
216
|
-
bag.err("schema.block.type.missing", `Block[${i}] is missing a \`type\`.`, where);
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
if (!SECTION_TYPE_RE.test(b.type)) {
|
|
220
|
-
bag.err(
|
|
221
|
-
"schema.block.type.format",
|
|
222
|
-
`Block type "${b.type}" must match ${SECTION_TYPE_RE}.`,
|
|
223
|
-
where
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
if (Array.isArray(b.settings)) {
|
|
227
|
-
const seen = /* @__PURE__ */ new Set();
|
|
228
|
-
b.settings.forEach(
|
|
229
|
-
(s, j) => validateSettingInto(bag, s, `${where}.blocks[${i}].settings[${j}]`, seen)
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
return bag.result();
|
|
236
|
-
}
|
|
237
|
-
function validateSettingsAgainstSchema(settings, schema, pathPrefix = "settings") {
|
|
238
|
-
const bag = new IssueBag();
|
|
239
|
-
if (!isObject(settings)) {
|
|
240
|
-
bag.err("instance.settings.invalid", "Section settings must be an object.", pathPrefix);
|
|
241
|
-
return bag.result();
|
|
242
|
-
}
|
|
243
|
-
const defs = Array.isArray(schema.settings) ? schema.settings : [];
|
|
244
|
-
const byId = /* @__PURE__ */ new Map();
|
|
245
|
-
for (const d of defs) if (isNonEmptyString(d?.id)) byId.set(d.id, d);
|
|
246
|
-
for (const [key, value] of Object.entries(settings)) {
|
|
247
|
-
const def = byId.get(key);
|
|
248
|
-
if (!def) {
|
|
249
|
-
bag.warn(
|
|
250
|
-
"instance.setting.unknown",
|
|
251
|
-
`Setting "${key}" is not declared in the "${String(schema.type)}" schema.`,
|
|
252
|
-
`${pathPrefix}.${key}`
|
|
253
|
-
);
|
|
254
|
-
continue;
|
|
255
|
-
}
|
|
256
|
-
if (value === null || value === void 0) continue;
|
|
257
|
-
const p = `${pathPrefix}.${key}`;
|
|
258
|
-
if ((def.type === "select" || def.type === "radio") && Array.isArray(def.options)) {
|
|
259
|
-
const allowed = def.options.map((o) => o.value);
|
|
260
|
-
if (typeof value === "string" && !allowed.includes(value)) {
|
|
261
|
-
bag.err(
|
|
262
|
-
"instance.setting.option.invalid",
|
|
263
|
-
`"${key}" = "${value}" is not one of: ${allowed.join(", ")}.`,
|
|
264
|
-
p
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
if (def.type === "range" && typeof value === "number") {
|
|
269
|
-
if (typeof def.min === "number" && value < def.min) {
|
|
270
|
-
bag.err("instance.setting.range.under", `"${key}" = ${value} is below min ${def.min}.`, p);
|
|
271
|
-
}
|
|
272
|
-
if (typeof def.max === "number" && value > def.max) {
|
|
273
|
-
bag.err("instance.setting.range.over", `"${key}" = ${value} is above max ${def.max}.`, p);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
if (def.type === "checkbox" && typeof value !== "boolean") {
|
|
277
|
-
bag.warn("instance.setting.type.mismatch", `"${key}" should be a boolean.`, p);
|
|
278
|
-
}
|
|
279
|
-
if ((def.type === "number" || def.type === "range") && typeof value !== "number") {
|
|
280
|
-
bag.warn("instance.setting.type.mismatch", `"${key}" should be a number.`, p);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
return bag.result();
|
|
284
|
-
}
|
|
285
|
-
function validateManifestCore(bag, m) {
|
|
286
|
-
if (!isNonEmptyString(m.id)) {
|
|
287
|
-
bag.err("manifest.id.missing", "theme.json is missing `id`.", "id");
|
|
288
|
-
} else if (!THEME_ID_RE.test(m.id)) {
|
|
289
|
-
bag.err(
|
|
290
|
-
"manifest.id.format",
|
|
291
|
-
`Theme id "${m.id}" must be lowercase alphanumeric with dashes/underscores (no leading/trailing separator).`,
|
|
292
|
-
"id"
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
const name = m.name;
|
|
296
|
-
const nameOk = isNonEmptyString(name) || isObject(name) && Object.values(name).some((v) => isNonEmptyString(v));
|
|
297
|
-
if (!nameOk) {
|
|
298
|
-
bag.err("manifest.name.missing", "theme.json is missing a non-empty `name`.", "name");
|
|
299
|
-
}
|
|
300
|
-
if (!isNonEmptyString(m.version)) {
|
|
301
|
-
bag.err("manifest.version.missing", "theme.json is missing `version`.", "version");
|
|
302
|
-
} else if (!SEMVER_RE.test(m.version)) {
|
|
303
|
-
bag.err("manifest.version.invalid", `Version "${m.version}" is not valid semver.`, "version");
|
|
304
|
-
}
|
|
305
|
-
if (!isNonEmptyString(m.author)) {
|
|
306
|
-
bag.err("manifest.author.missing", "theme.json is missing `author`.", "author");
|
|
307
|
-
}
|
|
308
|
-
if (m.min_sdk_version !== void 0 && !SEMVER_RE.test(String(m.min_sdk_version))) {
|
|
309
|
-
bag.warn("manifest.min_sdk_version.invalid", "`min_sdk_version` is not valid semver.", "min_sdk_version");
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
function validateManifest(manifest, ctx = {}) {
|
|
313
|
-
const bag = new IssueBag();
|
|
314
|
-
if (!isObject(manifest)) {
|
|
315
|
-
bag.err("manifest.invalid", "theme.json must be a JSON object.", "theme.json");
|
|
316
|
-
return bag.result();
|
|
317
|
-
}
|
|
318
|
-
validateManifestCore(bag, manifest);
|
|
319
|
-
const presets = manifest.presets;
|
|
320
|
-
if (!isObject(presets) || Object.keys(presets).length === 0) {
|
|
321
|
-
bag.warn("manifest.presets.empty", "theme.json has no presets \u2014 merchants start with an empty page.", "presets");
|
|
322
|
-
} else {
|
|
323
|
-
if (ctx.sectionTypes) {
|
|
324
|
-
for (const type of collectPresetSectionTypes(manifest)) {
|
|
325
|
-
if (!ctx.sectionTypes.has(type)) {
|
|
326
|
-
bag.err(
|
|
327
|
-
"manifest.preset.unknown_section",
|
|
328
|
-
`Preset references section type "${type}" with no schemas/sections/${type}.json.`,
|
|
329
|
-
"presets"
|
|
330
|
-
);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
const templates = isObject(presets.templates) ? presets.templates : {};
|
|
335
|
-
for (const t of REQUIRED_TEMPLATES) {
|
|
336
|
-
if (!(t in templates)) {
|
|
337
|
-
bag.warn(
|
|
338
|
-
"manifest.template.missing",
|
|
339
|
-
`No preset for required template "${t}" \u2014 the storefront will fall back to its built-in.`,
|
|
340
|
-
`presets.templates.${t}`
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
return bag.result();
|
|
346
|
-
}
|
|
347
|
-
function validateBuiltManifest(builtManifest, importMap, opts = {}) {
|
|
348
|
-
const bag = new IssueBag();
|
|
349
|
-
if (!isObject(builtManifest)) {
|
|
350
|
-
bag.err("built.invalid", "manifest.json must be a JSON object.", "manifest.json");
|
|
351
|
-
return bag.result();
|
|
352
|
-
}
|
|
353
|
-
validateManifestCore(bag, builtManifest);
|
|
354
|
-
const schemas = builtManifest.section_schemas;
|
|
355
|
-
const shipped = new Set(isObject(schemas) ? Object.keys(schemas) : []);
|
|
356
|
-
if (!isObject(schemas)) {
|
|
357
|
-
bag.warn("built.section_schemas.missing", "manifest.json has no `section_schemas`.", "section_schemas");
|
|
358
|
-
}
|
|
359
|
-
for (const type of collectPresetSectionTypes(builtManifest)) {
|
|
360
|
-
if (!shipped.has(type)) {
|
|
361
|
-
bag.err(
|
|
362
|
-
"built.preset.unknown_section",
|
|
363
|
-
`Preset references section type "${type}" not present in section_schemas.`,
|
|
364
|
-
"presets"
|
|
365
|
-
);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
if (isObject(schemas)) {
|
|
369
|
-
for (const [type, schema] of Object.entries(schemas)) {
|
|
370
|
-
const r = validateSectionSchema(schema, { filenameType: type });
|
|
371
|
-
for (const issue of r.issues) bag.issues.push(issue);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
if (!isNonEmptyString(builtManifest.plugin_version)) {
|
|
375
|
-
bag.warn("built.plugin_version.missing", "manifest.json has no `plugin_version`.", "plugin_version");
|
|
376
|
-
}
|
|
377
|
-
if (importMap !== void 0) {
|
|
378
|
-
if (!isObject(importMap)) {
|
|
379
|
-
bag.err("importmap.invalid", "import-map.json must be a JSON object.", "import-map.json");
|
|
380
|
-
} else {
|
|
381
|
-
const cv = importMap.contract_version;
|
|
382
|
-
if (typeof cv !== "number") {
|
|
383
|
-
bag.warn(
|
|
384
|
-
"importmap.contract_version.missing",
|
|
385
|
-
"import-map.json has no numeric `contract_version` \u2014 built by an older plugin.",
|
|
386
|
-
"contract_version"
|
|
387
|
-
);
|
|
388
|
-
} else if (typeof opts.hostContractVersion === "number" && cv > opts.hostContractVersion) {
|
|
389
|
-
bag.err(
|
|
390
|
-
"importmap.contract_version.incompatible",
|
|
391
|
-
`Theme built for contract v${cv}; this platform supports up to v${opts.hostContractVersion}.`,
|
|
392
|
-
"contract_version"
|
|
393
|
-
);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
return bag.result();
|
|
398
|
-
}
|
|
399
|
-
function mergeResults(...results) {
|
|
400
|
-
const issues = results.flatMap((r) => r.issues);
|
|
401
|
-
return { valid: !issues.some((i) => i.level === "error"), issues };
|
|
402
|
-
}
|
|
403
|
-
var ShopContext = react.createContext(null);
|
|
404
|
-
var ProductContext = react.createContext(null);
|
|
405
|
-
var CollectionContext = react.createContext(null);
|
|
406
|
-
var CartContext = react.createContext(null);
|
|
407
|
-
var CustomerContext = react.createContext(null);
|
|
408
|
-
var ThemeSettingsContext = react.createContext(null);
|
|
409
|
-
var LocalizationContext = react.createContext(null);
|
|
410
|
-
var PageContext = react.createContext(null);
|
|
411
|
-
var CurrentTemplateContext = react.createContext("home");
|
|
412
|
-
var NavigationContext = react.createContext(
|
|
413
|
-
{}
|
|
414
|
-
);
|
|
415
|
-
function useLocalization() {
|
|
416
|
-
const ctx = react.useContext(LocalizationContext);
|
|
417
|
-
if (!ctx) throw new Error("useLocalization must be used within NuMuProvider");
|
|
418
|
-
return ctx;
|
|
419
|
-
}
|
|
420
|
-
function useDirection() {
|
|
421
|
-
const { direction } = useLocalization();
|
|
422
|
-
return direction;
|
|
423
|
-
}
|
|
424
|
-
function useLocale() {
|
|
425
|
-
const { locale } = useLocalization();
|
|
426
|
-
return locale;
|
|
427
|
-
}
|
|
428
|
-
function useTranslation() {
|
|
429
|
-
const { translations, locale } = useLocalization();
|
|
430
|
-
return {
|
|
431
|
-
t: (key, fallback) => translations[key] || fallback || key,
|
|
432
|
-
locale
|
|
433
|
-
};
|
|
434
|
-
}
|
|
435
|
-
function useFieldTranslation(entity, field) {
|
|
436
|
-
const { locale } = useLocalization();
|
|
437
|
-
if (!entity) return void 0;
|
|
438
|
-
const translated = entity.attributes && typeof entity.attributes === "object" ? entity.attributes[`${field}_${locale}`] : void 0;
|
|
439
|
-
if (typeof translated === "string" && translated) return translated;
|
|
440
|
-
const base = entity[field];
|
|
441
|
-
if (typeof base === "string") return base;
|
|
442
|
-
if (base == null) return void 0;
|
|
443
|
-
return String(base);
|
|
444
|
-
}
|
|
445
|
-
function useNumberFormat() {
|
|
446
|
-
const { formatNumber } = useLocalization();
|
|
447
|
-
return formatNumber;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// src/hooks/useShop.ts
|
|
451
|
-
var DEFAULT_PROTOCOL = typeof window !== "undefined" ? window.location.protocol.replace(":", "") : "https";
|
|
452
|
-
function resolveDomain(store) {
|
|
453
|
-
if (store.domain) return store.domain;
|
|
454
|
-
if (store.subdomain) {
|
|
455
|
-
const platform = typeof window !== "undefined" && window.__NUMU_PLATFORM_DOMAIN || "numueg.app";
|
|
456
|
-
return `${store.subdomain}.${platform}`;
|
|
457
|
-
}
|
|
458
|
-
return store.slug;
|
|
459
|
-
}
|
|
460
|
-
function useShop() {
|
|
461
|
-
const ctx = react.useContext(ShopContext);
|
|
462
|
-
if (!ctx) throw new Error("useShop must be used within NuMuProvider");
|
|
463
|
-
const locale = useLocale();
|
|
464
|
-
return react.useMemo(() => {
|
|
465
|
-
const domain = resolveDomain(ctx);
|
|
466
|
-
const settings = ctx.settings;
|
|
467
|
-
const localePrefixEnabled = Boolean(
|
|
468
|
-
settings && settings.locale_url_prefix_enabled
|
|
469
|
-
);
|
|
470
|
-
const defaultLocale = ctx.default_language || "en";
|
|
471
|
-
const prefixedLocales = settings?.locale_url_prefix_locales || null;
|
|
472
|
-
const shouldPrefix = (l) => {
|
|
473
|
-
if (!localePrefixEnabled) return false;
|
|
474
|
-
if (prefixedLocales) return prefixedLocales.includes(l);
|
|
475
|
-
return l !== defaultLocale;
|
|
476
|
-
};
|
|
477
|
-
const formatUrl = (path) => {
|
|
478
|
-
if (!path) return `${DEFAULT_PROTOCOL}://${domain}/`;
|
|
479
|
-
if (/^https?:\/\//i.test(path)) return path;
|
|
480
|
-
if (path.startsWith("//")) return `${DEFAULT_PROTOCOL}:${path}`;
|
|
481
|
-
let normalized = path.startsWith("/") ? path : `/${path}`;
|
|
482
|
-
if (locale && shouldPrefix(locale)) {
|
|
483
|
-
const prefix = `/${locale}/`;
|
|
484
|
-
const root = `/${locale}`;
|
|
485
|
-
if (normalized !== root && !normalized.startsWith(prefix)) {
|
|
486
|
-
normalized = `${root}${normalized}`;
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
return `${DEFAULT_PROTOCOL}://${domain}${normalized}`;
|
|
490
|
-
};
|
|
491
|
-
return { ...ctx, domain, formatUrl };
|
|
492
|
-
}, [ctx, locale]);
|
|
493
|
-
}
|
|
494
12
|
function useProduct() {
|
|
495
|
-
const ctx = react.useContext(ProductContext);
|
|
13
|
+
const ctx = react.useContext(chunkIBNAZRUT_cjs.ProductContext);
|
|
496
14
|
if (!ctx) throw new Error("useProduct must be used within ProductProvider");
|
|
497
15
|
return ctx;
|
|
498
16
|
}
|
|
499
17
|
function useProductOptional() {
|
|
500
|
-
return react.useContext(ProductContext);
|
|
18
|
+
return react.useContext(chunkIBNAZRUT_cjs.ProductContext);
|
|
501
19
|
}
|
|
502
20
|
function useCollection() {
|
|
503
|
-
const ctx = react.useContext(CollectionContext);
|
|
21
|
+
const ctx = react.useContext(chunkIBNAZRUT_cjs.CollectionContext);
|
|
504
22
|
if (!ctx) throw new Error("useCollection must be used within CollectionProvider");
|
|
505
23
|
return ctx;
|
|
506
24
|
}
|
|
507
25
|
function useCollectionOptional() {
|
|
508
|
-
return react.useContext(CollectionContext);
|
|
26
|
+
return react.useContext(chunkIBNAZRUT_cjs.CollectionContext);
|
|
509
27
|
}
|
|
510
28
|
function useCart() {
|
|
511
|
-
const ctx = react.useContext(CartContext);
|
|
29
|
+
const ctx = react.useContext(chunkIBNAZRUT_cjs.CartContext);
|
|
512
30
|
if (!ctx) throw new Error("useCart must be used within NuMuProvider");
|
|
513
31
|
return ctx;
|
|
514
32
|
}
|
|
515
|
-
function useCustomer() {
|
|
516
|
-
return react.useContext(CustomerContext);
|
|
517
|
-
}
|
|
518
|
-
function useThemeSettings() {
|
|
519
|
-
const ctx = react.useContext(ThemeSettingsContext);
|
|
520
|
-
if (!ctx) throw new Error("useThemeSettings must be used within NuMuProvider");
|
|
521
|
-
return ctx;
|
|
522
|
-
}
|
|
523
33
|
function useCurrentTemplate() {
|
|
524
|
-
return react.useContext(CurrentTemplateContext);
|
|
525
|
-
}
|
|
526
|
-
function usePage() {
|
|
527
|
-
return react.useContext(PageContext);
|
|
34
|
+
return react.useContext(chunkIBNAZRUT_cjs.CurrentTemplateContext);
|
|
528
35
|
}
|
|
529
36
|
var SectionContext = react.createContext(null);
|
|
530
37
|
function useSection() {
|
|
@@ -535,13 +42,57 @@ function useSection() {
|
|
|
535
42
|
function useSectionOptional() {
|
|
536
43
|
return react.useContext(SectionContext);
|
|
537
44
|
}
|
|
45
|
+
var EMPTY = Object.freeze([]);
|
|
46
|
+
function useSectionGroup(group) {
|
|
47
|
+
const settings = react.useContext(chunkIBNAZRUT_cjs.ThemeSettingsContext);
|
|
48
|
+
return react.useMemo(() => {
|
|
49
|
+
const grp = settings?.section_groups?.[group];
|
|
50
|
+
if (!grp || !Array.isArray(grp.order) || grp.order.length === 0) {
|
|
51
|
+
return EMPTY;
|
|
52
|
+
}
|
|
53
|
+
const sections = grp.sections ?? {};
|
|
54
|
+
const out = [];
|
|
55
|
+
for (const id of grp.order) {
|
|
56
|
+
const instance = sections[id];
|
|
57
|
+
if (!instance) continue;
|
|
58
|
+
out.push({ id, ...instance });
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}, [settings, group]);
|
|
62
|
+
}
|
|
63
|
+
function useCurrency() {
|
|
64
|
+
const ctx = react.useContext(chunkIBNAZRUT_cjs.CurrencyContext);
|
|
65
|
+
const shop = chunkIBNAZRUT_cjs.useShop();
|
|
66
|
+
const fallback = react.useMemo(() => {
|
|
67
|
+
const ccy = shop?.currency || "EGP";
|
|
68
|
+
return {
|
|
69
|
+
base: ccy,
|
|
70
|
+
selected: ccy,
|
|
71
|
+
presentment: [ccy],
|
|
72
|
+
rates: {},
|
|
73
|
+
autoConvert: false,
|
|
74
|
+
loading: false,
|
|
75
|
+
setSelected: () => {
|
|
76
|
+
},
|
|
77
|
+
convert: (cents) => cents
|
|
78
|
+
};
|
|
79
|
+
}, [shop?.currency]);
|
|
80
|
+
return ctx ?? fallback;
|
|
81
|
+
}
|
|
538
82
|
|
|
539
83
|
// src/hooks/useMoney.ts
|
|
540
84
|
function useMoney(currencyOverride) {
|
|
541
|
-
const { formatMoney } = useLocalization();
|
|
542
|
-
const shop = useShop();
|
|
543
|
-
const
|
|
544
|
-
return (amount) =>
|
|
85
|
+
const { formatMoney } = chunkIBNAZRUT_cjs.useLocalization();
|
|
86
|
+
const shop = chunkIBNAZRUT_cjs.useShop();
|
|
87
|
+
const { selected, base, autoConvert, convert } = useCurrency();
|
|
88
|
+
return (amount) => {
|
|
89
|
+
const shouldConvert = autoConvert && !currencyOverride && !!selected && selected !== base;
|
|
90
|
+
if (shouldConvert) {
|
|
91
|
+
const converted = convert(Math.round(amount * 100), selected) / 100;
|
|
92
|
+
return formatMoney(converted, selected);
|
|
93
|
+
}
|
|
94
|
+
return formatMoney(amount, currencyOverride || shop?.currency);
|
|
95
|
+
};
|
|
545
96
|
}
|
|
546
97
|
var DEFAULT_WIDTHS = [320, 480, 640, 768, 1024, 1280, 1600, 1920];
|
|
547
98
|
var DEFAULT_SIZES = "(min-width: 1024px) 25vw, (min-width: 640px) 50vw, 100vw";
|
|
@@ -555,81 +106,6 @@ function useImage(src, opts = {}) {
|
|
|
555
106
|
return { src, srcSet, sizes, alt };
|
|
556
107
|
}, [src, sizes, widths, alt]);
|
|
557
108
|
}
|
|
558
|
-
function useProducts(opts = {}) {
|
|
559
|
-
const { limit, fetchIfMissing = false } = opts;
|
|
560
|
-
const page = usePage();
|
|
561
|
-
const shop = useShop();
|
|
562
|
-
const initial = page?.data?.products ?? null;
|
|
563
|
-
const [products, setProducts] = react.useState(initial ?? []);
|
|
564
|
-
const [loading, setLoading] = react.useState(
|
|
565
|
-
initial == null && fetchIfMissing
|
|
566
|
-
);
|
|
567
|
-
const [error, setError] = react.useState(null);
|
|
568
|
-
react.useEffect(() => {
|
|
569
|
-
if (initial != null) return;
|
|
570
|
-
if (!fetchIfMissing) return;
|
|
571
|
-
if (!shop?.id) return;
|
|
572
|
-
let cancelled = false;
|
|
573
|
-
(async () => {
|
|
574
|
-
try {
|
|
575
|
-
const params = new URLSearchParams({ store_id: shop.id });
|
|
576
|
-
if (limit) params.set("limit", String(limit));
|
|
577
|
-
const res = await fetch(`/api/products?${params.toString()}`);
|
|
578
|
-
if (!res.ok) throw new Error(`/api/products \u2192 ${res.status}`);
|
|
579
|
-
const data = await res.json();
|
|
580
|
-
if (cancelled) return;
|
|
581
|
-
setProducts(data.products ?? []);
|
|
582
|
-
setLoading(false);
|
|
583
|
-
} catch (err) {
|
|
584
|
-
if (cancelled) return;
|
|
585
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
586
|
-
setLoading(false);
|
|
587
|
-
}
|
|
588
|
-
})();
|
|
589
|
-
return () => {
|
|
590
|
-
cancelled = true;
|
|
591
|
-
};
|
|
592
|
-
}, [initial, fetchIfMissing, limit, shop?.id]);
|
|
593
|
-
const sliced = limit != null ? products.slice(0, limit) : products;
|
|
594
|
-
return { products: sliced, loading, error };
|
|
595
|
-
}
|
|
596
|
-
function useCollections(opts = {}) {
|
|
597
|
-
const { limit, fetchIfMissing = false } = opts;
|
|
598
|
-
const page = usePage();
|
|
599
|
-
const shop = useShop();
|
|
600
|
-
const initial = page?.data?.collections ?? null;
|
|
601
|
-
const [collections, setCollections] = react.useState(initial ?? []);
|
|
602
|
-
const [loading, setLoading] = react.useState(
|
|
603
|
-
initial == null && fetchIfMissing
|
|
604
|
-
);
|
|
605
|
-
const [error, setError] = react.useState(null);
|
|
606
|
-
react.useEffect(() => {
|
|
607
|
-
if (initial != null) return;
|
|
608
|
-
if (!fetchIfMissing) return;
|
|
609
|
-
if (!shop?.id) return;
|
|
610
|
-
let cancelled = false;
|
|
611
|
-
(async () => {
|
|
612
|
-
try {
|
|
613
|
-
const params = new URLSearchParams({ store_id: shop.id });
|
|
614
|
-
const res = await fetch(`/api/collections?${params.toString()}`);
|
|
615
|
-
if (!res.ok) throw new Error(`/api/collections \u2192 ${res.status}`);
|
|
616
|
-
const data = await res.json();
|
|
617
|
-
if (cancelled) return;
|
|
618
|
-
setCollections(data.collections ?? []);
|
|
619
|
-
setLoading(false);
|
|
620
|
-
} catch (err) {
|
|
621
|
-
if (cancelled) return;
|
|
622
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
623
|
-
setLoading(false);
|
|
624
|
-
}
|
|
625
|
-
})();
|
|
626
|
-
return () => {
|
|
627
|
-
cancelled = true;
|
|
628
|
-
};
|
|
629
|
-
}, [initial, fetchIfMissing, shop?.id]);
|
|
630
|
-
const sliced = limit != null ? collections.slice(0, limit) : collections;
|
|
631
|
-
return { collections: sliced, loading, error };
|
|
632
|
-
}
|
|
633
109
|
var NOOP_ACTIONS = {
|
|
634
110
|
login: async () => ({}),
|
|
635
111
|
register: async () => ({}),
|
|
@@ -655,7 +131,7 @@ function unwrap(json) {
|
|
|
655
131
|
return json;
|
|
656
132
|
}
|
|
657
133
|
function useOrders() {
|
|
658
|
-
const customer = useCustomer();
|
|
134
|
+
const customer = chunkIBNAZRUT_cjs.useCustomer();
|
|
659
135
|
const [orders, setOrders] = react.useState([]);
|
|
660
136
|
const [loading, setLoading] = react.useState(false);
|
|
661
137
|
const [error, setError] = react.useState(null);
|
|
@@ -702,7 +178,7 @@ function useOrders() {
|
|
|
702
178
|
};
|
|
703
179
|
}
|
|
704
180
|
function useOrder(id) {
|
|
705
|
-
const customer = useCustomer();
|
|
181
|
+
const customer = chunkIBNAZRUT_cjs.useCustomer();
|
|
706
182
|
const [order, setOrder] = react.useState(null);
|
|
707
183
|
const [loading, setLoading] = react.useState(false);
|
|
708
184
|
const [error, setError] = react.useState(null);
|
|
@@ -765,7 +241,7 @@ async function readBody(res) {
|
|
|
765
241
|
}
|
|
766
242
|
}
|
|
767
243
|
function useCustomerAddresses() {
|
|
768
|
-
const customer = useCustomer();
|
|
244
|
+
const customer = chunkIBNAZRUT_cjs.useCustomer();
|
|
769
245
|
const [addresses, setAddresses] = react.useState([]);
|
|
770
246
|
const [loading, setLoading] = react.useState(false);
|
|
771
247
|
const [error, setError] = react.useState(null);
|
|
@@ -928,8 +404,8 @@ function toNavigationItem(raw, locale) {
|
|
|
928
404
|
};
|
|
929
405
|
}
|
|
930
406
|
function useNavigation(handle, options) {
|
|
931
|
-
const navMap = react.useContext(NavigationContext);
|
|
932
|
-
const localization = react.useContext(LocalizationContext);
|
|
407
|
+
const navMap = react.useContext(chunkIBNAZRUT_cjs.NavigationContext);
|
|
408
|
+
const localization = react.useContext(chunkIBNAZRUT_cjs.LocalizationContext);
|
|
933
409
|
const locale = localization?.locale ?? "en";
|
|
934
410
|
const hostProvidedAny = !!navMap && Object.keys(navMap).length > 0;
|
|
935
411
|
const rawItems = handle ? navMap?.[handle] : void 0;
|
|
@@ -999,7 +475,7 @@ function useNavigation(handle, options) {
|
|
|
999
475
|
}, [handle, hostItems === null]);
|
|
1000
476
|
return { items, loading, error };
|
|
1001
477
|
}
|
|
1002
|
-
var
|
|
478
|
+
var EMPTY2 = {
|
|
1003
479
|
products: [],
|
|
1004
480
|
collections: [],
|
|
1005
481
|
pages: [],
|
|
@@ -1014,7 +490,7 @@ function useSearch(query, options = {}) {
|
|
|
1014
490
|
debounceMs = 200,
|
|
1015
491
|
immediate = false
|
|
1016
492
|
} = options;
|
|
1017
|
-
const [results, setResults] = react.useState(
|
|
493
|
+
const [results, setResults] = react.useState(EMPTY2);
|
|
1018
494
|
const [loading, setLoading] = react.useState(false);
|
|
1019
495
|
const [error, setError] = react.useState(null);
|
|
1020
496
|
const lastRequestId = react.useRef(0);
|
|
@@ -1022,7 +498,7 @@ function useSearch(query, options = {}) {
|
|
|
1022
498
|
if (typeof window === "undefined") return;
|
|
1023
499
|
const trimmed = query.trim();
|
|
1024
500
|
if (!trimmed) {
|
|
1025
|
-
setResults(
|
|
501
|
+
setResults(EMPTY2);
|
|
1026
502
|
setLoading(false);
|
|
1027
503
|
setError(null);
|
|
1028
504
|
return;
|
|
@@ -1040,7 +516,7 @@ function useSearch(query, options = {}) {
|
|
|
1040
516
|
});
|
|
1041
517
|
if (requestId !== lastRequestId.current) return;
|
|
1042
518
|
if (!res.ok) {
|
|
1043
|
-
setResults(
|
|
519
|
+
setResults(EMPTY2);
|
|
1044
520
|
return;
|
|
1045
521
|
}
|
|
1046
522
|
const json = await res.json();
|
|
@@ -1056,7 +532,7 @@ function useSearch(query, options = {}) {
|
|
|
1056
532
|
} catch (err) {
|
|
1057
533
|
if (requestId !== lastRequestId.current) return;
|
|
1058
534
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
1059
|
-
setResults(
|
|
535
|
+
setResults(EMPTY2);
|
|
1060
536
|
} finally {
|
|
1061
537
|
if (requestId === lastRequestId.current) setLoading(false);
|
|
1062
538
|
}
|
|
@@ -1161,63 +637,218 @@ function useAnalytics() {
|
|
|
1161
637
|
);
|
|
1162
638
|
return react.useMemo(() => ({ track }), [track]);
|
|
1163
639
|
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
640
|
+
var cache2 = /* @__PURE__ */ new Map();
|
|
641
|
+
function buildSnapshot(entry) {
|
|
642
|
+
return {
|
|
643
|
+
data: entry.data,
|
|
644
|
+
error: entry.error,
|
|
645
|
+
isLoading: !entry.settled,
|
|
646
|
+
isValidating: entry.inFlight
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
function commit(entry) {
|
|
650
|
+
entry.snapshot = buildSnapshot(entry);
|
|
651
|
+
entry.subscribers.forEach((cb) => cb());
|
|
652
|
+
}
|
|
653
|
+
function getEntry(key, initialData) {
|
|
654
|
+
let entry = cache2.get(key);
|
|
655
|
+
if (!entry) {
|
|
656
|
+
entry = {
|
|
657
|
+
data: initialData,
|
|
658
|
+
error: void 0,
|
|
659
|
+
settled: false,
|
|
660
|
+
inFlight: false,
|
|
661
|
+
seq: 0,
|
|
662
|
+
lastFetchAt: 0,
|
|
663
|
+
promise: void 0,
|
|
664
|
+
controller: void 0,
|
|
665
|
+
fetcher: void 0,
|
|
666
|
+
subscribers: /* @__PURE__ */ new Set(),
|
|
667
|
+
snapshot: {
|
|
668
|
+
data: initialData,
|
|
669
|
+
error: void 0,
|
|
670
|
+
isLoading: true,
|
|
671
|
+
isValidating: false
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
cache2.set(key, entry);
|
|
675
|
+
}
|
|
676
|
+
return entry;
|
|
677
|
+
}
|
|
678
|
+
function revalidateResource(key, fetcher, opts) {
|
|
679
|
+
const entry = getEntry(key);
|
|
680
|
+
entry.fetcher = fetcher;
|
|
681
|
+
if (!opts?.force && entry.promise) return entry.promise;
|
|
682
|
+
const mySeq = ++entry.seq;
|
|
683
|
+
if (entry.controller) entry.controller.abort();
|
|
684
|
+
const controller = new AbortController();
|
|
685
|
+
entry.controller = controller;
|
|
686
|
+
entry.inFlight = true;
|
|
687
|
+
commit(entry);
|
|
688
|
+
const run = (async () => {
|
|
1173
689
|
try {
|
|
690
|
+
const data = await fetcher(controller.signal);
|
|
691
|
+
if (mySeq !== entry.seq) return;
|
|
692
|
+
entry.data = data;
|
|
693
|
+
entry.error = void 0;
|
|
694
|
+
entry.settled = true;
|
|
695
|
+
entry.lastFetchAt = Date.now();
|
|
696
|
+
} catch (err) {
|
|
697
|
+
if (controller.signal.aborted) return;
|
|
698
|
+
if (mySeq !== entry.seq) return;
|
|
699
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
700
|
+
entry.error = error;
|
|
701
|
+
entry.settled = true;
|
|
702
|
+
entry.lastFetchAt = Date.now();
|
|
703
|
+
opts?.onError?.(error, key);
|
|
704
|
+
} finally {
|
|
705
|
+
if (mySeq === entry.seq) {
|
|
706
|
+
entry.inFlight = false;
|
|
707
|
+
entry.controller = void 0;
|
|
708
|
+
commit(entry);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
})();
|
|
712
|
+
entry.promise = run;
|
|
713
|
+
void run.finally(() => {
|
|
714
|
+
if (entry.promise === run) entry.promise = void 0;
|
|
715
|
+
});
|
|
716
|
+
return run;
|
|
717
|
+
}
|
|
718
|
+
function mutateResource(key, next, opts) {
|
|
719
|
+
const entry = getEntry(key);
|
|
720
|
+
const value = typeof next === "function" ? next(entry.data) : next;
|
|
721
|
+
entry.data = value;
|
|
722
|
+
entry.error = void 0;
|
|
723
|
+
entry.settled = true;
|
|
724
|
+
commit(entry);
|
|
725
|
+
if (opts?.revalidate && entry.fetcher) {
|
|
726
|
+
void revalidateResource(key, entry.fetcher, { force: true });
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
function readResource(key) {
|
|
730
|
+
return cache2.get(key)?.data;
|
|
731
|
+
}
|
|
732
|
+
var NOOP = () => {
|
|
733
|
+
};
|
|
734
|
+
function useCachedResource(key, fetcher, options) {
|
|
735
|
+
const enabled = options?.enabled !== false && key != null && key !== "";
|
|
736
|
+
const revalidateOnMount = options?.revalidateOnMount !== false;
|
|
737
|
+
const dedupeIntervalMs = options?.dedupeIntervalMs ?? 2e3;
|
|
738
|
+
const cacheKey = enabled ? key : "";
|
|
739
|
+
const fetcherRef = react.useRef(fetcher);
|
|
740
|
+
fetcherRef.current = fetcher;
|
|
741
|
+
const onErrorRef = react.useRef(options?.onError);
|
|
742
|
+
onErrorRef.current = options?.onError;
|
|
743
|
+
const initialDataRef = react.useRef(options?.initialData);
|
|
744
|
+
const fallbackRef = react.useRef(null);
|
|
745
|
+
if (fallbackRef.current === null) {
|
|
746
|
+
fallbackRef.current = {
|
|
747
|
+
data: initialDataRef.current,
|
|
748
|
+
error: void 0,
|
|
749
|
+
isLoading: enabled && revalidateOnMount,
|
|
750
|
+
isValidating: false
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
const subscribe = react.useCallback(
|
|
754
|
+
(cb) => {
|
|
755
|
+
if (!enabled) return NOOP;
|
|
756
|
+
const entry = getEntry(cacheKey, initialDataRef.current);
|
|
757
|
+
entry.subscribers.add(cb);
|
|
758
|
+
return () => {
|
|
759
|
+
entry.subscribers.delete(cb);
|
|
760
|
+
};
|
|
761
|
+
},
|
|
762
|
+
[cacheKey, enabled]
|
|
763
|
+
);
|
|
764
|
+
const getSnapshot = react.useCallback(() => {
|
|
765
|
+
if (!enabled) return fallbackRef.current;
|
|
766
|
+
return getEntry(cacheKey, initialDataRef.current).snapshot;
|
|
767
|
+
}, [cacheKey, enabled]);
|
|
768
|
+
const getServerSnapshot = react.useCallback(
|
|
769
|
+
() => fallbackRef.current,
|
|
770
|
+
[]
|
|
771
|
+
);
|
|
772
|
+
const state = react.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
773
|
+
react.useEffect(() => {
|
|
774
|
+
if (!enabled) return;
|
|
775
|
+
const entry = getEntry(cacheKey, initialDataRef.current);
|
|
776
|
+
const fresh = entry.settled && Date.now() - entry.lastFetchAt < dedupeIntervalMs;
|
|
777
|
+
if (revalidateOnMount && !fresh) {
|
|
778
|
+
void revalidateResource(
|
|
779
|
+
cacheKey,
|
|
780
|
+
(signal) => fetcherRef.current(signal),
|
|
781
|
+
{ onError: (e, k) => onErrorRef.current?.(e, k) }
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
}, [cacheKey, enabled, revalidateOnMount, dedupeIntervalMs]);
|
|
785
|
+
const mutate = react.useCallback(
|
|
786
|
+
(next, opts) => {
|
|
787
|
+
if (!enabled) return;
|
|
788
|
+
mutateResource(cacheKey, next, opts);
|
|
789
|
+
},
|
|
790
|
+
[cacheKey, enabled]
|
|
791
|
+
);
|
|
792
|
+
const revalidate = react.useCallback(() => {
|
|
793
|
+
if (!enabled) return Promise.resolve();
|
|
794
|
+
return revalidateResource(
|
|
795
|
+
cacheKey,
|
|
796
|
+
(signal) => fetcherRef.current(signal),
|
|
797
|
+
{ force: true, onError: (e, k) => onErrorRef.current?.(e, k) }
|
|
798
|
+
);
|
|
799
|
+
}, [cacheKey, enabled]);
|
|
800
|
+
return {
|
|
801
|
+
data: state.data,
|
|
802
|
+
error: state.error,
|
|
803
|
+
isLoading: state.isLoading,
|
|
804
|
+
isValidating: state.isValidating,
|
|
805
|
+
mutate,
|
|
806
|
+
revalidate
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// src/hooks/useApp.ts
|
|
811
|
+
function useApp(slug) {
|
|
812
|
+
const shop = chunkIBNAZRUT_cjs.useShop();
|
|
813
|
+
const key = slug ? `numu:app:${shop.id}:${slug}` : "";
|
|
814
|
+
const fetcher = react.useCallback(
|
|
815
|
+
async (signal) => {
|
|
1174
816
|
const url = `/api/storefront/apps/${encodeURIComponent(slug)}`;
|
|
1175
817
|
const res = await fetch(url, {
|
|
1176
818
|
credentials: "include",
|
|
1177
|
-
headers: { Accept: "application/json" }
|
|
819
|
+
headers: { Accept: "application/json" },
|
|
820
|
+
signal
|
|
1178
821
|
});
|
|
1179
822
|
if (res.status === 404) {
|
|
1180
|
-
|
|
1181
|
-
data: null,
|
|
1182
|
-
loading: false,
|
|
1183
|
-
available: false,
|
|
1184
|
-
error: null
|
|
1185
|
-
});
|
|
1186
|
-
return;
|
|
823
|
+
return { payload: null, available: false };
|
|
1187
824
|
}
|
|
1188
825
|
if (!res.ok) {
|
|
1189
826
|
throw new Error(`useApp(${slug}) failed: HTTP ${res.status}`);
|
|
1190
827
|
}
|
|
1191
828
|
const body = await res.json();
|
|
1192
|
-
const payload = body.data;
|
|
1193
|
-
if (!payload) {
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
}
|
|
1216
|
-
}, [slug, shop.id]);
|
|
1217
|
-
react.useEffect(() => {
|
|
1218
|
-
void fetchApp();
|
|
1219
|
-
}, [fetchApp]);
|
|
1220
|
-
return { ...state, refresh: fetchApp };
|
|
829
|
+
const payload = body.data ?? null;
|
|
830
|
+
if (!payload) return { payload: null, available: false };
|
|
831
|
+
return {
|
|
832
|
+
payload,
|
|
833
|
+
available: payload.available !== false
|
|
834
|
+
};
|
|
835
|
+
},
|
|
836
|
+
[slug]
|
|
837
|
+
);
|
|
838
|
+
const { data, isLoading, error, revalidate } = useCachedResource(key, fetcher, {
|
|
839
|
+
enabled: Boolean(slug),
|
|
840
|
+
initialData: { payload: null, available: false }
|
|
841
|
+
});
|
|
842
|
+
const refresh = react.useCallback(async () => {
|
|
843
|
+
await revalidate();
|
|
844
|
+
}, [revalidate]);
|
|
845
|
+
return {
|
|
846
|
+
data: error ? null : data?.payload ?? null,
|
|
847
|
+
loading: isLoading,
|
|
848
|
+
available: error ? false : data?.available ?? false,
|
|
849
|
+
error: error ?? null,
|
|
850
|
+
refresh
|
|
851
|
+
};
|
|
1221
852
|
}
|
|
1222
853
|
function storageKey(storeId) {
|
|
1223
854
|
return `numu_wishlist_${storeId}`;
|
|
@@ -1233,114 +864,100 @@ function readLocal(storeId) {
|
|
|
1233
864
|
return [];
|
|
1234
865
|
}
|
|
1235
866
|
}
|
|
1236
|
-
function
|
|
867
|
+
function persistWishlist(storeId, items) {
|
|
1237
868
|
if (typeof window === "undefined") return;
|
|
1238
|
-
|
|
1239
|
-
window.localStorage.setItem(storageKey(storeId), JSON.stringify(items));
|
|
1240
|
-
} catch {
|
|
1241
|
-
}
|
|
869
|
+
window.localStorage.setItem(storageKey(storeId), JSON.stringify(items));
|
|
1242
870
|
}
|
|
1243
871
|
function sameItem(a, productId, variantId) {
|
|
1244
872
|
return a.product_id === productId && (a.variant_id ?? null) === (variantId ?? null);
|
|
1245
873
|
}
|
|
874
|
+
var EMPTY3 = [];
|
|
1246
875
|
function useWishlist(storeId) {
|
|
1247
|
-
const
|
|
1248
|
-
const
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
876
|
+
const key = storageKey(storeId);
|
|
877
|
+
const fetcher = react.useCallback(
|
|
878
|
+
() => Promise.resolve(readLocal(storeId)),
|
|
879
|
+
[storeId]
|
|
880
|
+
);
|
|
881
|
+
const { data, isLoading, mutate } = useCachedResource(
|
|
882
|
+
key,
|
|
883
|
+
fetcher,
|
|
884
|
+
{ initialData: EMPTY3 }
|
|
885
|
+
);
|
|
886
|
+
const items = data ?? EMPTY3;
|
|
1255
887
|
const has = react.useCallback(
|
|
1256
888
|
(productId, variantId) => items.some((it) => sameItem(it, productId, variantId)),
|
|
1257
889
|
[items]
|
|
1258
890
|
);
|
|
891
|
+
const applyOptimistic = react.useCallback(
|
|
892
|
+
(compute) => {
|
|
893
|
+
const prev = readResource(key) ?? EMPTY3;
|
|
894
|
+
const next = compute(prev);
|
|
895
|
+
if (next === null) return;
|
|
896
|
+
mutate(next, { revalidate: false });
|
|
897
|
+
try {
|
|
898
|
+
persistWishlist(storeId, next);
|
|
899
|
+
} catch {
|
|
900
|
+
mutate(prev, { revalidate: false });
|
|
901
|
+
}
|
|
902
|
+
},
|
|
903
|
+
[key, storeId, mutate]
|
|
904
|
+
);
|
|
1259
905
|
const addToWishlist = react.useCallback(
|
|
1260
|
-
(productId, variantId) =>
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
906
|
+
(productId, variantId) => applyOptimistic(
|
|
907
|
+
(prev) => prev.some((it) => sameItem(it, productId, variantId)) ? null : [
|
|
908
|
+
...prev,
|
|
909
|
+
{
|
|
910
|
+
product_id: productId,
|
|
911
|
+
variant_id: variantId ?? null,
|
|
912
|
+
added_at: Date.now()
|
|
1264
913
|
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
product_id: productId,
|
|
1269
|
-
variant_id: variantId ?? null,
|
|
1270
|
-
added_at: Date.now()
|
|
1271
|
-
}
|
|
1272
|
-
];
|
|
1273
|
-
writeLocal(storeId, next);
|
|
1274
|
-
return next;
|
|
1275
|
-
});
|
|
1276
|
-
},
|
|
1277
|
-
[storeId]
|
|
914
|
+
]
|
|
915
|
+
),
|
|
916
|
+
[applyOptimistic]
|
|
1278
917
|
);
|
|
1279
918
|
const removeFromWishlist = react.useCallback(
|
|
1280
|
-
(productId, variantId) => {
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
});
|
|
1286
|
-
},
|
|
1287
|
-
[storeId]
|
|
919
|
+
(productId, variantId) => applyOptimistic((prev) => {
|
|
920
|
+
const next = prev.filter((it) => !sameItem(it, productId, variantId));
|
|
921
|
+
return next.length === prev.length ? null : next;
|
|
922
|
+
}),
|
|
923
|
+
[applyOptimistic]
|
|
1288
924
|
);
|
|
1289
|
-
const clear = react.useCallback(
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
return { items, loading, has, addToWishlist, removeFromWishlist, clear };
|
|
925
|
+
const clear = react.useCallback(
|
|
926
|
+
() => applyOptimistic((prev) => prev.length === 0 ? null : []),
|
|
927
|
+
[applyOptimistic]
|
|
928
|
+
);
|
|
929
|
+
return { items, loading: isLoading, has, addToWishlist, removeFromWishlist, clear };
|
|
1294
930
|
}
|
|
931
|
+
var EMPTY4 = [];
|
|
1295
932
|
function useRelatedProducts(productId, options = {}) {
|
|
1296
933
|
const limit = options.limit ?? 4;
|
|
1297
|
-
const
|
|
1298
|
-
const
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
let cancelled = false;
|
|
1309
|
-
setLoading(true);
|
|
1310
|
-
setError(null);
|
|
1311
|
-
void (async () => {
|
|
1312
|
-
try {
|
|
1313
|
-
const res = await fetch(
|
|
1314
|
-
`/api/storefront/products/${encodeURIComponent(productId)}/related?limit=${limit}`,
|
|
1315
|
-
{ cache: "no-store" }
|
|
1316
|
-
);
|
|
1317
|
-
if (!res.ok) {
|
|
1318
|
-
if (cancelled) return;
|
|
1319
|
-
setItems([]);
|
|
1320
|
-
return;
|
|
1321
|
-
}
|
|
1322
|
-
const json = await res.json();
|
|
1323
|
-
const list = Array.isArray(json) ? json : Array.isArray(json.data) ? json.data : Array.isArray(json.items) ? json.items : [];
|
|
1324
|
-
if (cancelled) return;
|
|
1325
|
-
setItems(list);
|
|
1326
|
-
} catch (err) {
|
|
1327
|
-
if (cancelled) return;
|
|
1328
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
1329
|
-
setItems([]);
|
|
1330
|
-
} finally {
|
|
1331
|
-
if (!cancelled) setLoading(false);
|
|
934
|
+
const key = productId ? `numu:related:${productId}:${limit}` : "";
|
|
935
|
+
const fetcher = react.useCallback(
|
|
936
|
+
async (signal) => {
|
|
937
|
+
const res = await fetch(
|
|
938
|
+
`/api/storefront/products/${encodeURIComponent(
|
|
939
|
+
productId
|
|
940
|
+
)}/related?limit=${limit}`,
|
|
941
|
+
{ cache: "no-store", signal }
|
|
942
|
+
);
|
|
943
|
+
if (!res.ok) {
|
|
944
|
+
return EMPTY4;
|
|
1332
945
|
}
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
946
|
+
const json = await res.json();
|
|
947
|
+
return Array.isArray(json) ? json : Array.isArray(json.data) ? json.data : Array.isArray(json.items) ? json.items : EMPTY4;
|
|
948
|
+
},
|
|
949
|
+
[productId, limit]
|
|
950
|
+
);
|
|
951
|
+
const { data, isLoading, error } = useCachedResource(key, fetcher, {
|
|
952
|
+
enabled: Boolean(productId),
|
|
953
|
+
initialData: EMPTY4
|
|
954
|
+
});
|
|
955
|
+
return { items: data ?? EMPTY4, loading: isLoading, error: error ?? null };
|
|
1339
956
|
}
|
|
1340
957
|
function useProductSizeChart(productOverride) {
|
|
1341
958
|
const ctxProduct = useProductOptional();
|
|
1342
959
|
const product = productOverride ?? ctxProduct;
|
|
1343
|
-
const shop = useShop();
|
|
960
|
+
const shop = chunkIBNAZRUT_cjs.useShop();
|
|
1344
961
|
const storeSettings = shop?.settings;
|
|
1345
962
|
return react.useMemo(
|
|
1346
963
|
() => resolveSizeChart(product?.attributes, storeSettings),
|
|
@@ -1360,86 +977,6 @@ function resolveSizeChart(productAttributes, storeSettings) {
|
|
|
1360
977
|
if (hasRows(storeDefault)) return storeDefault;
|
|
1361
978
|
return null;
|
|
1362
979
|
}
|
|
1363
|
-
var COOKIE_NAME = "numu_currency";
|
|
1364
|
-
var COOKIE_MAX_AGE = 60 * 60 * 24 * 30;
|
|
1365
|
-
function readCookie(name) {
|
|
1366
|
-
if (typeof document === "undefined") return null;
|
|
1367
|
-
const match = document.cookie.match(
|
|
1368
|
-
new RegExp(`(?:^|;\\s*)${name}=([^;]+)`)
|
|
1369
|
-
);
|
|
1370
|
-
return match ? decodeURIComponent(match[1]) : null;
|
|
1371
|
-
}
|
|
1372
|
-
function writeCookie(name, value) {
|
|
1373
|
-
if (typeof document === "undefined") return;
|
|
1374
|
-
document.cookie = `${name}=${encodeURIComponent(value)}; path=/; max-age=${COOKIE_MAX_AGE}; SameSite=Lax`;
|
|
1375
|
-
}
|
|
1376
|
-
function useCurrency() {
|
|
1377
|
-
const shop = useShop();
|
|
1378
|
-
const [config, setConfig] = react.useState(null);
|
|
1379
|
-
const [loading, setLoading] = react.useState(true);
|
|
1380
|
-
const [selected, setSelectedState] = react.useState("");
|
|
1381
|
-
react.useEffect(() => {
|
|
1382
|
-
let cancelled = false;
|
|
1383
|
-
(async () => {
|
|
1384
|
-
try {
|
|
1385
|
-
const res = await fetch(`/api/storefront/currencies`, {
|
|
1386
|
-
credentials: "include",
|
|
1387
|
-
headers: { Accept: "application/json" }
|
|
1388
|
-
});
|
|
1389
|
-
if (!res.ok) throw new Error(`currencies: HTTP ${res.status}`);
|
|
1390
|
-
const body = await res.json();
|
|
1391
|
-
if (cancelled) return;
|
|
1392
|
-
setConfig(body.data);
|
|
1393
|
-
const cookie = readCookie(COOKIE_NAME);
|
|
1394
|
-
const valid = cookie && body.data.presentment.includes(cookie) ? cookie : null;
|
|
1395
|
-
setSelectedState(valid || body.data.default_presentment || body.data.base);
|
|
1396
|
-
} catch {
|
|
1397
|
-
if (cancelled) return;
|
|
1398
|
-
setConfig(null);
|
|
1399
|
-
setSelectedState(shop.currency || "EGP");
|
|
1400
|
-
} finally {
|
|
1401
|
-
if (!cancelled) setLoading(false);
|
|
1402
|
-
}
|
|
1403
|
-
})();
|
|
1404
|
-
return () => {
|
|
1405
|
-
cancelled = true;
|
|
1406
|
-
};
|
|
1407
|
-
}, [shop.id, shop.currency]);
|
|
1408
|
-
const rates = react.useMemo(() => {
|
|
1409
|
-
const out = {};
|
|
1410
|
-
if (!config) return out;
|
|
1411
|
-
for (const [k, v] of Object.entries(config.rates)) {
|
|
1412
|
-
const n = Number.parseFloat(v);
|
|
1413
|
-
if (Number.isFinite(n)) out[k] = n;
|
|
1414
|
-
}
|
|
1415
|
-
return out;
|
|
1416
|
-
}, [config]);
|
|
1417
|
-
const convert = react.useCallback(
|
|
1418
|
-
(cents, target) => {
|
|
1419
|
-
if (!config) return cents;
|
|
1420
|
-
const to = target || selected;
|
|
1421
|
-
if (!to || to === config.base) return cents;
|
|
1422
|
-
const rate = rates[to];
|
|
1423
|
-
if (!rate || !Number.isFinite(rate)) return cents;
|
|
1424
|
-
return Math.round(cents * rate);
|
|
1425
|
-
},
|
|
1426
|
-
[config, rates, selected]
|
|
1427
|
-
);
|
|
1428
|
-
const setSelected = react.useCallback((currency) => {
|
|
1429
|
-
setSelectedState(currency);
|
|
1430
|
-
writeCookie(COOKIE_NAME, currency);
|
|
1431
|
-
}, []);
|
|
1432
|
-
return {
|
|
1433
|
-
base: config?.base || shop.currency || "EGP",
|
|
1434
|
-
selected: selected || config?.base || shop.currency || "EGP",
|
|
1435
|
-
presentment: config?.presentment || [shop.currency || "EGP"],
|
|
1436
|
-
rates,
|
|
1437
|
-
autoConvert: Boolean(config?.auto_convert),
|
|
1438
|
-
loading,
|
|
1439
|
-
setSelected,
|
|
1440
|
-
convert
|
|
1441
|
-
};
|
|
1442
|
-
}
|
|
1443
980
|
|
|
1444
981
|
// src/utils/variants.ts
|
|
1445
982
|
function findVariantByOptions(product, selection) {
|
|
@@ -1484,6 +1021,25 @@ function availableValues(product, selection) {
|
|
|
1484
1021
|
}
|
|
1485
1022
|
return out;
|
|
1486
1023
|
}
|
|
1024
|
+
|
|
1025
|
+
// src/utils/selectionRegistry.ts
|
|
1026
|
+
var selections = /* @__PURE__ */ new Map();
|
|
1027
|
+
function publishVariantSelection(productId, selection) {
|
|
1028
|
+
if (!productId) return;
|
|
1029
|
+
selections.set(productId, selection);
|
|
1030
|
+
}
|
|
1031
|
+
function readVariantSelection(productId) {
|
|
1032
|
+
if (!productId) return null;
|
|
1033
|
+
const sel = selections.get(productId);
|
|
1034
|
+
if (!sel) return null;
|
|
1035
|
+
const filtered = {};
|
|
1036
|
+
for (const [k, v] of Object.entries(sel)) {
|
|
1037
|
+
if (v) filtered[k] = v;
|
|
1038
|
+
}
|
|
1039
|
+
return Object.keys(filtered).length > 0 ? filtered : null;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// src/hooks/useVariantSelection.ts
|
|
1487
1043
|
function useVariantSelection(product, opts = {}) {
|
|
1488
1044
|
const autoSelect = opts.autoSelect ?? true;
|
|
1489
1045
|
const initial = react.useMemo(() => {
|
|
@@ -1502,6 +1058,9 @@ function useVariantSelection(product, opts = {}) {
|
|
|
1502
1058
|
() => findVariantByOptions(product, selection),
|
|
1503
1059
|
[product, selection]
|
|
1504
1060
|
);
|
|
1061
|
+
react.useEffect(() => {
|
|
1062
|
+
publishVariantSelection(product.id, selection);
|
|
1063
|
+
}, [product.id, selection]);
|
|
1505
1064
|
const availability = react.useMemo(
|
|
1506
1065
|
() => availableValues(product, selection),
|
|
1507
1066
|
[product, selection]
|
|
@@ -1895,6 +1454,19 @@ function readCsrfCookie() {
|
|
|
1895
1454
|
const match = document.cookie.match(/(?:^|;\s*)numu_csrf=([^;]+)/);
|
|
1896
1455
|
return match ? decodeURIComponent(match[1]) : null;
|
|
1897
1456
|
}
|
|
1457
|
+
var CURRENCY_COOKIE_NAME = "numu_currency";
|
|
1458
|
+
var CURRENCY_COOKIE_MAX_AGE = 60 * 60 * 24 * 30;
|
|
1459
|
+
function readCurrencyCookie() {
|
|
1460
|
+
if (typeof document === "undefined") return null;
|
|
1461
|
+
const match = document.cookie.match(
|
|
1462
|
+
new RegExp(`(?:^|;\\s*)${CURRENCY_COOKIE_NAME}=([^;]+)`)
|
|
1463
|
+
);
|
|
1464
|
+
return match ? decodeURIComponent(match[1]) : null;
|
|
1465
|
+
}
|
|
1466
|
+
function writeCurrencyCookie(value) {
|
|
1467
|
+
if (typeof document === "undefined") return;
|
|
1468
|
+
document.cookie = `${CURRENCY_COOKIE_NAME}=${encodeURIComponent(value)}; path=/; max-age=${CURRENCY_COOKIE_MAX_AGE}; SameSite=Lax`;
|
|
1469
|
+
}
|
|
1898
1470
|
async function postCartMutation(endpoint, body, applyCart, reserveToken) {
|
|
1899
1471
|
const headers = {
|
|
1900
1472
|
"Content-Type": "application/json"
|
|
@@ -1908,9 +1480,21 @@ async function postCartMutation(endpoint, body, applyCart, reserveToken) {
|
|
|
1908
1480
|
headers,
|
|
1909
1481
|
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
1910
1482
|
});
|
|
1911
|
-
if (!res.ok)
|
|
1483
|
+
if (!res.ok) {
|
|
1484
|
+
let message;
|
|
1485
|
+
try {
|
|
1486
|
+
const err = await res.json();
|
|
1487
|
+
if (err && typeof err === "object") {
|
|
1488
|
+
const m = err.message ?? err.error ?? err.detail;
|
|
1489
|
+
if (typeof m === "string") message = m;
|
|
1490
|
+
}
|
|
1491
|
+
} catch {
|
|
1492
|
+
}
|
|
1493
|
+
return { ok: false, status: res.status, message };
|
|
1494
|
+
}
|
|
1912
1495
|
const json = await res.json();
|
|
1913
1496
|
applyCart(unwrapCart(json));
|
|
1497
|
+
return { ok: true, status: res.status };
|
|
1914
1498
|
}
|
|
1915
1499
|
function NuMuProvider({
|
|
1916
1500
|
store,
|
|
@@ -1920,6 +1504,7 @@ function NuMuProvider({
|
|
|
1920
1504
|
locale: initialLocale,
|
|
1921
1505
|
translations: initialTranslations,
|
|
1922
1506
|
currentTemplate = "home",
|
|
1507
|
+
pageTemplate,
|
|
1923
1508
|
initialProducts,
|
|
1924
1509
|
initialCollections,
|
|
1925
1510
|
navigation,
|
|
@@ -1929,12 +1514,16 @@ function NuMuProvider({
|
|
|
1929
1514
|
() => ({
|
|
1930
1515
|
type: currentTemplate,
|
|
1931
1516
|
title: store?.name ?? "",
|
|
1517
|
+
// I3 — resolved alternate template key (e.g. "product.wholesale"),
|
|
1518
|
+
// surfaced via usePage()?.template. Omitted when undefined so a
|
|
1519
|
+
// page on its default template exposes no `template` field.
|
|
1520
|
+
...pageTemplate ? { template: pageTemplate } : {},
|
|
1932
1521
|
data: {
|
|
1933
1522
|
products: initialProducts ?? [],
|
|
1934
1523
|
collections: initialCollections ?? []
|
|
1935
1524
|
}
|
|
1936
1525
|
}),
|
|
1937
|
-
[currentTemplate, store?.name, initialProducts, initialCollections]
|
|
1526
|
+
[currentTemplate, pageTemplate, store?.name, initialProducts, initialCollections]
|
|
1938
1527
|
);
|
|
1939
1528
|
const [cart, setCart] = react.useState(
|
|
1940
1529
|
initialCart || { ...EMPTY_CART, currency: store.currency }
|
|
@@ -2125,7 +1714,7 @@ function NuMuProvider({
|
|
|
2125
1714
|
const token = reserveToken();
|
|
2126
1715
|
setLoading(true);
|
|
2127
1716
|
try {
|
|
2128
|
-
await postCartMutation(
|
|
1717
|
+
return await postCartMutation(
|
|
2129
1718
|
endpoint,
|
|
2130
1719
|
body,
|
|
2131
1720
|
buildApplyCart(token),
|
|
@@ -2139,15 +1728,18 @@ function NuMuProvider({
|
|
|
2139
1728
|
[reserveToken, buildApplyCart]
|
|
2140
1729
|
);
|
|
2141
1730
|
const addItem = react.useCallback(
|
|
2142
|
-
async (productId, variantId, quantity) => {
|
|
1731
|
+
async (productId, variantId, quantity, selectedOptions) => {
|
|
2143
1732
|
const eventId = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}`;
|
|
2144
1733
|
const qty = quantity || 1;
|
|
2145
|
-
|
|
1734
|
+
const axes = selectedOptions && Object.keys(selectedOptions).length > 0 ? selectedOptions : readVariantSelection(productId) ?? void 0;
|
|
1735
|
+
const result = await mutate("/api/cart/add", {
|
|
2146
1736
|
product_id: productId,
|
|
2147
1737
|
variant_id: variantId,
|
|
2148
1738
|
quantity: qty,
|
|
1739
|
+
selected_options: axes,
|
|
2149
1740
|
_event_id: eventId
|
|
2150
1741
|
});
|
|
1742
|
+
if (!result.ok) return result;
|
|
2151
1743
|
try {
|
|
2152
1744
|
if (typeof window !== "undefined") {
|
|
2153
1745
|
window.dispatchEvent(
|
|
@@ -2166,34 +1758,29 @@ function NuMuProvider({
|
|
|
2166
1758
|
}
|
|
2167
1759
|
} catch {
|
|
2168
1760
|
}
|
|
1761
|
+
return result;
|
|
2169
1762
|
},
|
|
2170
1763
|
[mutate]
|
|
2171
1764
|
);
|
|
2172
1765
|
const removeItem = react.useCallback(
|
|
2173
|
-
|
|
2174
|
-
await mutate("/api/cart/remove", { item_id: itemId });
|
|
2175
|
-
},
|
|
1766
|
+
(itemId) => mutate("/api/cart/remove", { item_id: itemId }),
|
|
2176
1767
|
[mutate]
|
|
2177
1768
|
);
|
|
2178
1769
|
const updateQuantity = react.useCallback(
|
|
2179
|
-
|
|
2180
|
-
await mutate("/api/cart/update", { item_id: itemId, quantity });
|
|
2181
|
-
},
|
|
1770
|
+
(itemId, quantity) => mutate("/api/cart/update", { item_id: itemId, quantity }),
|
|
2182
1771
|
[mutate]
|
|
2183
1772
|
);
|
|
2184
1773
|
const applyDiscount = react.useCallback(
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
1774
|
+
(code) => mutate("/api/cart/discount", { code }),
|
|
1775
|
+
[mutate]
|
|
1776
|
+
);
|
|
1777
|
+
const removeDiscount = react.useCallback(
|
|
1778
|
+
// DELETE — postCartMutation interprets undefined body as DELETE.
|
|
1779
|
+
() => mutate("/api/cart/discount", void 0),
|
|
2188
1780
|
[mutate]
|
|
2189
1781
|
);
|
|
2190
|
-
const removeDiscount = react.useCallback(async () => {
|
|
2191
|
-
await mutate("/api/cart/discount", void 0);
|
|
2192
|
-
}, [mutate]);
|
|
2193
1782
|
const updateNote = react.useCallback(
|
|
2194
|
-
|
|
2195
|
-
await mutate("/api/cart/update", { note });
|
|
2196
|
-
},
|
|
1783
|
+
(note) => mutate("/api/cart/update", { note }),
|
|
2197
1784
|
[mutate]
|
|
2198
1785
|
);
|
|
2199
1786
|
const clearCart = react.useCallback(async () => {
|
|
@@ -2329,13 +1916,93 @@ function NuMuProvider({
|
|
|
2329
1916
|
defaultNumberFmt
|
|
2330
1917
|
]
|
|
2331
1918
|
);
|
|
2332
|
-
|
|
1919
|
+
const [currencyConfig, setCurrencyConfig] = react.useState(
|
|
1920
|
+
null
|
|
1921
|
+
);
|
|
1922
|
+
const [currencyLoading, setCurrencyLoading] = react.useState(true);
|
|
1923
|
+
const [currencySelected, setCurrencySelected] = react.useState("");
|
|
1924
|
+
react.useEffect(() => {
|
|
1925
|
+
if (typeof window === "undefined") return;
|
|
1926
|
+
let cancelled = false;
|
|
1927
|
+
void (async () => {
|
|
1928
|
+
try {
|
|
1929
|
+
const res = await fetch("/api/storefront/currencies", {
|
|
1930
|
+
credentials: "include",
|
|
1931
|
+
headers: { Accept: "application/json" }
|
|
1932
|
+
});
|
|
1933
|
+
if (!res.ok) throw new Error(`currencies: HTTP ${res.status}`);
|
|
1934
|
+
const body = await res.json();
|
|
1935
|
+
if (cancelled) return;
|
|
1936
|
+
setCurrencyConfig(body.data);
|
|
1937
|
+
const cookie = readCurrencyCookie();
|
|
1938
|
+
const valid = cookie && body.data.presentment?.includes(cookie) ? cookie : null;
|
|
1939
|
+
setCurrencySelected(
|
|
1940
|
+
valid || body.data.default_presentment || body.data.base
|
|
1941
|
+
);
|
|
1942
|
+
} catch {
|
|
1943
|
+
if (cancelled) return;
|
|
1944
|
+
setCurrencyConfig(null);
|
|
1945
|
+
setCurrencySelected(store.currency || "EGP");
|
|
1946
|
+
} finally {
|
|
1947
|
+
if (!cancelled) setCurrencyLoading(false);
|
|
1948
|
+
}
|
|
1949
|
+
})();
|
|
1950
|
+
return () => {
|
|
1951
|
+
cancelled = true;
|
|
1952
|
+
};
|
|
1953
|
+
}, [store.id, store.currency]);
|
|
1954
|
+
const currencyRates = react.useMemo(() => {
|
|
1955
|
+
const out = {};
|
|
1956
|
+
if (!currencyConfig) return out;
|
|
1957
|
+
for (const [k, v] of Object.entries(currencyConfig.rates)) {
|
|
1958
|
+
const n = Number.parseFloat(v);
|
|
1959
|
+
if (Number.isFinite(n)) out[k] = n;
|
|
1960
|
+
}
|
|
1961
|
+
return out;
|
|
1962
|
+
}, [currencyConfig]);
|
|
1963
|
+
const convertCurrency = react.useCallback(
|
|
1964
|
+
(cents, target) => {
|
|
1965
|
+
if (!currencyConfig) return cents;
|
|
1966
|
+
const to = target || currencySelected;
|
|
1967
|
+
if (!to || to === currencyConfig.base) return cents;
|
|
1968
|
+
const rate = currencyRates[to];
|
|
1969
|
+
if (!rate || !Number.isFinite(rate)) return cents;
|
|
1970
|
+
return Math.round(cents * rate);
|
|
1971
|
+
},
|
|
1972
|
+
[currencyConfig, currencyRates, currencySelected]
|
|
1973
|
+
);
|
|
1974
|
+
const setCurrency = react.useCallback((currency) => {
|
|
1975
|
+
setCurrencySelected(currency);
|
|
1976
|
+
writeCurrencyCookie(currency);
|
|
1977
|
+
}, []);
|
|
1978
|
+
const currencyValue = react.useMemo(
|
|
1979
|
+
() => ({
|
|
1980
|
+
base: currencyConfig?.base || store.currency || "EGP",
|
|
1981
|
+
selected: currencySelected || currencyConfig?.base || store.currency || "EGP",
|
|
1982
|
+
presentment: currencyConfig?.presentment || [store.currency || "EGP"],
|
|
1983
|
+
rates: currencyRates,
|
|
1984
|
+
autoConvert: Boolean(currencyConfig?.auto_convert),
|
|
1985
|
+
loading: currencyLoading,
|
|
1986
|
+
setSelected: setCurrency,
|
|
1987
|
+
convert: convertCurrency
|
|
1988
|
+
}),
|
|
1989
|
+
[
|
|
1990
|
+
currencyConfig,
|
|
1991
|
+
currencySelected,
|
|
1992
|
+
currencyRates,
|
|
1993
|
+
currencyLoading,
|
|
1994
|
+
store.currency,
|
|
1995
|
+
setCurrency,
|
|
1996
|
+
convertCurrency
|
|
1997
|
+
]
|
|
1998
|
+
);
|
|
1999
|
+
return /* @__PURE__ */ jsxRuntime.jsx(chunkIBNAZRUT_cjs.ShopContext.Provider, { value: store, children: /* @__PURE__ */ jsxRuntime.jsx(chunkIBNAZRUT_cjs.ThemeSettingsContext.Provider, { value: themeSettings, children: /* @__PURE__ */ jsxRuntime.jsx(chunkIBNAZRUT_cjs.CurrentTemplateContext.Provider, { value: currentTemplate, children: /* @__PURE__ */ jsxRuntime.jsx(chunkIBNAZRUT_cjs.PageContext.Provider, { value: pageValue, children: /* @__PURE__ */ jsxRuntime.jsx(chunkIBNAZRUT_cjs.LocalizationContext.Provider, { value: localization, children: /* @__PURE__ */ jsxRuntime.jsx(chunkIBNAZRUT_cjs.CurrencyContext.Provider, { value: currencyValue, children: /* @__PURE__ */ jsxRuntime.jsx(chunkIBNAZRUT_cjs.CartContext.Provider, { value: cartValue, children: /* @__PURE__ */ jsxRuntime.jsx(chunkIBNAZRUT_cjs.CustomerContext.Provider, { value: customerState, children: /* @__PURE__ */ jsxRuntime.jsx(CustomerActionsContext.Provider, { value: customerActions, children: /* @__PURE__ */ jsxRuntime.jsx(chunkIBNAZRUT_cjs.NavigationContext.Provider, { value: navigation ?? {}, children }) }) }) }) }) }) }) }) }) });
|
|
2333
2000
|
}
|
|
2334
2001
|
function ProductProvider({ product, children }) {
|
|
2335
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ProductContext.Provider, { value: product, children });
|
|
2002
|
+
return /* @__PURE__ */ jsxRuntime.jsx(chunkIBNAZRUT_cjs.ProductContext.Provider, { value: product, children });
|
|
2336
2003
|
}
|
|
2337
2004
|
function CollectionProvider({ collection, children }) {
|
|
2338
|
-
return /* @__PURE__ */ jsxRuntime.jsx(CollectionContext.Provider, { value: collection, children });
|
|
2005
|
+
return /* @__PURE__ */ jsxRuntime.jsx(chunkIBNAZRUT_cjs.CollectionContext.Provider, { value: collection, children });
|
|
2339
2006
|
}
|
|
2340
2007
|
|
|
2341
2008
|
// src/utils/logoStyle.ts
|
|
@@ -2630,6 +2297,7 @@ var ThemeMountBridge = react.forwardRef(function ThemeMountBridge2({ ctx, mountE
|
|
|
2630
2297
|
initialProducts: pageData.products,
|
|
2631
2298
|
initialCollections: pageData.collections,
|
|
2632
2299
|
currentTemplate: template,
|
|
2300
|
+
pageTemplate: ctx.page?.template,
|
|
2633
2301
|
children: wrapEntityProviders(app, pageData)
|
|
2634
2302
|
}
|
|
2635
2303
|
);
|
|
@@ -2673,17 +2341,20 @@ function Money({
|
|
|
2673
2341
|
className,
|
|
2674
2342
|
as = "span"
|
|
2675
2343
|
}) {
|
|
2676
|
-
const { formatMoney } = useLocalization();
|
|
2677
|
-
const shop = useShop();
|
|
2678
|
-
const
|
|
2344
|
+
const { formatMoney } = chunkIBNAZRUT_cjs.useLocalization();
|
|
2345
|
+
const shop = chunkIBNAZRUT_cjs.useShop();
|
|
2346
|
+
const { selected, base, autoConvert, convert } = useCurrency();
|
|
2347
|
+
const shouldConvert = autoConvert && !currency && !!selected && selected !== base;
|
|
2348
|
+
const ccy = shouldConvert ? selected : currency || shop?.currency;
|
|
2349
|
+
const toDisplay = (major) => shouldConvert ? convert(Math.round(major * 100), selected) / 100 : major;
|
|
2679
2350
|
const showCompare = compareAt != null && compareAt > amount;
|
|
2680
2351
|
const children = [
|
|
2681
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: formatMoney(amount, ccy) }, "amt")
|
|
2352
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: formatMoney(toDisplay(amount), ccy) }, "amt")
|
|
2682
2353
|
];
|
|
2683
2354
|
if (showCompare) {
|
|
2684
2355
|
children.push(" ");
|
|
2685
2356
|
children.push(
|
|
2686
|
-
/* @__PURE__ */ jsxRuntime.jsx("s", { style: { opacity: 0.6 }, children: formatMoney(compareAt, ccy) }, "cmp")
|
|
2357
|
+
/* @__PURE__ */ jsxRuntime.jsx("s", { style: { opacity: 0.6 }, children: formatMoney(toDisplay(compareAt), ccy) }, "cmp")
|
|
2687
2358
|
);
|
|
2688
2359
|
}
|
|
2689
2360
|
return react.createElement(
|
|
@@ -2935,8 +2606,8 @@ function Logo({
|
|
|
2935
2606
|
style,
|
|
2936
2607
|
fallback
|
|
2937
2608
|
}) {
|
|
2938
|
-
const settings = useThemeSettings();
|
|
2939
|
-
const shop = useShop();
|
|
2609
|
+
const settings = chunkIBNAZRUT_cjs.useThemeSettings();
|
|
2610
|
+
const shop = chunkIBNAZRUT_cjs.useShop();
|
|
2940
2611
|
const g = settings?.global_settings ?? {};
|
|
2941
2612
|
const url = str(src) || str(g.logo_url) || shop?.logo_url || "";
|
|
2942
2613
|
const resolvedShape = shape || str(g.logo_shape) || "none";
|
|
@@ -2962,19 +2633,40 @@ function Logo({
|
|
|
2962
2633
|
);
|
|
2963
2634
|
}
|
|
2964
2635
|
var ABSOLUTE_URL = /^[a-z]+:|^\/\//i;
|
|
2965
|
-
|
|
2966
|
-
|
|
2636
|
+
var NAVIGATE_EVENT = "numu:navigate";
|
|
2637
|
+
function requestNavigate(href) {
|
|
2638
|
+
if (typeof window === "undefined") return false;
|
|
2639
|
+
const event = new CustomEvent(NAVIGATE_EVENT, {
|
|
2640
|
+
detail: { href },
|
|
2641
|
+
cancelable: true
|
|
2642
|
+
});
|
|
2643
|
+
return !window.dispatchEvent(event);
|
|
2644
|
+
}
|
|
2645
|
+
function Link({ to, children, onClick, ...rest }) {
|
|
2646
|
+
const shop = chunkIBNAZRUT_cjs.useShop();
|
|
2967
2647
|
const isAbsolute = ABSOLUTE_URL.test(to);
|
|
2968
2648
|
let href = to;
|
|
2969
2649
|
if (!isAbsolute && shop && !to.startsWith("/")) {
|
|
2970
2650
|
href = `/${to}`;
|
|
2971
2651
|
}
|
|
2972
|
-
|
|
2652
|
+
const handleClick = (e) => {
|
|
2653
|
+
onClick?.(e);
|
|
2654
|
+
if (e.defaultPrevented) return;
|
|
2655
|
+
if (e.button !== 0) return;
|
|
2656
|
+
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
|
|
2657
|
+
if (rest.target && rest.target !== "_self") return;
|
|
2658
|
+
if (rest.download !== void 0) return;
|
|
2659
|
+
if (isAbsolute) return;
|
|
2660
|
+
if (to.startsWith("#") || href.startsWith("#")) return;
|
|
2661
|
+
if (requestNavigate(href)) e.preventDefault();
|
|
2662
|
+
};
|
|
2663
|
+
return /* @__PURE__ */ jsxRuntime.jsx("a", { href, onClick: handleClick, ...rest, children });
|
|
2973
2664
|
}
|
|
2974
2665
|
function AddToCartButton({
|
|
2975
2666
|
product,
|
|
2976
2667
|
variant,
|
|
2977
2668
|
quantity = 1,
|
|
2669
|
+
selectedOptions,
|
|
2978
2670
|
label = "Add to cart",
|
|
2979
2671
|
loadingLabel = "Adding\u2026",
|
|
2980
2672
|
soldOutLabel = "Sold out",
|
|
@@ -3001,7 +2693,7 @@ function AddToCartButton({
|
|
|
3001
2693
|
if (state === "adding") return;
|
|
3002
2694
|
setState("adding");
|
|
3003
2695
|
try {
|
|
3004
|
-
await addItem(product.id, variant?.id, quantity);
|
|
2696
|
+
await addItem(product.id, variant?.id, quantity, selectedOptions);
|
|
3005
2697
|
setState("idle");
|
|
3006
2698
|
onAdded?.(product, variant);
|
|
3007
2699
|
} catch {
|
|
@@ -3419,11 +3111,29 @@ function walk(node) {
|
|
|
3419
3111
|
}
|
|
3420
3112
|
}
|
|
3421
3113
|
function sanitizeHtmlServer(input) {
|
|
3422
|
-
|
|
3423
|
-
s =
|
|
3114
|
+
if (!input) return "";
|
|
3115
|
+
let s = input;
|
|
3116
|
+
let prev;
|
|
3117
|
+
const DANGEROUS = [
|
|
3118
|
+
/<script\b[\s\S]*?<\/script\s*>/gi,
|
|
3119
|
+
/<style\b[\s\S]*?<\/style\s*>/gi,
|
|
3120
|
+
/<iframe\b[\s\S]*?<\/iframe\s*>/gi,
|
|
3121
|
+
/<object\b[\s\S]*?<\/object\s*>/gi,
|
|
3122
|
+
/<embed\b[\s\S]*?<\/embed\s*>/gi,
|
|
3123
|
+
/<\/?(?:script|style|iframe|object|embed|link|meta|form|input)\b[^>]*>/gi
|
|
3124
|
+
];
|
|
3125
|
+
do {
|
|
3126
|
+
prev = s;
|
|
3127
|
+
for (const re of DANGEROUS) s = s.replace(re, "");
|
|
3128
|
+
} while (s !== prev);
|
|
3129
|
+
const HANDLER = /[\s/"'<]on\w+\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi;
|
|
3130
|
+
do {
|
|
3131
|
+
prev = s;
|
|
3132
|
+
s = s.replace(HANDLER, "");
|
|
3133
|
+
} while (s !== prev);
|
|
3424
3134
|
s = s.replace(
|
|
3425
|
-
|
|
3426
|
-
(full,
|
|
3135
|
+
/[\s/"'<](href|src)\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi,
|
|
3136
|
+
(full, _attr, val) => {
|
|
3427
3137
|
const cleaned = String(val).replace(/^['"]|['"]$/g, "");
|
|
3428
3138
|
return isSafeUrl(cleaned) ? full : "";
|
|
3429
3139
|
}
|
|
@@ -3498,8 +3208,8 @@ function LocaleSwitcher({
|
|
|
3498
3208
|
onSelect,
|
|
3499
3209
|
render
|
|
3500
3210
|
}) {
|
|
3501
|
-
const shop = useShop();
|
|
3502
|
-
const { locale } = useLocalization();
|
|
3211
|
+
const shop = chunkIBNAZRUT_cjs.useShop();
|
|
3212
|
+
const { locale } = chunkIBNAZRUT_cjs.useLocalization();
|
|
3503
3213
|
const available = shop.available_locales || [];
|
|
3504
3214
|
const list = available.length > 0 ? available : shop.default_language === "en" ? ["en"] : Array.from(/* @__PURE__ */ new Set([shop.default_language, "en"]));
|
|
3505
3215
|
const labelFor = react.useCallback((code) => {
|
|
@@ -3768,97 +3478,6 @@ function Icon({
|
|
|
3768
3478
|
);
|
|
3769
3479
|
}
|
|
3770
3480
|
|
|
3771
|
-
// src/utils/normalize.ts
|
|
3772
|
-
var DEFAULT_THEME_ID = typeof process !== "undefined" && process.env?.NUMU_DEFAULT_THEME_ID || "modern";
|
|
3773
|
-
function resolveThemeSettings(raw) {
|
|
3774
|
-
const safeRaw = raw ?? {};
|
|
3775
|
-
if (safeRaw.schema_version === 3) {
|
|
3776
|
-
return safeRaw;
|
|
3777
|
-
}
|
|
3778
|
-
const themeBlock = safeRaw.theme || {};
|
|
3779
|
-
const themeId = typeof themeBlock.base_theme === "string" && themeBlock.base_theme || DEFAULT_THEME_ID;
|
|
3780
|
-
const globalSettings = {};
|
|
3781
|
-
for (const key of [
|
|
3782
|
-
"primary_color",
|
|
3783
|
-
"secondary_color",
|
|
3784
|
-
"font_family",
|
|
3785
|
-
"logo_url"
|
|
3786
|
-
]) {
|
|
3787
|
-
if (themeBlock[key] !== void 0) globalSettings[key] = themeBlock[key];
|
|
3788
|
-
}
|
|
3789
|
-
if (safeRaw.identity) globalSettings.identity = safeRaw.identity;
|
|
3790
|
-
const sections = {};
|
|
3791
|
-
const order = [];
|
|
3792
|
-
const hero = safeRaw.hero;
|
|
3793
|
-
if (hero) {
|
|
3794
|
-
sections["hero_1"] = {
|
|
3795
|
-
type: "hero",
|
|
3796
|
-
settings: {
|
|
3797
|
-
// Mirrors backend `normalize_legacy_to_v3` exactly — includes
|
|
3798
|
-
// headline_ar so Arabic content survives the round-trip.
|
|
3799
|
-
headline: hero.headline ?? "",
|
|
3800
|
-
headline_ar: hero.headline_ar ?? "",
|
|
3801
|
-
subtitle: hero.subtitle ?? "",
|
|
3802
|
-
background_image: hero.hero_image_url ?? "",
|
|
3803
|
-
cta_text: hero.cta_text ?? "",
|
|
3804
|
-
cta_link: hero.cta_link ?? ""
|
|
3805
|
-
}
|
|
3806
|
-
};
|
|
3807
|
-
order.push("hero_1");
|
|
3808
|
-
}
|
|
3809
|
-
const products = safeRaw.products;
|
|
3810
|
-
if (products) {
|
|
3811
|
-
sections["featured_1"] = {
|
|
3812
|
-
type: "featured-products",
|
|
3813
|
-
settings: products
|
|
3814
|
-
};
|
|
3815
|
-
order.push("featured_1");
|
|
3816
|
-
}
|
|
3817
|
-
const templates = {};
|
|
3818
|
-
if (Object.keys(sections).length > 0) {
|
|
3819
|
-
templates["home"] = { name: "Home", sections, order };
|
|
3820
|
-
}
|
|
3821
|
-
const sectionGroups = {
|
|
3822
|
-
header: {
|
|
3823
|
-
name: "Header Group",
|
|
3824
|
-
sections: {
|
|
3825
|
-
header_1: {
|
|
3826
|
-
type: "header",
|
|
3827
|
-
settings: safeRaw.header || {}
|
|
3828
|
-
}
|
|
3829
|
-
},
|
|
3830
|
-
order: ["header_1"]
|
|
3831
|
-
},
|
|
3832
|
-
footer: {
|
|
3833
|
-
name: "Footer Group",
|
|
3834
|
-
sections: {
|
|
3835
|
-
footer_1: {
|
|
3836
|
-
type: "footer",
|
|
3837
|
-
settings: safeRaw.footer || {}
|
|
3838
|
-
}
|
|
3839
|
-
},
|
|
3840
|
-
order: ["footer_1"]
|
|
3841
|
-
}
|
|
3842
|
-
};
|
|
3843
|
-
let externalTheme = null;
|
|
3844
|
-
const ext = safeRaw.external_theme;
|
|
3845
|
-
if (ext && typeof ext === "object" && typeof ext.bundle_url === "string" && ext.bundle_url) {
|
|
3846
|
-
externalTheme = {
|
|
3847
|
-
bundle_url: ext.bundle_url,
|
|
3848
|
-
css_url: typeof ext.css_url === "string" ? ext.css_url : null,
|
|
3849
|
-
mode: typeof ext.mode === "string" ? ext.mode : "production"
|
|
3850
|
-
};
|
|
3851
|
-
}
|
|
3852
|
-
return {
|
|
3853
|
-
schema_version: 3,
|
|
3854
|
-
theme_id: themeId,
|
|
3855
|
-
global_settings: globalSettings,
|
|
3856
|
-
templates,
|
|
3857
|
-
section_groups: sectionGroups,
|
|
3858
|
-
external_theme: externalTheme
|
|
3859
|
-
};
|
|
3860
|
-
}
|
|
3861
|
-
|
|
3862
3481
|
// src/utils/federation.ts
|
|
3863
3482
|
function sdkSlot() {
|
|
3864
3483
|
return /* @__PURE__ */ Symbol.for("@numueg/theme-sdk:singleton");
|
|
@@ -3995,9 +3614,9 @@ function resolveSettingsMap(settings, ctx) {
|
|
|
3995
3614
|
return out;
|
|
3996
3615
|
}
|
|
3997
3616
|
function useResolvedSettings(instance) {
|
|
3998
|
-
const product = react.useContext(ProductContext);
|
|
3999
|
-
const collection = react.useContext(CollectionContext);
|
|
4000
|
-
const store = react.useContext(ShopContext);
|
|
3617
|
+
const product = react.useContext(chunkIBNAZRUT_cjs.ProductContext);
|
|
3618
|
+
const collection = react.useContext(chunkIBNAZRUT_cjs.CollectionContext);
|
|
3619
|
+
const store = react.useContext(chunkIBNAZRUT_cjs.ShopContext);
|
|
4001
3620
|
const ctx = react.useMemo(
|
|
4002
3621
|
() => ({ product, collection, store }),
|
|
4003
3622
|
[product, collection, store]
|
|
@@ -4171,14 +3790,143 @@ function buildLocaleBundle(modules) {
|
|
|
4171
3790
|
return bundle;
|
|
4172
3791
|
}
|
|
4173
3792
|
|
|
3793
|
+
Object.defineProperty(exports, "resolveThemeSettings", {
|
|
3794
|
+
enumerable: true,
|
|
3795
|
+
get: function () { return chunkTBSNHHFH_cjs.resolveThemeSettings; }
|
|
3796
|
+
});
|
|
3797
|
+
Object.defineProperty(exports, "KNOWN_SETTING_TYPES", {
|
|
3798
|
+
enumerable: true,
|
|
3799
|
+
get: function () { return chunkKWODVI7F_cjs.KNOWN_SETTING_TYPES; }
|
|
3800
|
+
});
|
|
3801
|
+
Object.defineProperty(exports, "KNOWN_TEMPLATES", {
|
|
3802
|
+
enumerable: true,
|
|
3803
|
+
get: function () { return chunkKWODVI7F_cjs.KNOWN_TEMPLATES; }
|
|
3804
|
+
});
|
|
3805
|
+
Object.defineProperty(exports, "REQUIRED_TEMPLATES", {
|
|
3806
|
+
enumerable: true,
|
|
3807
|
+
get: function () { return chunkKWODVI7F_cjs.REQUIRED_TEMPLATES; }
|
|
3808
|
+
});
|
|
3809
|
+
Object.defineProperty(exports, "SDK_VERSION", {
|
|
3810
|
+
enumerable: true,
|
|
3811
|
+
get: function () { return chunkKWODVI7F_cjs.SDK_VERSION; }
|
|
3812
|
+
});
|
|
3813
|
+
Object.defineProperty(exports, "THEME_CONTRACT_VERSION", {
|
|
3814
|
+
enumerable: true,
|
|
3815
|
+
get: function () { return chunkKWODVI7F_cjs.THEME_CONTRACT_VERSION; }
|
|
3816
|
+
});
|
|
3817
|
+
Object.defineProperty(exports, "mergeResults", {
|
|
3818
|
+
enumerable: true,
|
|
3819
|
+
get: function () { return chunkKWODVI7F_cjs.mergeResults; }
|
|
3820
|
+
});
|
|
3821
|
+
Object.defineProperty(exports, "validateBuiltManifest", {
|
|
3822
|
+
enumerable: true,
|
|
3823
|
+
get: function () { return chunkKWODVI7F_cjs.validateBuiltManifest; }
|
|
3824
|
+
});
|
|
3825
|
+
Object.defineProperty(exports, "validateManifest", {
|
|
3826
|
+
enumerable: true,
|
|
3827
|
+
get: function () { return chunkKWODVI7F_cjs.validateManifest; }
|
|
3828
|
+
});
|
|
3829
|
+
Object.defineProperty(exports, "validateSectionSchema", {
|
|
3830
|
+
enumerable: true,
|
|
3831
|
+
get: function () { return chunkKWODVI7F_cjs.validateSectionSchema; }
|
|
3832
|
+
});
|
|
3833
|
+
Object.defineProperty(exports, "validateSettingsAgainstSchema", {
|
|
3834
|
+
enumerable: true,
|
|
3835
|
+
get: function () { return chunkKWODVI7F_cjs.validateSettingsAgainstSchema; }
|
|
3836
|
+
});
|
|
3837
|
+
Object.defineProperty(exports, "CartContext", {
|
|
3838
|
+
enumerable: true,
|
|
3839
|
+
get: function () { return chunkIBNAZRUT_cjs.CartContext; }
|
|
3840
|
+
});
|
|
3841
|
+
Object.defineProperty(exports, "CollectionContext", {
|
|
3842
|
+
enumerable: true,
|
|
3843
|
+
get: function () { return chunkIBNAZRUT_cjs.CollectionContext; }
|
|
3844
|
+
});
|
|
3845
|
+
Object.defineProperty(exports, "CurrencyContext", {
|
|
3846
|
+
enumerable: true,
|
|
3847
|
+
get: function () { return chunkIBNAZRUT_cjs.CurrencyContext; }
|
|
3848
|
+
});
|
|
3849
|
+
Object.defineProperty(exports, "CustomerContext", {
|
|
3850
|
+
enumerable: true,
|
|
3851
|
+
get: function () { return chunkIBNAZRUT_cjs.CustomerContext; }
|
|
3852
|
+
});
|
|
3853
|
+
Object.defineProperty(exports, "LocalizationContext", {
|
|
3854
|
+
enumerable: true,
|
|
3855
|
+
get: function () { return chunkIBNAZRUT_cjs.LocalizationContext; }
|
|
3856
|
+
});
|
|
3857
|
+
Object.defineProperty(exports, "NavigationContext", {
|
|
3858
|
+
enumerable: true,
|
|
3859
|
+
get: function () { return chunkIBNAZRUT_cjs.NavigationContext; }
|
|
3860
|
+
});
|
|
3861
|
+
Object.defineProperty(exports, "PageContext", {
|
|
3862
|
+
enumerable: true,
|
|
3863
|
+
get: function () { return chunkIBNAZRUT_cjs.PageContext; }
|
|
3864
|
+
});
|
|
3865
|
+
Object.defineProperty(exports, "ProductContext", {
|
|
3866
|
+
enumerable: true,
|
|
3867
|
+
get: function () { return chunkIBNAZRUT_cjs.ProductContext; }
|
|
3868
|
+
});
|
|
3869
|
+
Object.defineProperty(exports, "ShopContext", {
|
|
3870
|
+
enumerable: true,
|
|
3871
|
+
get: function () { return chunkIBNAZRUT_cjs.ShopContext; }
|
|
3872
|
+
});
|
|
3873
|
+
Object.defineProperty(exports, "ThemeSettingsContext", {
|
|
3874
|
+
enumerable: true,
|
|
3875
|
+
get: function () { return chunkIBNAZRUT_cjs.ThemeSettingsContext; }
|
|
3876
|
+
});
|
|
3877
|
+
Object.defineProperty(exports, "useCollections", {
|
|
3878
|
+
enumerable: true,
|
|
3879
|
+
get: function () { return chunkIBNAZRUT_cjs.useCollections; }
|
|
3880
|
+
});
|
|
3881
|
+
Object.defineProperty(exports, "useCustomer", {
|
|
3882
|
+
enumerable: true,
|
|
3883
|
+
get: function () { return chunkIBNAZRUT_cjs.useCustomer; }
|
|
3884
|
+
});
|
|
3885
|
+
Object.defineProperty(exports, "useDirection", {
|
|
3886
|
+
enumerable: true,
|
|
3887
|
+
get: function () { return chunkIBNAZRUT_cjs.useDirection; }
|
|
3888
|
+
});
|
|
3889
|
+
Object.defineProperty(exports, "useFieldTranslation", {
|
|
3890
|
+
enumerable: true,
|
|
3891
|
+
get: function () { return chunkIBNAZRUT_cjs.useFieldTranslation; }
|
|
3892
|
+
});
|
|
3893
|
+
Object.defineProperty(exports, "useLocale", {
|
|
3894
|
+
enumerable: true,
|
|
3895
|
+
get: function () { return chunkIBNAZRUT_cjs.useLocale; }
|
|
3896
|
+
});
|
|
3897
|
+
Object.defineProperty(exports, "useLocalization", {
|
|
3898
|
+
enumerable: true,
|
|
3899
|
+
get: function () { return chunkIBNAZRUT_cjs.useLocalization; }
|
|
3900
|
+
});
|
|
3901
|
+
Object.defineProperty(exports, "useNumberFormat", {
|
|
3902
|
+
enumerable: true,
|
|
3903
|
+
get: function () { return chunkIBNAZRUT_cjs.useNumberFormat; }
|
|
3904
|
+
});
|
|
3905
|
+
Object.defineProperty(exports, "usePage", {
|
|
3906
|
+
enumerable: true,
|
|
3907
|
+
get: function () { return chunkIBNAZRUT_cjs.usePage; }
|
|
3908
|
+
});
|
|
3909
|
+
Object.defineProperty(exports, "useProducts", {
|
|
3910
|
+
enumerable: true,
|
|
3911
|
+
get: function () { return chunkIBNAZRUT_cjs.useProducts; }
|
|
3912
|
+
});
|
|
3913
|
+
Object.defineProperty(exports, "useShop", {
|
|
3914
|
+
enumerable: true,
|
|
3915
|
+
get: function () { return chunkIBNAZRUT_cjs.useShop; }
|
|
3916
|
+
});
|
|
3917
|
+
Object.defineProperty(exports, "useThemeSettings", {
|
|
3918
|
+
enumerable: true,
|
|
3919
|
+
get: function () { return chunkIBNAZRUT_cjs.useThemeSettings; }
|
|
3920
|
+
});
|
|
3921
|
+
Object.defineProperty(exports, "useTranslation", {
|
|
3922
|
+
enumerable: true,
|
|
3923
|
+
get: function () { return chunkIBNAZRUT_cjs.useTranslation; }
|
|
3924
|
+
});
|
|
4174
3925
|
exports.AddToCartButton = AddToCartButton;
|
|
4175
3926
|
exports.Block = Block;
|
|
4176
|
-
exports.CartContext = CartContext;
|
|
4177
3927
|
exports.CollectionCard = CollectionCard;
|
|
4178
|
-
exports.CollectionContext = CollectionContext;
|
|
4179
3928
|
exports.CollectionProvider = CollectionProvider;
|
|
4180
3929
|
exports.CurrencySwitcher = CurrencySwitcher;
|
|
4181
|
-
exports.CustomerContext = CustomerContext;
|
|
4182
3930
|
exports.EditableImage = EditableImage;
|
|
4183
3931
|
exports.EditableText = EditableText;
|
|
4184
3932
|
exports.Form = Form;
|
|
@@ -4187,30 +3935,20 @@ exports.ICON_NAMES = ICON_NAMES;
|
|
|
4187
3935
|
exports.Icon = Icon;
|
|
4188
3936
|
exports.IconMap = IconMap;
|
|
4189
3937
|
exports.Image = Image;
|
|
4190
|
-
exports.KNOWN_SETTING_TYPES = KNOWN_SETTING_TYPES;
|
|
4191
|
-
exports.KNOWN_TEMPLATES = KNOWN_TEMPLATES;
|
|
4192
3938
|
exports.LOGO_SHAPE_OPTIONS = LOGO_SHAPE_OPTIONS;
|
|
4193
3939
|
exports.LOGO_SIZE_OPTIONS = LOGO_SIZE_OPTIONS;
|
|
4194
3940
|
exports.Link = Link;
|
|
4195
3941
|
exports.LocaleSwitcher = LocaleSwitcher;
|
|
4196
|
-
exports.LocalizationContext = LocalizationContext;
|
|
4197
3942
|
exports.Logo = Logo;
|
|
4198
3943
|
exports.MAX_BLOCK_DEPTH = MAX_BLOCK_DEPTH;
|
|
4199
3944
|
exports.Money = Money;
|
|
4200
|
-
exports.
|
|
3945
|
+
exports.NAVIGATE_EVENT = NAVIGATE_EVENT;
|
|
4201
3946
|
exports.NuMuProvider = NuMuProvider;
|
|
4202
|
-
exports.PageContext = PageContext;
|
|
4203
3947
|
exports.ProductCard = ProductCard;
|
|
4204
|
-
exports.ProductContext = ProductContext;
|
|
4205
3948
|
exports.ProductProvider = ProductProvider;
|
|
4206
|
-
exports.REQUIRED_TEMPLATES = REQUIRED_TEMPLATES;
|
|
4207
3949
|
exports.RichText = RichText;
|
|
4208
|
-
exports.SDK_VERSION = SDK_VERSION;
|
|
4209
3950
|
exports.Section = Section;
|
|
4210
3951
|
exports.SectionContext = SectionContext;
|
|
4211
|
-
exports.ShopContext = ShopContext;
|
|
4212
|
-
exports.THEME_CONTRACT_VERSION = THEME_CONTRACT_VERSION;
|
|
4213
|
-
exports.ThemeSettingsContext = ThemeSettingsContext;
|
|
4214
3952
|
exports.applyGlobalStyleTokens = applyGlobalStyleTokens;
|
|
4215
3953
|
exports.applyImageTransform = applyImageTransform;
|
|
4216
3954
|
exports.asImageTransform = asImageTransform;
|
|
@@ -4238,61 +3976,48 @@ exports.isDynamicSource = isDynamicSource;
|
|
|
4238
3976
|
exports.isSdkAvailable = isSdkAvailable;
|
|
4239
3977
|
exports.logoImgStyle = logoImgStyle;
|
|
4240
3978
|
exports.logoStyleTokens = logoStyleTokens;
|
|
4241
|
-
exports.mergeResults = mergeResults;
|
|
4242
3979
|
exports.mountTheme = mountTheme;
|
|
4243
3980
|
exports.pickTranslations = pickTranslations;
|
|
3981
|
+
exports.publishVariantSelection = publishVariantSelection;
|
|
3982
|
+
exports.readVariantSelection = readVariantSelection;
|
|
4244
3983
|
exports.registerReactSingleton = registerReactSingleton;
|
|
4245
3984
|
exports.registerSdkSingleton = registerSdkSingleton;
|
|
3985
|
+
exports.requestNavigate = requestNavigate;
|
|
4246
3986
|
exports.resolveDynamicValue = resolveDynamicValue;
|
|
4247
3987
|
exports.resolveFontStack = resolveFontStack;
|
|
4248
3988
|
exports.resolveSettingsMap = resolveSettingsMap;
|
|
4249
3989
|
exports.resolveSizeChart = resolveSizeChart;
|
|
4250
3990
|
exports.resolveSourcePath = resolveSourcePath;
|
|
4251
|
-
exports.resolveThemeSettings = resolveThemeSettings;
|
|
4252
3991
|
exports.sanitizeHtml = sanitizeHtml;
|
|
4253
3992
|
exports.useAnalytics = useAnalytics;
|
|
4254
3993
|
exports.useApp = useApp;
|
|
3994
|
+
exports.useCachedResource = useCachedResource;
|
|
4255
3995
|
exports.useCart = useCart;
|
|
4256
3996
|
exports.useCheckout = useCheckout;
|
|
4257
3997
|
exports.useCollection = useCollection;
|
|
4258
3998
|
exports.useCollectionOptional = useCollectionOptional;
|
|
4259
|
-
exports.useCollections = useCollections;
|
|
4260
3999
|
exports.useCurrency = useCurrency;
|
|
4261
4000
|
exports.useCurrentTemplate = useCurrentTemplate;
|
|
4262
|
-
exports.useCustomer = useCustomer;
|
|
4263
4001
|
exports.useCustomerActions = useCustomerActions;
|
|
4264
4002
|
exports.useCustomerAddresses = useCustomerAddresses;
|
|
4265
|
-
exports.useDirection = useDirection;
|
|
4266
|
-
exports.useFieldTranslation = useFieldTranslation;
|
|
4267
4003
|
exports.useGiftCardBalance = useGiftCardBalance;
|
|
4268
4004
|
exports.useImage = useImage;
|
|
4269
|
-
exports.useLocale = useLocale;
|
|
4270
|
-
exports.useLocalization = useLocalization;
|
|
4271
4005
|
exports.useMoney = useMoney;
|
|
4272
4006
|
exports.useNavigation = useNavigation;
|
|
4273
|
-
exports.useNumberFormat = useNumberFormat;
|
|
4274
4007
|
exports.useOrder = useOrder;
|
|
4275
4008
|
exports.useOrders = useOrders;
|
|
4276
|
-
exports.usePage = usePage;
|
|
4277
4009
|
exports.useProduct = useProduct;
|
|
4278
4010
|
exports.useProductOptional = useProductOptional;
|
|
4279
4011
|
exports.useProductSizeChart = useProductSizeChart;
|
|
4280
|
-
exports.useProducts = useProducts;
|
|
4281
4012
|
exports.useRelatedProducts = useRelatedProducts;
|
|
4282
4013
|
exports.useReorder = useReorder;
|
|
4283
4014
|
exports.useResolvedSettings = useResolvedSettings;
|
|
4284
4015
|
exports.useSearch = useSearch;
|
|
4285
4016
|
exports.useSection = useSection;
|
|
4017
|
+
exports.useSectionGroup = useSectionGroup;
|
|
4286
4018
|
exports.useSectionOptional = useSectionOptional;
|
|
4287
4019
|
exports.useShippingRates = useShippingRates;
|
|
4288
|
-
exports.useShop = useShop;
|
|
4289
|
-
exports.useThemeSettings = useThemeSettings;
|
|
4290
|
-
exports.useTranslation = useTranslation;
|
|
4291
4020
|
exports.useVariantSelection = useVariantSelection;
|
|
4292
4021
|
exports.useWishlist = useWishlist;
|
|
4293
|
-
exports.validateBuiltManifest = validateBuiltManifest;
|
|
4294
|
-
exports.validateManifest = validateManifest;
|
|
4295
|
-
exports.validateSectionSchema = validateSectionSchema;
|
|
4296
|
-
exports.validateSettingsAgainstSchema = validateSettingsAgainstSchema;
|
|
4297
4022
|
//# sourceMappingURL=index.cjs.map
|
|
4298
4023
|
//# sourceMappingURL=index.cjs.map
|