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