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.2",
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
- if (!defaults.projectName) {
167
- questions.push({
168
- type: "text",
169
- name: "projectName",
170
- message: "Project name:",
171
- initial: "my-store",
172
- validate: (value) => {
173
- const error = validateProjectName(value);
174
- return error || true;
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 HEBREW_TO_LATIN = {
628
- \u05D0: "a",
629
- \u05D1: "b",
630
- \u05D2: "g",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-brainerce-store",
3
- "version": "1.27.2",
3
+ "version": "1.27.4",
4
4
  "description": "Scaffold a production-ready e-commerce storefront connected to Brainerce",
5
5
  "bin": {
6
6
  "create-brainerce-store": "dist/index.js"
@@ -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 (keep tree structure)
245
+ // Load categories, brands, and tags
242
246
  useEffect(() => {
243
- async function loadCategories() {
244
- try {
245
- const client = getClient();
246
- const result = await client.getCategories();
247
- setCategories(result.categories as CategoryNode[]);
248
- } catch {
249
- // Categories endpoint may not be available in all modes
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
- loadCategories();
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 items-start gap-4 sm:flex-row sm:items-center">
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-2 sm:ms-auto">
364
- <label htmlFor="sort" className="text-muted-foreground whitespace-nowrap text-sm">
365
- {tc('sortBy')}
366
- </label>
367
- <select
368
- id="sort"
369
- value={sortIndex}
370
- onChange={(e) => updateParam('sort', e.target.value)}
371
- 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"
372
- >
373
- {sortOptions.map((opt, idx) => (
374
- <option key={idx} value={idx}>
375
- {t(opt.labelKey)}
376
- </option>
377
- ))}
378
- </select>
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
- return ns[key] || `${namespace}.${key}`;
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: keyof Messages[N]): string {
28
- return ns[key as string] || `${String(namespace)}.${key as string}`;
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
  <% } %>