create-brainerce-store 1.27.2 → 1.27.4
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/dist/index.js
CHANGED
|
@@ -31,7 +31,7 @@ var require_package = __commonJS({
|
|
|
31
31
|
"package.json"(exports2, module2) {
|
|
32
32
|
module2.exports = {
|
|
33
33
|
name: "create-brainerce-store",
|
|
34
|
-
version: "1.27.
|
|
34
|
+
version: "1.27.4",
|
|
35
35
|
description: "Scaffold a production-ready e-commerce storefront connected to Brainerce",
|
|
36
36
|
bin: {
|
|
37
37
|
"create-brainerce-store": "dist/index.js"
|
|
@@ -163,29 +163,16 @@ async function installDependencies(projectDir, pkgManager) {
|
|
|
163
163
|
// src/cli.ts
|
|
164
164
|
async function runInteractive(defaults) {
|
|
165
165
|
const questions = [];
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
});
|
|
177
|
-
} else {
|
|
178
|
-
questions.push({
|
|
179
|
-
type: "text",
|
|
180
|
-
name: "projectName",
|
|
181
|
-
message: "Project name:",
|
|
182
|
-
initial: defaults.projectName,
|
|
183
|
-
validate: (value) => {
|
|
184
|
-
const error = validateProjectName(value);
|
|
185
|
-
return error || true;
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
|
-
}
|
|
166
|
+
questions.push({
|
|
167
|
+
type: "text",
|
|
168
|
+
name: "projectName",
|
|
169
|
+
message: "Project name:",
|
|
170
|
+
initial: defaults.projectName || "",
|
|
171
|
+
validate: (value) => {
|
|
172
|
+
const error = validateProjectName(value);
|
|
173
|
+
return error || true;
|
|
174
|
+
}
|
|
175
|
+
});
|
|
189
176
|
if (!defaults.connectionId) {
|
|
190
177
|
questions.push({
|
|
191
178
|
type: "text",
|
|
@@ -624,41 +611,10 @@ program.name("create-brainerce-store").description("Scaffold a production-ready
|
|
|
624
611
|
);
|
|
625
612
|
}
|
|
626
613
|
}
|
|
627
|
-
const
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
\u05D3: "d",
|
|
632
|
-
\u05D4: "h",
|
|
633
|
-
\u05D5: "v",
|
|
634
|
-
\u05D6: "z",
|
|
635
|
-
\u05D7: "ch",
|
|
636
|
-
\u05D8: "t",
|
|
637
|
-
\u05D9: "y",
|
|
638
|
-
\u05DB: "k",
|
|
639
|
-
\u05DA: "k",
|
|
640
|
-
\u05DC: "l",
|
|
641
|
-
\u05DE: "m",
|
|
642
|
-
\u05DD: "m",
|
|
643
|
-
\u05E0: "n",
|
|
644
|
-
\u05DF: "n",
|
|
645
|
-
\u05E1: "s",
|
|
646
|
-
\u05E2: "a",
|
|
647
|
-
\u05E4: "p",
|
|
648
|
-
\u05E3: "f",
|
|
649
|
-
\u05E6: "ts",
|
|
650
|
-
\u05E5: "ts",
|
|
651
|
-
\u05E7: "k",
|
|
652
|
-
\u05E8: "r",
|
|
653
|
-
\u05E9: "sh",
|
|
654
|
-
\u05EA: "t"
|
|
655
|
-
};
|
|
656
|
-
const transliterate = (s) => s.normalize("NFD").replace(/[\u0300-\u036f]/g, "").split("").map((ch) => HEBREW_TO_LATIN[ch] ?? ch).join("");
|
|
657
|
-
const slugify = (s) => {
|
|
658
|
-
const slug = transliterate(s).toLowerCase().trim().replace(/[^a-z0-9._-]+/g, "-").replace(/^[.-]+|[.-]+$/g, "").slice(0, 50);
|
|
659
|
-
return slug || "my-store";
|
|
660
|
-
};
|
|
661
|
-
const projectNameDefault = projectName || (storeInfo ? slugify(storeInfo.name) : void 0);
|
|
614
|
+
const slugify = (s) => s.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().trim().replace(/[^a-z0-9._-]+/g, "-").replace(/^[.-]+|[.-]+$/g, "").slice(0, 50);
|
|
615
|
+
const channelSlug = storeInfo ? slugify(storeInfo.name) : "";
|
|
616
|
+
const storeSlug = storeInfo ? slugify(storeInfo.storeName) : "";
|
|
617
|
+
const projectNameDefault = projectName || channelSlug || storeSlug || void 0;
|
|
662
618
|
const languageDefault = language || storeInfo?.language;
|
|
663
619
|
if (!projectName || !connectionId || !language) {
|
|
664
620
|
const answers = await runInteractive({
|
package/messages/en.json
CHANGED
|
@@ -65,7 +65,9 @@
|
|
|
65
65
|
"sortNameZA": "Name Z-A",
|
|
66
66
|
"sortPriceLow": "Price: Low to High",
|
|
67
67
|
"sortPriceHigh": "Price: High to Low",
|
|
68
|
-
"selectOptions": "Select Options"
|
|
68
|
+
"selectOptions": "Select Options",
|
|
69
|
+
"allBrands": "All Brands",
|
|
70
|
+
"allTags": "All Tags"
|
|
69
71
|
},
|
|
70
72
|
"productDetail": {
|
|
71
73
|
"notFound": "Product not found.",
|
package/messages/he.json
CHANGED
|
@@ -65,7 +65,9 @@
|
|
|
65
65
|
"sortNameZA": "שם ת-א",
|
|
66
66
|
"sortPriceLow": "מחיר: מהנמוך לגבוה",
|
|
67
67
|
"sortPriceHigh": "מחיר: מהגבוה לנמוך",
|
|
68
|
-
"selectOptions": "בחר אפשרויות"
|
|
68
|
+
"selectOptions": "בחר אפשרויות",
|
|
69
|
+
"allBrands": "כל המותגים",
|
|
70
|
+
"allTags": "כל התגיות"
|
|
69
71
|
},
|
|
70
72
|
"productDetail": {
|
|
71
73
|
"notFound": "המוצר לא נמצא.",
|
package/package.json
CHANGED
|
@@ -225,6 +225,8 @@ function ProductsContent() {
|
|
|
225
225
|
|
|
226
226
|
const searchQuery = searchParams.get('search') || '';
|
|
227
227
|
const categoryId = searchParams.get('category') || '';
|
|
228
|
+
const brandId = searchParams.get('brand') || '';
|
|
229
|
+
const tagId = searchParams.get('tag') || '';
|
|
228
230
|
const sortParam = searchParams.get('sort') || '0';
|
|
229
231
|
|
|
230
232
|
const [products, setProducts] = useState<Product[]>([]);
|
|
@@ -234,22 +236,26 @@ function ProductsContent() {
|
|
|
234
236
|
const [totalPages, setTotalPages] = useState(1);
|
|
235
237
|
const [total, setTotal] = useState(0);
|
|
236
238
|
const [categories, setCategories] = useState<CategoryNode[]>([]);
|
|
239
|
+
const [brands, setBrands] = useState<Array<{ id: string; name: string }>>([]);
|
|
240
|
+
const [tags, setTags] = useState<Array<{ id: string; name: string }>>([]);
|
|
237
241
|
|
|
238
242
|
const sortIndex = parseInt(sortParam, 10) || 0;
|
|
239
243
|
const currentSort = sortOptions[sortIndex] || sortOptions[0];
|
|
240
244
|
|
|
241
|
-
// Load categories
|
|
245
|
+
// Load categories, brands, and tags
|
|
242
246
|
useEffect(() => {
|
|
243
|
-
async function
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
247
|
+
async function loadFilters() {
|
|
248
|
+
const client = getClient();
|
|
249
|
+
const [catRes, brandRes, tagRes] = await Promise.allSettled([
|
|
250
|
+
client.getCategories(),
|
|
251
|
+
client.getBrands(),
|
|
252
|
+
client.getTags(),
|
|
253
|
+
]);
|
|
254
|
+
if (catRes.status === 'fulfilled') setCategories(catRes.value.categories as CategoryNode[]);
|
|
255
|
+
if (brandRes.status === 'fulfilled') setBrands(brandRes.value.brands);
|
|
256
|
+
if (tagRes.status === 'fulfilled') setTags(tagRes.value.tags);
|
|
251
257
|
}
|
|
252
|
-
|
|
258
|
+
loadFilters();
|
|
253
259
|
}, []);
|
|
254
260
|
|
|
255
261
|
// Load products when filters change
|
|
@@ -272,6 +278,8 @@ function ProductsContent() {
|
|
|
272
278
|
|
|
273
279
|
if (searchQuery) params.search = searchQuery;
|
|
274
280
|
if (categoryId) params.categories = categoryId;
|
|
281
|
+
if (brandId) params.brands = brandId;
|
|
282
|
+
if (tagId) params.tags = tagId;
|
|
275
283
|
|
|
276
284
|
const result = await client.getProducts(params);
|
|
277
285
|
|
|
@@ -290,7 +298,7 @@ function ProductsContent() {
|
|
|
290
298
|
setLoadingMore(false);
|
|
291
299
|
}
|
|
292
300
|
},
|
|
293
|
-
[searchQuery, categoryId, currentSort.sortBy, currentSort.sortOrder]
|
|
301
|
+
[searchQuery, categoryId, brandId, tagId, currentSort.sortBy, currentSort.sortOrder]
|
|
294
302
|
);
|
|
295
303
|
|
|
296
304
|
useEffect(() => {
|
|
@@ -332,7 +340,7 @@ function ProductsContent() {
|
|
|
332
340
|
</div>
|
|
333
341
|
|
|
334
342
|
{/* Filters and Sort */}
|
|
335
|
-
<div className="mb-6 flex flex-col
|
|
343
|
+
<div className="mb-6 flex flex-col gap-4">
|
|
336
344
|
{/* Category Filter */}
|
|
337
345
|
{categories.length > 0 && (
|
|
338
346
|
<div className="flex flex-wrap gap-2">
|
|
@@ -359,23 +367,54 @@ function ProductsContent() {
|
|
|
359
367
|
</div>
|
|
360
368
|
)}
|
|
361
369
|
|
|
362
|
-
{/* Sort */}
|
|
363
|
-
<div className="flex items-center gap-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
370
|
+
{/* Brand, Tag & Sort row */}
|
|
371
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
372
|
+
{/* Brand Filter */}
|
|
373
|
+
{brands.length > 0 && (
|
|
374
|
+
<select
|
|
375
|
+
value={brandId}
|
|
376
|
+
onChange={(e) => updateParam('brand', e.target.value)}
|
|
377
|
+
className="border-border bg-background text-foreground focus:ring-primary/20 focus:border-primary h-9 rounded border px-3 text-sm focus:outline-none focus:ring-2"
|
|
378
|
+
>
|
|
379
|
+
<option value="">{t('allBrands')}</option>
|
|
380
|
+
{brands.map((b) => (
|
|
381
|
+
<option key={b.id} value={b.id}>{b.name}</option>
|
|
382
|
+
))}
|
|
383
|
+
</select>
|
|
384
|
+
)}
|
|
385
|
+
|
|
386
|
+
{/* Tag Filter */}
|
|
387
|
+
{tags.length > 0 && (
|
|
388
|
+
<select
|
|
389
|
+
value={tagId}
|
|
390
|
+
onChange={(e) => updateParam('tag', e.target.value)}
|
|
391
|
+
className="border-border bg-background text-foreground focus:ring-primary/20 focus:border-primary h-9 rounded border px-3 text-sm focus:outline-none focus:ring-2"
|
|
392
|
+
>
|
|
393
|
+
<option value="">{t('allTags')}</option>
|
|
394
|
+
{tags.map((tg) => (
|
|
395
|
+
<option key={tg.id} value={tg.id}>{tg.name}</option>
|
|
396
|
+
))}
|
|
397
|
+
</select>
|
|
398
|
+
)}
|
|
399
|
+
|
|
400
|
+
{/* Sort */}
|
|
401
|
+
<div className="flex items-center gap-2 sm:ms-auto">
|
|
402
|
+
<label htmlFor="sort" className="text-muted-foreground whitespace-nowrap text-sm">
|
|
403
|
+
{tc('sortBy')}
|
|
404
|
+
</label>
|
|
405
|
+
<select
|
|
406
|
+
id="sort"
|
|
407
|
+
value={sortIndex}
|
|
408
|
+
onChange={(e) => updateParam('sort', e.target.value)}
|
|
409
|
+
className="border-border bg-background text-foreground focus:ring-primary/20 focus:border-primary h-9 rounded border px-3 text-sm focus:outline-none focus:ring-2"
|
|
410
|
+
>
|
|
411
|
+
{sortOptions.map((opt, idx) => (
|
|
412
|
+
<option key={idx} value={idx}>
|
|
413
|
+
{t(opt.labelKey)}
|
|
414
|
+
</option>
|
|
415
|
+
))}
|
|
416
|
+
</select>
|
|
417
|
+
</div>
|
|
379
418
|
</div>
|
|
380
419
|
</div>
|
|
381
420
|
|
|
@@ -12,8 +12,14 @@ export { MessagesContext };
|
|
|
12
12
|
export function useTranslations(namespace: string) {
|
|
13
13
|
const messages = useContext(MessagesContext);
|
|
14
14
|
const ns = (messages[namespace] || {}) as Record<string, string>;
|
|
15
|
-
return function t(key: string): string {
|
|
16
|
-
|
|
15
|
+
return function t(key: string, values?: Record<string, string>): string {
|
|
16
|
+
let result = ns[key] || `${namespace}.${key}`;
|
|
17
|
+
if (values) {
|
|
18
|
+
for (const [k, v] of Object.entries(values)) {
|
|
19
|
+
result = result.replace(`{${k}}`, v);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
17
23
|
};
|
|
18
24
|
}
|
|
19
25
|
<% } else { %>
|
|
@@ -24,8 +30,14 @@ type Namespace = keyof Messages;
|
|
|
24
30
|
|
|
25
31
|
export function useTranslations<N extends Namespace>(namespace: N) {
|
|
26
32
|
const ns = messages[namespace] as Record<string, string>;
|
|
27
|
-
return function t(key:
|
|
28
|
-
|
|
33
|
+
return function t(key: string, values?: Record<string, string>): string {
|
|
34
|
+
let result = ns[key] || `${String(namespace)}.${key}`;
|
|
35
|
+
if (values) {
|
|
36
|
+
for (const [k, v] of Object.entries(values)) {
|
|
37
|
+
result = result.replace(`{${k}}`, v);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
29
41
|
};
|
|
30
42
|
}
|
|
31
43
|
<% } %>
|