create-brainerce-store 1.13.0 → 1.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -69,19 +69,19 @@ function ChevronDown({ className }: { className?: string }) {
69
69
 
70
70
  /** Recursive dropdown items for nested categories */
71
71
  function CategoryDropdownItems({
72
- children,
72
+ items,
73
73
  depth,
74
74
  selectedId,
75
75
  onSelect,
76
76
  }: {
77
- children: CategoryNode[];
77
+ items: CategoryNode[];
78
78
  depth: number;
79
79
  selectedId: string;
80
80
  onSelect: (id: string) => void;
81
81
  }) {
82
82
  return (
83
83
  <>
84
- {children.map((child) => (
84
+ {items.map((child) => (
85
85
  <div key={child.id}>
86
86
  <button
87
87
  onClick={() => onSelect(child.id)}
@@ -95,7 +95,7 @@ function CategoryDropdownItems({
95
95
  </button>
96
96
  {child.children.length > 0 && (
97
97
  <CategoryDropdownItems
98
- children={child.children}
98
+ items={child.children}
99
99
  depth={depth + 1}
100
100
  selectedId={selectedId}
101
101
  onSelect={onSelect}
@@ -204,7 +204,7 @@ function CategoryChip({
204
204
  {/* Recursive children */}
205
205
  <div onClick={() => setOpen(false)}>
206
206
  <CategoryDropdownItems
207
- children={category.children}
207
+ items={category.children}
208
208
  depth={0}
209
209
  selectedId={selectedId}
210
210
  onSelect={onSelect}
@@ -1,14 +1,14 @@
1
- import type { MetadataRoute } from 'next';
2
-
3
- export default function robots(): MetadataRoute.Robots {
4
- const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://example.com';
5
-
6
- return {
7
- rules: {
8
- userAgent: '*',
9
- allow: '/',
10
- disallow: ['/api/', '/auth/', '/checkout/', '/account/'],
11
- },
12
- sitemap: `${baseUrl}/sitemap.xml`,
13
- };
14
- }
1
+ import type { MetadataRoute } from 'next';
2
+
3
+ export default function robots(): MetadataRoute.Robots {
4
+ const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://example.com';
5
+
6
+ return {
7
+ rules: {
8
+ userAgent: '*',
9
+ allow: '/',
10
+ disallow: ['/api/', '/auth/', '/checkout/', '/account/'],
11
+ },
12
+ sitemap: `${baseUrl}/sitemap.xml`,
13
+ };
14
+ }
@@ -1,25 +1,25 @@
1
- import type { MetadataRoute } from 'next';
2
- import { getServerClient } from '@/lib/brainerce';
3
-
4
- export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
5
- const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://example.com';
6
-
7
- const staticPages: MetadataRoute.Sitemap = [
8
- { url: baseUrl, lastModified: new Date(), priority: 1 },
9
- { url: `${baseUrl}/products`, lastModified: new Date(), priority: 0.9 },
10
- ];
11
-
12
- try {
13
- const client = getServerClient();
14
- const { data: products } = await client.getProducts({ limit: 1000 });
15
- const productPages: MetadataRoute.Sitemap = products.map((product) => ({
16
- url: `${baseUrl}/products/${product.slug}`,
17
- lastModified: product.updatedAt ? new Date(product.updatedAt) : new Date(),
18
- priority: 0.8,
19
- }));
20
-
21
- return [...staticPages, ...productPages];
22
- } catch {
23
- return staticPages;
24
- }
25
- }
1
+ import type { MetadataRoute } from 'next';
2
+ import { getServerClient } from '@/lib/brainerce';
3
+
4
+ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
5
+ const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://example.com';
6
+
7
+ const staticPages: MetadataRoute.Sitemap = [
8
+ { url: baseUrl, lastModified: new Date(), priority: 1 },
9
+ { url: `${baseUrl}/products`, lastModified: new Date(), priority: 0.9 },
10
+ ];
11
+
12
+ try {
13
+ const client = getServerClient();
14
+ const { data: products } = await client.getProducts({ limit: 1000 });
15
+ const productPages: MetadataRoute.Sitemap = products.map((product) => ({
16
+ url: `${baseUrl}/products/${product.slug}`,
17
+ lastModified: product.updatedAt ? new Date(product.updatedAt) : new Date(),
18
+ priority: 0.8,
19
+ }));
20
+
21
+ return [...staticPages, ...productPages];
22
+ } catch {
23
+ return staticPages;
24
+ }
25
+ }
@@ -77,7 +77,7 @@ function OrderCard({ order }: { order: Order }) {
77
77
  const t = useTranslations('account');
78
78
  const tc = useTranslations('common');
79
79
  const [expanded, setExpanded] = useState(false);
80
- const statusConfig = STATUS_CONFIG[order.status] || STATUS_CONFIG.pending;
80
+ const statusConfig = STATUS_CONFIG[order.status?.toLowerCase() as OrderStatus] || STATUS_CONFIG.pending;
81
81
  const currency = order.currency || 'USD';
82
82
  const totalAmount = order.totalAmount || order.total || '0';
83
83
 
@@ -243,11 +243,11 @@ function OrderDownloads({ orderId }: { orderId: string }) {
243
243
  {link.productName}
244
244
  {' · '}
245
245
  {link.downloadLimit != null
246
- ? t('downloadsRemaining', { used: link.downloadsUsed, limit: link.downloadLimit })
246
+ ? `${link.downloadsUsed}/${link.downloadLimit} ${t('downloadsRemaining')}`
247
247
  : t('unlimitedDownloads')}
248
248
  {' · '}
249
249
  {link.expiresAt
250
- ? t('expiresAt', { date: new Date(link.expiresAt).toLocaleDateString() })
250
+ ? `${t('expiresAt')} ${new Date(link.expiresAt).toLocaleDateString()}`
251
251
  : t('noExpiry')}
252
252
  </p>
253
253
  </div>
@@ -243,9 +243,7 @@ export function PaymentStep({ checkoutId, className }: PaymentStepProps) {
243
243
  },
244
244
  };
245
245
 
246
- console.info(
247
- `Payment SDK: ${method}({ environment: "${config.environment}", version: ${config.version} })`
248
- );
246
+ console.info(`Payment SDK: calling ${method}()`);
249
247
  global[method](config);
250
248
  sdkInitDone = true;
251
249
  }
@@ -81,7 +81,7 @@ export function Header() {
81
81
  <div className="flex h-16 items-center justify-between gap-4">
82
82
  {/* Logo / Store Name */}
83
83
  <Link href="/" className="text-foreground flex-shrink-0 text-xl font-bold">
84
- {storeInfo?.name || tc('store')}
84
+ {storeInfo?.name || process.env.NEXT_PUBLIC_STORE_NAME || tc('store')}
85
85
  </Link>
86
86
 
87
87
  {/* Desktop Navigation */}
@@ -1,6 +1,5 @@
1
1
  'use client';
2
2
 
3
- import { getStockStatus } from 'brainerce';
4
3
  import type { InventoryInfo } from 'brainerce';
5
4
  import { cn } from '@/lib/utils';
6
5
 
@@ -11,24 +10,44 @@ interface StockBadgeProps {
11
10
  }
12
11
 
13
12
  export function StockBadge({ inventory, lowStockThreshold = 5, className }: StockBadgeProps) {
14
- const status = getStockStatus(inventory, { lowStockThreshold });
15
-
16
- const colorClasses =
17
- status === 'Out of Stock' || status === 'Unavailable'
18
- ? 'bg-red-100 text-red-800'
19
- : status === 'Low Stock'
20
- ? 'bg-yellow-100 text-yellow-800'
21
- : 'bg-green-100 text-green-800';
13
+ const label = getStockLabel(inventory, lowStockThreshold);
14
+ const color = getStockColor(inventory, lowStockThreshold);
22
15
 
23
16
  return (
24
17
  <span
25
18
  className={cn(
26
19
  'inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium',
27
- colorClasses,
20
+ color,
28
21
  className
29
22
  )}
30
23
  >
31
- {status}
24
+ {label}
32
25
  </span>
33
26
  );
34
27
  }
28
+
29
+ function getStockLabel(inventory: InventoryInfo | null | undefined, lowStockThreshold: number): string {
30
+ if (!inventory) return 'Out of Stock';
31
+
32
+ const { trackingMode, inStock, available } = inventory;
33
+
34
+ if (trackingMode === 'DISABLED') return 'Unavailable';
35
+ if (!inStock) return 'Out of Stock';
36
+ if (trackingMode === 'UNLIMITED') return 'In Stock';
37
+
38
+ // TRACKED — show actual quantity
39
+ if (available <= lowStockThreshold) {
40
+ return `Only ${available} left`;
41
+ }
42
+ return `${available} in stock`;
43
+ }
44
+
45
+ function getStockColor(inventory: InventoryInfo | null | undefined, lowStockThreshold: number): string {
46
+ if (!inventory) return 'bg-red-100 text-red-800';
47
+
48
+ const { trackingMode, inStock, available } = inventory;
49
+
50
+ if (trackingMode === 'DISABLED' || !inStock) return 'bg-red-100 text-red-800';
51
+ if (trackingMode === 'TRACKED' && available <= lowStockThreshold) return 'bg-yellow-100 text-yellow-800';
52
+ return 'bg-green-100 text-green-800';
53
+ }
@@ -1,39 +1,39 @@
1
- import type { Product } from 'brainerce';
2
- import { getProductPriceInfo } from 'brainerce';
3
-
4
- interface ProductJsonLdProps {
5
- product: Product;
6
- url: string;
7
- }
8
-
9
- export function ProductJsonLd({ product, url }: ProductJsonLdProps) {
10
- const priceInfo = getProductPriceInfo(product);
11
- const imageUrl = product.images?.[0]?.url;
12
-
13
- const jsonLd = {
14
- '@context': 'https://schema.org',
15
- '@type': 'Product',
16
- name: product.name,
17
- description: product.description || product.name,
18
- image: imageUrl,
19
- url,
20
- sku: product.sku || product.id,
21
- offers: {
22
- '@type': 'Offer',
23
- price: priceInfo.price,
24
- priceCurrency: 'ILS',
25
- availability:
26
- product.inventory?.canPurchase !== false
27
- ? 'https://schema.org/InStock'
28
- : 'https://schema.org/OutOfStock',
29
- url,
30
- },
31
- };
32
-
33
- return (
34
- <script
35
- type="application/ld+json"
36
- dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
37
- />
38
- );
39
- }
1
+ import type { Product } from 'brainerce';
2
+ import { getProductPriceInfo } from 'brainerce';
3
+
4
+ interface ProductJsonLdProps {
5
+ product: Product;
6
+ url: string;
7
+ }
8
+
9
+ export function ProductJsonLd({ product, url }: ProductJsonLdProps) {
10
+ const priceInfo = getProductPriceInfo(product);
11
+ const imageUrl = product.images?.[0]?.url;
12
+
13
+ const jsonLd = {
14
+ '@context': 'https://schema.org',
15
+ '@type': 'Product',
16
+ name: product.name,
17
+ description: product.description || product.name,
18
+ image: imageUrl,
19
+ url,
20
+ sku: product.sku || product.id,
21
+ offers: {
22
+ '@type': 'Offer',
23
+ price: priceInfo.price,
24
+ priceCurrency: 'ILS',
25
+ availability:
26
+ product.inventory?.canPurchase !== false
27
+ ? 'https://schema.org/InStock'
28
+ : 'https://schema.org/OutOfStock',
29
+ url,
30
+ },
31
+ };
32
+
33
+ return (
34
+ <script
35
+ type="application/ld+json"
36
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
37
+ />
38
+ );
39
+ }
@@ -45,5 +45,6 @@ export function getServerClient(): BrainerceClient {
45
45
  return new BrainerceClient({
46
46
  connectionId: CONNECTION_ID,
47
47
  baseUrl: apiUrl,
48
+ origin: process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000',
48
49
  });
49
50
  }