create-brainerce-store 1.3.0 → 1.3.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-brainerce-store",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "Scaffold a production-ready e-commerce storefront connected to Brainerce",
5
5
  "bin": {
6
6
  "create-brainerce-store": "dist/index.js"
@@ -1,36 +1,22 @@
1
- 'use client';
2
-
3
- import { useEffect, useState } from 'react';
4
- import type { ProductDiscountBadge } from 'brainerce';
5
- import { getClient } from '@/lib/brainerce';
6
- import { cn } from '@/lib/utils';
7
-
8
- interface DiscountBadgeProps {
9
- productId: string;
10
- className?: string;
11
- }
12
-
13
- export function DiscountBadge({ productId, className }: DiscountBadgeProps) {
14
- const [badge, setBadge] = useState<ProductDiscountBadge | null>(null);
15
-
16
- useEffect(() => {
17
- const client = getClient();
18
- client
19
- .getProductDiscountBadge(productId)
20
- .then(setBadge)
21
- .catch(() => setBadge(null));
22
- }, [productId]);
23
-
24
- if (!badge) return null;
25
-
26
- return (
27
- <span
28
- className={cn(
29
- 'bg-destructive text-destructive-foreground inline-flex items-center rounded px-2 py-1 text-xs font-bold',
30
- className
31
- )}
32
- >
33
- {badge.badgeText}
34
- </span>
35
- );
36
- }
1
+ import type { ProductDiscount } from 'brainerce';
2
+ import { cn } from '@/lib/utils';
3
+
4
+ interface DiscountBadgeProps {
5
+ discount?: ProductDiscount | null;
6
+ className?: string;
7
+ }
8
+
9
+ export function DiscountBadge({ discount, className }: DiscountBadgeProps) {
10
+ if (!discount) return null;
11
+
12
+ return (
13
+ <span
14
+ className={cn(
15
+ 'bg-destructive text-destructive-foreground inline-flex items-center rounded px-2 py-1 text-xs font-bold',
16
+ className
17
+ )}
18
+ >
19
+ {discount.badgeText}
20
+ </span>
21
+ );
22
+ }
@@ -1,94 +1,94 @@
1
- 'use client';
2
-
3
- import Link from 'next/link';
4
- import Image from 'next/image';
5
- import type { Product } from 'brainerce';
6
- import { getProductPriceInfo } from 'brainerce';
7
- import { PriceDisplay } from '@/components/shared/price-display';
8
- import { StockBadge } from '@/components/products/stock-badge';
9
- import { DiscountBadge } from '@/components/products/discount-badge';
10
- import { cn } from '@/lib/utils';
11
-
12
- interface ProductCardProps {
13
- product: Product;
14
- className?: string;
15
- }
16
-
17
- export function ProductCard({ product, className }: ProductCardProps) {
18
- const { price, originalPrice, isOnSale } = getProductPriceInfo(product);
19
- const mainImage = product.images?.[0];
20
- const imageUrl = mainImage?.url || null;
21
- const slug = product.slug || product.id;
22
-
23
- return (
24
- <Link
25
- href={`/products/${slug}`}
26
- className={cn(
27
- 'border-border bg-background group block overflow-hidden rounded-lg border transition-shadow hover:shadow-md',
28
- className
29
- )}
30
- >
31
- {/* Image */}
32
- <div className="bg-muted relative aspect-square overflow-hidden">
33
- {imageUrl ? (
34
- <Image
35
- src={imageUrl}
36
- alt={mainImage?.alt || product.name}
37
- fill
38
- sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 25vw"
39
- className="object-cover transition-transform duration-300 group-hover:scale-105"
40
- />
41
- ) : (
42
- <div className="text-muted-foreground absolute inset-0 flex items-center justify-center">
43
- <svg className="h-12 w-12" fill="none" viewBox="0 0 24 24" stroke="currentColor">
44
- <path
45
- strokeLinecap="round"
46
- strokeLinejoin="round"
47
- strokeWidth={1.5}
48
- d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
49
- />
50
- </svg>
51
- </div>
52
- )}
53
-
54
- {/* Badges */}
55
- <div className="absolute start-2 top-2 flex flex-col gap-1">
56
- {isOnSale && (
57
- <span className="bg-destructive text-destructive-foreground rounded px-2 py-1 text-xs font-bold">
58
- Sale
59
- </span>
60
- )}
61
- <DiscountBadge productId={product.id} />
62
- </div>
63
- </div>
64
-
65
- {/* Content */}
66
- <div className="space-y-2 p-3">
67
- {/* Categories */}
68
- {product.categories && product.categories.length > 0 && (
69
- <div className="flex flex-wrap gap-1">
70
- {product.categories.slice(0, 2).map((cat) => (
71
- <span
72
- key={cat.id}
73
- className="text-muted-foreground bg-muted rounded px-1.5 py-0.5 text-[10px]"
74
- >
75
- {cat.name}
76
- </span>
77
- ))}
78
- </div>
79
- )}
80
-
81
- {/* Name */}
82
- <h3 className="text-foreground group-hover:text-primary line-clamp-2 text-sm font-medium transition-colors">
83
- {product.name}
84
- </h3>
85
-
86
- {/* Price */}
87
- <PriceDisplay price={originalPrice} salePrice={isOnSale ? price : undefined} size="sm" />
88
-
89
- {/* Stock */}
90
- <StockBadge inventory={product.inventory} />
91
- </div>
92
- </Link>
93
- );
94
- }
1
+ 'use client';
2
+
3
+ import Link from 'next/link';
4
+ import Image from 'next/image';
5
+ import type { Product } from 'brainerce';
6
+ import { getProductPriceInfo } from 'brainerce';
7
+ import { PriceDisplay } from '@/components/shared/price-display';
8
+ import { StockBadge } from '@/components/products/stock-badge';
9
+ import { DiscountBadge } from '@/components/products/discount-badge';
10
+ import { cn } from '@/lib/utils';
11
+
12
+ interface ProductCardProps {
13
+ product: Product;
14
+ className?: string;
15
+ }
16
+
17
+ export function ProductCard({ product, className }: ProductCardProps) {
18
+ const { price, originalPrice, isOnSale } = getProductPriceInfo(product);
19
+ const mainImage = product.images?.[0];
20
+ const imageUrl = mainImage?.url || null;
21
+ const slug = product.slug || product.id;
22
+
23
+ return (
24
+ <Link
25
+ href={`/products/${slug}`}
26
+ className={cn(
27
+ 'border-border bg-background group block overflow-hidden rounded-lg border transition-shadow hover:shadow-md',
28
+ className
29
+ )}
30
+ >
31
+ {/* Image */}
32
+ <div className="bg-muted relative aspect-square overflow-hidden">
33
+ {imageUrl ? (
34
+ <Image
35
+ src={imageUrl}
36
+ alt={mainImage?.alt || product.name}
37
+ fill
38
+ sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 25vw"
39
+ className="object-cover transition-transform duration-300 group-hover:scale-105"
40
+ />
41
+ ) : (
42
+ <div className="text-muted-foreground absolute inset-0 flex items-center justify-center">
43
+ <svg className="h-12 w-12" fill="none" viewBox="0 0 24 24" stroke="currentColor">
44
+ <path
45
+ strokeLinecap="round"
46
+ strokeLinejoin="round"
47
+ strokeWidth={1.5}
48
+ d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
49
+ />
50
+ </svg>
51
+ </div>
52
+ )}
53
+
54
+ {/* Badges */}
55
+ <div className="absolute start-2 top-2 flex flex-col gap-1">
56
+ {isOnSale && (
57
+ <span className="bg-destructive text-destructive-foreground rounded px-2 py-1 text-xs font-bold">
58
+ Sale
59
+ </span>
60
+ )}
61
+ <DiscountBadge discount={product.discount} />
62
+ </div>
63
+ </div>
64
+
65
+ {/* Content */}
66
+ <div className="space-y-2 p-3">
67
+ {/* Categories */}
68
+ {product.categories && product.categories.length > 0 && (
69
+ <div className="flex flex-wrap gap-1">
70
+ {product.categories.slice(0, 2).map((cat) => (
71
+ <span
72
+ key={cat.id}
73
+ className="text-muted-foreground bg-muted rounded px-1.5 py-0.5 text-[10px]"
74
+ >
75
+ {cat.name}
76
+ </span>
77
+ ))}
78
+ </div>
79
+ )}
80
+
81
+ {/* Name */}
82
+ <h3 className="text-foreground group-hover:text-primary line-clamp-2 text-sm font-medium transition-colors">
83
+ {product.name}
84
+ </h3>
85
+
86
+ {/* Price */}
87
+ <PriceDisplay price={originalPrice} salePrice={isOnSale ? price : undefined} size="sm" />
88
+
89
+ {/* Stock */}
90
+ <StockBadge inventory={product.inventory} />
91
+ </div>
92
+ </Link>
93
+ );
94
+ }