@promakeai/cli 0.1.2 → 0.1.3
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 +56 -56
- package/dist/registry/about-page.json +2 -2
- package/dist/registry/about-section.json +2 -2
- package/dist/registry/announcement-bar.json +1 -1
- package/dist/registry/blog-section.json +6 -4
- package/dist/registry/cart-drawer.json +5 -4
- package/dist/registry/case-study-page.json +2 -2
- package/dist/registry/coming-soon-page-minimal.json +1 -1
- package/dist/registry/coming-soon-page.json +1 -1
- package/dist/registry/contact-info-grid.json +2 -2
- package/dist/registry/contact-page-centered.json +2 -2
- package/dist/registry/contact-page-split.json +2 -2
- package/dist/registry/contact-page.json +2 -2
- package/dist/registry/cta-section.json +2 -2
- package/dist/registry/docs/blog-section.md +3 -1
- package/dist/registry/docs/cart-drawer.md +9 -9
- package/dist/registry/docs/favorites-blog-block.md +10 -3
- package/dist/registry/docs/favorites-blog-page.md +38 -0
- package/dist/registry/docs/favorites-ecommerce-block.md +10 -3
- package/dist/registry/docs/favorites-ecommerce-page.md +38 -0
- package/dist/registry/docs/login-page.md +6 -16
- package/dist/registry/docs/payment-success-block.md +8 -1
- package/dist/registry/docs/post-detail-page.md +39 -0
- package/dist/registry/docs/product-card-detailed.md +7 -11
- package/dist/registry/docs/product-detail-page.md +39 -0
- package/dist/registry/docs/product-detail-section.md +7 -13
- package/dist/registry/docs/product-quick-view.md +4 -2
- package/dist/registry/faq-categorized.json +2 -2
- package/dist/registry/faq-simple.json +2 -2
- package/dist/registry/favorites-blog-block.json +1 -1
- package/dist/registry/favorites-blog-page.json +48 -0
- package/dist/registry/favorites-ecommerce-block.json +1 -1
- package/dist/registry/favorites-ecommerce-page.json +48 -0
- package/dist/registry/feature-section.json +2 -2
- package/dist/registry/footer.json +2 -2
- package/dist/registry/header-ecommerce.json +1 -1
- package/dist/registry/hero-carousel.json +2 -2
- package/dist/registry/hero-cta.json +2 -2
- package/dist/registry/hero-gradient.json +2 -2
- package/dist/registry/hero.json +2 -2
- package/dist/registry/index.json +9 -0
- package/dist/registry/landing-page-app.json +1 -1
- package/dist/registry/landing-page-saas.json +1 -1
- package/dist/registry/login-page.json +8 -6
- package/dist/registry/logo-cloud.json +1 -1
- package/dist/registry/payment-success-block.json +7 -3
- package/dist/registry/portfolio-page.json +2 -2
- package/dist/registry/post-detail-page.json +48 -0
- package/dist/registry/pricing-page.json +1 -1
- package/dist/registry/pricing-section.json +2 -2
- package/dist/registry/product-card-detailed.json +5 -4
- package/dist/registry/product-detail-page.json +48 -0
- package/dist/registry/product-detail-section.json +5 -4
- package/dist/registry/product-quick-view.json +5 -4
- package/dist/registry/team-page.json +1 -1
- package/dist/registry/testimonials-carousel.json +2 -2
- package/dist/registry/testimonials-grid.json +2 -2
- package/dist/registry/timeline-section.json +2 -2
- package/dist/registry/video-hero.json +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "post-detail-page",
|
|
3
|
+
"type": "registry:page",
|
|
4
|
+
"title": "Post Detail Page",
|
|
5
|
+
"description": "Blog post detail page that fetches post data by slug from URL params. Uses usePostBySlug hook from blog-core and renders PostDetailBlock. Includes loading skeleton, error handling for not found posts, and automatic page title.",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"blog-core",
|
|
8
|
+
"post-detail-block"
|
|
9
|
+
],
|
|
10
|
+
"usage": "import { PostDetailPage } from '@/modules/post-detail-page';\n\n<Route path=\"/blog/:slug\" element={<PostDetailPage />} />\n\n• Uses usePostBySlug() from blog-core\n• Fetches post by slug from URL params\n• Shows loading skeleton while fetching\n• Handles post not found state",
|
|
11
|
+
"route": {
|
|
12
|
+
"path": "/blog/:slug",
|
|
13
|
+
"componentName": "PostDetailPage"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
{
|
|
17
|
+
"path": "post-detail-page/index.ts",
|
|
18
|
+
"type": "registry:index",
|
|
19
|
+
"target": "$modules$/post-detail-page/index.ts",
|
|
20
|
+
"content": "export * from './post-detail-page';\r\nexport { PostDetailPage as default } from './post-detail-page';\r\n"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"path": "post-detail-page/post-detail-page.tsx",
|
|
24
|
+
"type": "registry:page",
|
|
25
|
+
"target": "$modules$/post-detail-page/post-detail-page.tsx",
|
|
26
|
+
"content": "import { useParams } from \"react-router\";\r\nimport { usePostBySlug } from \"@/modules/blog-core\";\r\nimport { PostDetailBlock } from \"@/modules/post-detail-block\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { useTranslation } from \"react-i18next\";\r\n\r\nexport function PostDetailPage() {\r\n const { t } = useTranslation(\"post-detail-page\");\r\n const { slug } = useParams<{ slug: string }>();\r\n const { post, loading, error } = usePostBySlug(slug || \"\");\r\n\r\n usePageTitle({ title: post?.title || t(\"loading\", \"Loading...\") });\r\n\r\n if (loading) {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\r\n <div className=\"animate-pulse space-y-4\">\r\n <div className=\"h-8 bg-muted rounded w-1/3\"></div>\r\n <div className=\"h-4 bg-muted rounded w-1/4\"></div>\r\n <div className=\"h-64 bg-muted rounded\"></div>\r\n <div className=\"space-y-2\">\r\n <div className=\"h-4 bg-muted rounded\"></div>\r\n <div className=\"h-4 bg-muted rounded\"></div>\r\n <div className=\"h-4 bg-muted rounded w-3/4\"></div>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n if (error || !post) {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8 text-center\">\r\n <h1 className=\"text-2xl font-bold mb-4\">{t(\"notFound\", \"Post Not Found\")}</h1>\r\n <p className=\"text-muted-foreground\">{t(\"notFoundDescription\", \"The post you're looking for doesn't exist or has been removed.\")}</p>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n return (\r\n <Layout>\r\n <PostDetailBlock post={post} />\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default PostDetailPage;\r\n"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"path": "post-detail-page/lang/en.json",
|
|
30
|
+
"type": "registry:lang",
|
|
31
|
+
"target": "$modules$/post-detail-page/lang/en.json",
|
|
32
|
+
"content": "{\r\n \"loading\": \"Loading...\",\r\n \"notFound\": \"Post Not Found\",\r\n \"notFoundDescription\": \"The post you're looking for doesn't exist or has been removed.\"\r\n}\r\n"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"path": "post-detail-page/lang/tr.json",
|
|
36
|
+
"type": "registry:lang",
|
|
37
|
+
"target": "$modules$/post-detail-page/lang/tr.json",
|
|
38
|
+
"content": "{\r\n \"loading\": \"Yükleniyor...\",\r\n \"notFound\": \"Yazı Bulunamadı\",\r\n \"notFoundDescription\": \"Aradığınız yazı mevcut değil veya kaldırılmış.\"\r\n}\r\n"
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"exports": {
|
|
42
|
+
"types": [],
|
|
43
|
+
"variables": [
|
|
44
|
+
"PostDetailPage",
|
|
45
|
+
"default"
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"path": "pricing-page/lang/en.json",
|
|
29
29
|
"type": "registry:lang",
|
|
30
30
|
"target": "$modules$/pricing-page/lang/en.json",
|
|
31
|
-
"content": "{\r\n \"title\": \"Pricing\",\r\n \"label\": \"Pricing\",\r\n \"heading\": \"Simple, Transparent Pricing\",\r\n \"description\": \"
|
|
31
|
+
"content": "{\r\n \"title\": \"Pricing\",\r\n \"label\": \"Pricing\",\r\n \"heading\": \"Simple, Transparent Pricing\",\r\n \"description\": \"Customize this pricing description with Promake to match your plans.\",\r\n \"monthly\": \"Monthly\",\r\n \"annual\": \"Annual\",\r\n \"save\": \"Save 20%\",\r\n \"month\": \"mo\",\r\n \"billedAnnually\": \"Billed annually\",\r\n \"mostPopular\": \"Most Popular\",\r\n \"freeName\": \"Free\",\r\n \"freeDesc\": \"Perfect for getting started\",\r\n \"freeFeature1\": \"Feature included\",\r\n \"freeFeature2\": \"Feature included\",\r\n \"freeFeature3\": \"Feature included\",\r\n \"freeFeature4\": \"Feature included\",\r\n \"freeFeature5\": \"Feature not included\",\r\n \"freeFeature6\": \"Feature not included\",\r\n \"freeFeature7\": \"Feature not included\",\r\n \"freeCta\": \"Get Started\",\r\n \"proName\": \"Professional\",\r\n \"proDesc\": \"Best for growing businesses\",\r\n \"proFeature1\": \"All Free features\",\r\n \"proFeature2\": \"Feature included\",\r\n \"proFeature3\": \"Feature included\",\r\n \"proFeature4\": \"Feature included\",\r\n \"proFeature5\": \"Feature included\",\r\n \"proFeature6\": \"Feature included\",\r\n \"proFeature7\": \"Feature included\",\r\n \"proCta\": \"Start Free Trial\",\r\n \"enterpriseName\": \"Enterprise\",\r\n \"enterpriseDesc\": \"For large organizations\",\r\n \"enterpriseFeature1\": \"All Pro features\",\r\n \"enterpriseFeature2\": \"Feature included\",\r\n \"enterpriseFeature3\": \"Feature included\",\r\n \"enterpriseFeature4\": \"Feature included\",\r\n \"enterpriseFeature5\": \"Feature included\",\r\n \"enterpriseFeature6\": \"Feature included\",\r\n \"enterpriseFeature7\": \"Feature included\",\r\n \"enterpriseCta\": \"Contact Sales\",\r\n \"compareTitle\": \"Compare Plans\",\r\n \"feature\": \"Feature\",\r\n \"compProjects\": \"Projects\",\r\n \"compStorage\": \"Storage\",\r\n \"compUsers\": \"Team members\",\r\n \"compApi\": \"API requests\",\r\n \"compSupport\": \"Support\",\r\n \"unlimited\": \"Unlimited\",\r\n \"community\": \"Community\",\r\n \"email\": \"Email\",\r\n \"dedicated\": \"Dedicated\",\r\n \"faqTooltip\": \"Click to see frequently asked questions\",\r\n \"faqTitle\": \"Have questions?\",\r\n \"faqDesc\": \"Check out our FAQ or contact our sales team for more information.\",\r\n \"viewFaq\": \"View FAQ\",\r\n \"contactSales\": \"Contact Sales\"\r\n}\r\n"
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
34
|
"path": "pricing-page/lang/tr.json",
|
|
@@ -25,13 +25,13 @@
|
|
|
25
25
|
"path": "pricing-section/lang/en.json",
|
|
26
26
|
"type": "registry:lang",
|
|
27
27
|
"target": "$modules$/pricing-section/lang/en.json",
|
|
28
|
-
"content": "{\r\n \"label\": \"Pricing\",\r\n \"title\": \"
|
|
28
|
+
"content": "{\r\n \"label\": \"Pricing\",\r\n \"title\": \"Customize this pricing title with Promake\",\r\n \"subtitle\": \"Let Promake personalize this with pricing details for your services.\",\r\n \"perMonth\": \"/month\",\r\n \"popular\": \"Most Popular\",\r\n \"cta\": \"Get Started\",\r\n \"guarantee\": \"Lorem ipsum dolor sit amet. Update this guarantee text using Promake.\",\r\n \"starterName\": \"Starter\",\r\n \"starterDesc\": \"Work with Promake to tailor this plan description\",\r\n \"starterPrice\": \"$9\",\r\n \"starterFeature1\": \"Replace with your starter plan feature\",\r\n \"starterFeature2\": \"Promake can help customize this feature\",\r\n \"starterFeature3\": \"Placeholder feature text\",\r\n \"starterFeature4\": \"Have Promake update this with relevant feature\",\r\n \"proName\": \"Pro\",\r\n \"proDesc\": \"Edit this description via Promake for your mid-tier plan\",\r\n \"proPrice\": \"$29\",\r\n \"proFeature1\": \"Customize this plan feature\",\r\n \"proFeature2\": \"Use Promake to generate an appropriate feature description\",\r\n \"proFeature3\": \"Placeholder text for plan features\",\r\n \"proFeature4\": \"Replace with actual plan feature\",\r\n \"proFeature5\": \"Let Promake tailor this feature to your plan\",\r\n \"enterpriseName\": \"Advanced\",\r\n \"enterpriseDesc\": \"Promake can help personalize this for your advanced tier\",\r\n \"enterprisePrice\": \"$99\",\r\n \"enterpriseFeature1\": \"Advanced plan feature placeholder\",\r\n \"enterpriseFeature2\": \"Work with Promake to add advanced features\",\r\n \"enterpriseFeature3\": \"Customize advanced plan feature description\",\r\n \"enterpriseFeature4\": \"Update this content using Promake\",\r\n \"enterpriseFeature5\": \"Placeholder for advanced plan feature\",\r\n \"enterpriseFeature6\": \"Replace with an actual advanced plan feature\"\r\n}\r\n"
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"path": "pricing-section/lang/tr.json",
|
|
32
32
|
"type": "registry:lang",
|
|
33
33
|
"target": "$modules$/pricing-section/lang/tr.json",
|
|
34
|
-
"content": "{\r\n \"label\": \"Fiyatlandırma\",\r\n \"title\": \"
|
|
34
|
+
"content": "{\r\n \"label\": \"Fiyatlandırma\",\r\n \"title\": \"Bu fiyatlandırma başlığını Promake ile özelleştirin\",\r\n \"subtitle\": \"Promake ile hizmetleriniz için fiyatlandırma detaylarıyla kişiselleştirin.\",\r\n \"perMonth\": \"/ay\",\r\n \"popular\": \"En Popüler\",\r\n \"cta\": \"Başlayın\",\r\n \"guarantee\": \"Lorem ipsum dolor sit amet. Bu garanti metnini Promake kullanarak güncelleyin.\",\r\n \"starterName\": \"Başlangıç\",\r\n \"starterDesc\": \"Promake ile bu plan açıklamasını uyarlayın\",\r\n \"starterPrice\": \"99₺\",\r\n \"starterFeature1\": \"Başlangıç planı özelliğinizle değiştirin\",\r\n \"starterFeature2\": \"Promake bu özelliği özelleştirmenize yardımcı olabilir\",\r\n \"starterFeature3\": \"Placeholder özellik metni\",\r\n \"starterFeature4\": \"Promake'ten bunu ilgili özellikle güncellemesini isteyin\",\r\n \"proName\": \"Pro\",\r\n \"proDesc\": \"Bu açıklamayı orta seviye planınız için Promake üzerinden düzenleyin\",\r\n \"proPrice\": \"299₺\",\r\n \"proFeature1\": \"Bu plan özelliğini özelleştirin\",\r\n \"proFeature2\": \"Uygun özellik açıklaması üretmek için Promake kullanın\",\r\n \"proFeature3\": \"Plan özellikleri için placeholder metin\",\r\n \"proFeature4\": \"Gerçek plan özelliğiyle değiştirin\",\r\n \"proFeature5\": \"Promake ile bu özelliği planınıza göre uyarlayın\",\r\n \"enterpriseName\": \"Gelişmiş\",\r\n \"enterpriseDesc\": \"Promake bunu gelişmiş seviyeniz için kişiselleştirmenize yardımcı olabilir\",\r\n \"enterprisePrice\": \"999₺\",\r\n \"enterpriseFeature1\": \"Gelişmiş plan özelliği placeholder\",\r\n \"enterpriseFeature2\": \"Promake ile gelişmiş özellikler ekleyin\",\r\n \"enterpriseFeature3\": \"Gelişmiş plan özelliği açıklamasını özelleştirin\",\r\n \"enterpriseFeature4\": \"Bu içeriği Promake kullanarak güncelleyin\",\r\n \"enterpriseFeature5\": \"Gelişmiş plan özelliği için placeholder\",\r\n \"enterpriseFeature6\": \"Gerçek gelişmiş plan özelliğiyle değiştirin\"\r\n}\r\n"
|
|
35
35
|
}
|
|
36
36
|
],
|
|
37
37
|
"exports": {
|
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
"name": "product-card-detailed",
|
|
3
3
|
"type": "registry:component",
|
|
4
4
|
"title": "Product Card Detailed",
|
|
5
|
-
"description": "Detailed product card with wishlist
|
|
5
|
+
"description": "Detailed product card with wishlist toggle, hover scale effect, price with optional discount, description, and Add to Cart/Buy Now action buttons. Integrated with ecommerce-core for cart and favorites.",
|
|
6
6
|
"dependencies": [
|
|
7
7
|
"lucide-react"
|
|
8
8
|
],
|
|
9
9
|
"registryDependencies": [
|
|
10
|
-
"button"
|
|
10
|
+
"button",
|
|
11
|
+
"ecommerce-core"
|
|
11
12
|
],
|
|
12
|
-
"usage": "import { ProductCardDetailed } from '@/modules/product-card-detailed';\n\n<ProductCardDetailed
|
|
13
|
+
"usage": "import { ProductCardDetailed } from '@/modules/product-card-detailed';\nimport type { Product } from '@/modules/ecommerce-core';\n\n<ProductCardDetailed product={product} />\n\n• Uses useCart and useFavorites from ecommerce-core\n• Wishlist toggle shows filled heart when favorited",
|
|
13
14
|
"files": [
|
|
14
15
|
{
|
|
15
16
|
"path": "product-card-detailed/index.ts",
|
|
@@ -21,7 +22,7 @@
|
|
|
21
22
|
"path": "product-card-detailed/product-card-detailed.tsx",
|
|
22
23
|
"type": "registry:component",
|
|
23
24
|
"target": "$modules$/product-card-detailed/product-card-detailed.tsx",
|
|
24
|
-
"content": "import { Link } from \"react-router\";\r\nimport { Heart } from \"lucide-react\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { useTranslation } from \"react-i18next\";\r\n\r\ninterface ProductCardDetailedProps {\r\n
|
|
25
|
+
"content": "import { Link, useNavigate } from \"react-router\";\r\nimport { Heart } from \"lucide-react\";\r\nimport { toast } from \"sonner\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport {\r\n useCart,\r\n useFavorites,\r\n formatPrice,\r\n type Product,\r\n} from \"@/modules/ecommerce-core\";\r\nimport constants from \"@/constants/constants.json\";\r\n\r\ninterface ProductCardDetailedProps {\r\n product: Product;\r\n className?: string;\r\n}\r\n\r\nexport function ProductCardDetailed({\r\n product,\r\n className,\r\n}: ProductCardDetailedProps) {\r\n const { t } = useTranslation(\"product-card-detailed\");\r\n const navigate = useNavigate();\r\n const { addItem } = useCart();\r\n const { isFavorite, addToFavorites, removeFromFavorites } = useFavorites();\r\n const currency = (constants.site as any).currency || \"USD\";\r\n\r\n if (!product) {\r\n return null;\r\n }\r\n\r\n const price = product.on_sale && product.sale_price\r\n ? product.sale_price\r\n : product.price;\r\n\r\n const handleAddToCart = () => {\r\n addItem(product);\r\n toast.success(t(\"addedToCart\", \"Added to cart!\"));\r\n };\r\n\r\n const handleBuyNow = () => {\r\n addItem(product);\r\n navigate(\"/checkout\");\r\n };\r\n\r\n const handleToggleFavorite = () => {\r\n if (isFavorite(product.id)) {\r\n removeFromFavorites(product.id);\r\n } else {\r\n addToFavorites(product);\r\n }\r\n };\r\n\r\n return (\r\n <div\r\n className={cn(\r\n \"group relative block overflow-hidden h-full flex flex-col\",\r\n className\r\n )}\r\n >\r\n <div className=\"relative\">\r\n <button\r\n onClick={handleToggleFavorite}\r\n className=\"absolute end-4 top-4 z-10 rounded-full bg-background p-1.5 transition hover:text-primary\"\r\n >\r\n <span className=\"sr-only\">{t(\"wishlist\", \"Wishlist\")}</span>\r\n <Heart\r\n className={cn(\r\n \"size-4\",\r\n isFavorite(product.id) && \"fill-current text-red-500\"\r\n )}\r\n />\r\n </button>\r\n\r\n <Link to={`/products/${product.slug}`}>\r\n <img\r\n src={product.images[0] || \"/images/placeholder.png\"}\r\n alt={product.name}\r\n className=\"h-64 w-full object-cover transition duration-500 group-hover:scale-105 sm:h-72\"\r\n />\r\n </Link>\r\n </div>\r\n\r\n <div className=\"relative border border-border bg-background p-6 flex-1 flex flex-col\">\r\n <p>\r\n {formatPrice(price, currency)}\r\n {product.on_sale && product.sale_price && (\r\n <span className=\"text-muted-foreground line-through ml-2\">\r\n {formatPrice(product.price, currency)}\r\n </span>\r\n )}\r\n </p>\r\n\r\n <h3 className=\"mt-1.5 text-lg font-medium\">\r\n <Link to={`/products/${product.slug}`} className=\"hover:underline\">\r\n {product.name}\r\n </Link>\r\n </h3>\r\n\r\n <div className=\"mt-1.5 min-h-[4.5rem]\">\r\n {product.description && (\r\n <p className=\"line-clamp-3 text-muted-foreground text-sm\">\r\n {product.description}\r\n </p>\r\n )}\r\n </div>\r\n\r\n <div className=\"mt-auto pt-4 flex flex-col gap-3\">\r\n <Button\r\n className=\"w-full transition hover:scale-105\"\r\n onClick={handleBuyNow}\r\n >\r\n {t(\"buyNow\", \"Buy Now\")}\r\n </Button>\r\n <Button\r\n variant=\"secondary\"\r\n className=\"w-full transition hover:scale-105\"\r\n onClick={handleAddToCart}\r\n >\r\n {t(\"addToCart\", \"Add to Cart\")}\r\n </Button>\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n}\r\n"
|
|
25
26
|
},
|
|
26
27
|
{
|
|
27
28
|
"path": "product-card-detailed/lang/en.json",
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "product-detail-page",
|
|
3
|
+
"type": "registry:page",
|
|
4
|
+
"title": "Product Detail Page",
|
|
5
|
+
"description": "Product detail page that fetches product data by slug from URL params. Uses useProductBySlug hook from ecommerce-core and renders ProductDetailBlock. Includes loading skeleton, error handling for not found products, and automatic page title.",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"ecommerce-core",
|
|
8
|
+
"product-detail-block"
|
|
9
|
+
],
|
|
10
|
+
"usage": "import { ProductDetailPage } from '@/modules/product-detail-page';\n\n<Route path=\"/products/:slug\" element={<ProductDetailPage />} />\n\n• Uses useProductBySlug() from ecommerce-core\n• Fetches product by slug from URL params\n• Shows loading skeleton while fetching\n• Handles product not found state",
|
|
11
|
+
"route": {
|
|
12
|
+
"path": "/products/:slug",
|
|
13
|
+
"componentName": "ProductDetailPage"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
{
|
|
17
|
+
"path": "product-detail-page/index.ts",
|
|
18
|
+
"type": "registry:index",
|
|
19
|
+
"target": "$modules$/product-detail-page/index.ts",
|
|
20
|
+
"content": "export * from './product-detail-page';\r\nexport { ProductDetailPage as default } from './product-detail-page';\r\n"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"path": "product-detail-page/product-detail-page.tsx",
|
|
24
|
+
"type": "registry:page",
|
|
25
|
+
"target": "$modules$/product-detail-page/product-detail-page.tsx",
|
|
26
|
+
"content": "import { useParams } from \"react-router\";\r\nimport { useProductBySlug } from \"@/modules/ecommerce-core\";\r\nimport { ProductDetailBlock } from \"@/modules/product-detail-block\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { useTranslation } from \"react-i18next\";\r\n\r\nexport function ProductDetailPage() {\r\n const { t } = useTranslation(\"product-detail-page\");\r\n const { slug } = useParams<{ slug: string }>();\r\n const { product, loading, error } = useProductBySlug(slug || \"\");\r\n\r\n usePageTitle({ title: product?.name || t(\"loading\", \"Loading...\") });\r\n\r\n if (loading) {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\r\n <div className=\"animate-pulse\">\r\n <div className=\"grid lg:grid-cols-2 gap-12\">\r\n <div className=\"aspect-square bg-muted rounded-lg\"></div>\r\n <div className=\"space-y-4\">\r\n <div className=\"h-6 bg-muted rounded w-1/4\"></div>\r\n <div className=\"h-10 bg-muted rounded w-3/4\"></div>\r\n <div className=\"h-4 bg-muted rounded w-1/3\"></div>\r\n <div className=\"h-8 bg-muted rounded w-1/4\"></div>\r\n <div className=\"space-y-2\">\r\n <div className=\"h-4 bg-muted rounded\"></div>\r\n <div className=\"h-4 bg-muted rounded\"></div>\r\n <div className=\"h-4 bg-muted rounded w-2/3\"></div>\r\n </div>\r\n <div className=\"h-12 bg-muted rounded w-1/2\"></div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n if (error || !product) {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8 text-center\">\r\n <h1 className=\"text-2xl font-bold mb-4\">{t(\"notFound\", \"Product Not Found\")}</h1>\r\n <p className=\"text-muted-foreground\">{t(\"notFoundDescription\", \"The product you're looking for doesn't exist or has been removed.\")}</p>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\r\n <ProductDetailBlock product={product} />\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default ProductDetailPage;\r\n"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"path": "product-detail-page/lang/en.json",
|
|
30
|
+
"type": "registry:lang",
|
|
31
|
+
"target": "$modules$/product-detail-page/lang/en.json",
|
|
32
|
+
"content": "{\r\n \"loading\": \"Loading...\",\r\n \"notFound\": \"Product Not Found\",\r\n \"notFoundDescription\": \"The product you're looking for doesn't exist or has been removed.\"\r\n}\r\n"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"path": "product-detail-page/lang/tr.json",
|
|
36
|
+
"type": "registry:lang",
|
|
37
|
+
"target": "$modules$/product-detail-page/lang/tr.json",
|
|
38
|
+
"content": "{\r\n \"loading\": \"Yükleniyor...\",\r\n \"notFound\": \"Ürün Bulunamadı\",\r\n \"notFoundDescription\": \"Aradığınız ürün mevcut değil veya kaldırılmış.\"\r\n}\r\n"
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"exports": {
|
|
42
|
+
"types": [],
|
|
43
|
+
"variables": [
|
|
44
|
+
"ProductDetailPage",
|
|
45
|
+
"default"
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
"name": "product-detail-section",
|
|
3
3
|
"type": "registry:component",
|
|
4
4
|
"title": "Product Detail Section",
|
|
5
|
-
"description": "Product detail UI section with image, title, brand, price, description,
|
|
5
|
+
"description": "Product detail UI section with image, title, brand, price, description, rating stars, social share buttons, and add to cart/wishlist actions. Integrated with ecommerce-core for cart and favorites.",
|
|
6
6
|
"dependencies": [
|
|
7
7
|
"lucide-react"
|
|
8
8
|
],
|
|
9
9
|
"registryDependencies": [
|
|
10
10
|
"button",
|
|
11
|
-
"select"
|
|
11
|
+
"select",
|
|
12
|
+
"ecommerce-core"
|
|
12
13
|
],
|
|
13
|
-
"usage": "import { ProductDetailSection } from '@/modules/product-detail-section';\n\n<ProductDetailSection
|
|
14
|
+
"usage": "import { ProductDetailSection } from '@/modules/product-detail-section';\nimport type { Product } from '@/modules/ecommerce-core';\n\n<ProductDetailSection product={product} />\n\n• Uses useCart and useFavorites from ecommerce-core\n• Wishlist toggle shows filled heart when favorited",
|
|
14
15
|
"files": [
|
|
15
16
|
{
|
|
16
17
|
"path": "product-detail-section/index.ts",
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
"path": "product-detail-section/product-detail-section.tsx",
|
|
23
24
|
"type": "registry:component",
|
|
24
25
|
"target": "$modules$/product-detail-section/product-detail-section.tsx",
|
|
25
|
-
"content": "import {
|
|
26
|
+
"content": "import { Star, Heart, Facebook, Twitter, MessageCircle } from \"lucide-react\";\r\nimport { toast } from \"sonner\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport {\r\n useCart,\r\n useFavorites,\r\n formatPrice,\r\n type Product,\r\n} from \"@/modules/ecommerce-core\";\r\nimport constants from \"@/constants/constants.json\";\r\n\r\ninterface ProductDetailSectionProps {\r\n product: Product;\r\n className?: string;\r\n}\r\n\r\nexport function ProductDetailSection({\r\n product,\r\n className,\r\n}: ProductDetailSectionProps) {\r\n const { t } = useTranslation(\"product-detail-section\");\r\n const { addItem } = useCart();\r\n const { isFavorite, addToFavorites, removeFromFavorites } = useFavorites();\r\n const currency = (constants.site as any).currency || \"USD\";\r\n\r\n if (!product) {\r\n return null;\r\n }\r\n\r\n const price = product.on_sale && product.sale_price\r\n ? product.sale_price\r\n : product.price;\r\n\r\n const handleAddToCart = () => {\r\n addItem(product);\r\n toast.success(t(\"addedToCart\", \"Added to cart!\"));\r\n };\r\n\r\n const handleToggleFavorite = () => {\r\n if (isFavorite(product.id)) {\r\n removeFromFavorites(product.id);\r\n } else {\r\n addToFavorites(product);\r\n }\r\n };\r\n\r\n const renderStars = (rating: number) => {\r\n return Array.from({ length: 5 }, (_, i) => (\r\n <Star\r\n key={i}\r\n className={cn(\r\n \"w-4 h-4\",\r\n i < Math.floor(rating) ? \"fill-primary text-primary\" : \"text-primary\"\r\n )}\r\n />\r\n ));\r\n };\r\n\r\n return (\r\n <section className={cn(\"py-24\", className)}>\r\n <div className=\"container px-5 mx-auto\">\r\n <div className=\"lg:w-4/5 mx-auto flex flex-wrap\">\r\n <img\r\n alt={product.name}\r\n className=\"lg:w-1/2 w-full lg:h-auto h-64 object-cover object-center rounded\"\r\n src={product.images[0] || \"/images/placeholder.png\"}\r\n />\r\n <div className=\"lg:w-1/2 w-full lg:pl-10 lg:py-6 mt-6 lg:mt-0\">\r\n {product.brand && (\r\n <h2 className=\"text-sm text-muted-foreground tracking-widest uppercase\">\r\n {product.brand}\r\n </h2>\r\n )}\r\n <h1 className=\"text-3xl font-medium mb-1\">{product.name}</h1>\r\n\r\n <div className=\"flex mb-4\">\r\n <span className=\"flex items-center\">\r\n {renderStars(product.rating)}\r\n <span className=\"text-muted-foreground ml-3\">\r\n {product.review_count} {t(\"reviews\", \"Reviews\")}\r\n </span>\r\n </span>\r\n <span className=\"flex ml-3 pl-3 py-2 border-l-2 border-border space-x-2\">\r\n <a\r\n href=\"#\"\r\n className=\"text-muted-foreground hover:text-foreground\"\r\n >\r\n <Facebook className=\"w-5 h-5\" />\r\n </a>\r\n <a\r\n href=\"#\"\r\n className=\"text-muted-foreground hover:text-foreground\"\r\n >\r\n <Twitter className=\"w-5 h-5\" />\r\n </a>\r\n <a\r\n href=\"#\"\r\n className=\"text-muted-foreground hover:text-foreground\"\r\n >\r\n <MessageCircle className=\"w-5 h-5\" />\r\n </a>\r\n </span>\r\n </div>\r\n\r\n <p className=\"leading-relaxed text-muted-foreground\">\r\n {product.description}\r\n </p>\r\n\r\n {product.category_name && (\r\n <p className=\"mt-4 text-sm text-muted-foreground\">\r\n {t(\"category\", \"Category\")}: {product.category_name}\r\n </p>\r\n )}\r\n\r\n <div className=\"flex mt-6 items-center pb-5 border-b-2 border-border mb-5\">\r\n {product.stock > 0 ? (\r\n <span className=\"text-sm text-green-600 dark:text-green-400\">\r\n {t(\"inStock\", \"In Stock\")} ({product.stock})\r\n </span>\r\n ) : (\r\n <span className=\"text-sm text-red-600 dark:text-red-400\">\r\n {t(\"outOfStock\", \"Out of Stock\")}\r\n </span>\r\n )}\r\n </div>\r\n\r\n <div className=\"flex items-center\">\r\n <span className=\"font-medium text-2xl\">\r\n {formatPrice(price, currency)}\r\n {product.on_sale && product.sale_price && (\r\n <span className=\"text-lg text-muted-foreground line-through ml-2\">\r\n {formatPrice(product.price, currency)}\r\n </span>\r\n )}\r\n </span>\r\n <Button\r\n onClick={handleAddToCart}\r\n className=\"ml-auto\"\r\n disabled={product.stock === 0}\r\n >\r\n {t(\"addToCart\", \"Add to Cart\")}\r\n </Button>\r\n <Button\r\n variant=\"outline\"\r\n size=\"icon\"\r\n onClick={handleToggleFavorite}\r\n className=\"rounded-full ml-4\"\r\n >\r\n <Heart\r\n className={cn(\r\n \"w-5 h-5\",\r\n isFavorite(product.id) && \"fill-current text-red-500\"\r\n )}\r\n />\r\n </Button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </section>\r\n );\r\n}\r\n"
|
|
26
27
|
},
|
|
27
28
|
{
|
|
28
29
|
"path": "product-detail-section/lang/en.json",
|
|
@@ -2,21 +2,22 @@
|
|
|
2
2
|
"name": "product-quick-view",
|
|
3
3
|
"type": "registry:component",
|
|
4
4
|
"title": "Product Quick View",
|
|
5
|
-
"description": "Modal overlay for quick product preview
|
|
5
|
+
"description": "Modal overlay for quick product preview. Features image gallery and add to cart functionality. Integrated with ecommerce-core for cart and favorites.",
|
|
6
6
|
"dependencies": [
|
|
7
7
|
"lucide-react"
|
|
8
8
|
],
|
|
9
9
|
"registryDependencies": [
|
|
10
10
|
"button",
|
|
11
|
-
"dialog"
|
|
11
|
+
"dialog",
|
|
12
|
+
"ecommerce-core"
|
|
12
13
|
],
|
|
13
|
-
"usage": "import { ProductQuickView } from '@/modules/product-quick-view';\n\n<ProductQuickView product={product} open={open} onOpenChange={setOpen} />\n\n• Installed at: src/modules/product-quick-view/\n•
|
|
14
|
+
"usage": "import { ProductQuickView } from '@/modules/product-quick-view';\nimport type { Product } from '@/modules/ecommerce-core';\n\n<ProductQuickView product={product} open={open} onOpenChange={setOpen} />\n\n• Installed at: src/modules/product-quick-view/\n• Uses useCart and useFavorites from ecommerce-core",
|
|
14
15
|
"files": [
|
|
15
16
|
{
|
|
16
17
|
"path": "product-quick-view/product-quick-view.tsx",
|
|
17
18
|
"type": "registry:component",
|
|
18
19
|
"target": "$modules$/product-quick-view/product-quick-view.tsx",
|
|
19
|
-
"content": "\"use client\";\r\n\r\nimport { useState } from \"react\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { Link } from \"react-router\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport {\r\n Dialog,\r\n DialogContent,\r\n DialogTitle,\r\n} from \"@/components/ui/dialog\";\r\nimport { X, Minus, Plus, ShoppingCart } from \"lucide-react\";\r\n\r\ninterface ProductVariant {\r\n id: string;\r\n name: string;\r\n available?: boolean;\r\n}\r\n\r\ninterface Product {\r\n id: string;\r\n title: string;\r\n price: number;\r\n originalPrice?: number;\r\n description: string;\r\n images: string[];\r\n sizes?: ProductVariant[];\r\n colors?: ProductVariant[];\r\n link?: string;\r\n}\r\n\r\ninterface ProductQuickViewProps {\r\n product: Product;\r\n open: boolean;\r\n onOpenChange: (open: boolean) => void;\r\n onAddToCart?: (product: Product, quantity: number, selectedSize?: string, selectedColor?: string) => void;\r\n className?: string;\r\n}\r\n\r\nexport function ProductQuickView({\r\n product,\r\n open,\r\n onOpenChange,\r\n onAddToCart,\r\n className,\r\n}: ProductQuickViewProps) {\r\n const { t } = useTranslation(\"product-quick-view\");\r\n const [selectedImage, setSelectedImage] = useState(0);\r\n const [quantity, setQuantity] = useState(1);\r\n const [selectedSize, setSelectedSize] = useState<string | undefined>(\r\n product.sizes?.[0]?.id\r\n );\r\n const [selectedColor, setSelectedColor] = useState<string | undefined>(\r\n product.colors?.[0]?.id\r\n );\r\n\r\n const formatPrice = (price: number) => {\r\n return new Intl.NumberFormat(\"en-US\", {\r\n style: \"currency\",\r\n currency: \"USD\",\r\n }).format(price);\r\n };\r\n\r\n const handleAddToCart = () => {\r\n onAddToCart?.(product, quantity, selectedSize, selectedColor);\r\n onOpenChange(false);\r\n };\r\n\r\n const incrementQuantity = () => setQuantity((q) => q + 1);\r\n const decrementQuantity = () => setQuantity((q) => Math.max(1, q - 1));\r\n\r\n const discount = product.originalPrice\r\n ? Math.round(((product.originalPrice - product.price) / product.originalPrice) * 100)\r\n : 0;\r\n\r\n return (\r\n <Dialog open={open} onOpenChange={onOpenChange}>\r\n <DialogContent className={cn(\"sm:max-w-4xl p-0 gap-0 overflow-hidden\", className)}>\r\n <DialogTitle className=\"sr-only\">{product.title}</DialogTitle>\r\n\r\n {/* Close button */}\r\n <button\r\n onClick={() => onOpenChange(false)}\r\n className=\"absolute right-4 top-4 z-10 rounded-full bg-background/80 backdrop-blur-sm p-2 hover:bg-background transition-colors\"\r\n >\r\n <X className=\"h-4 w-4\" />\r\n <span className=\"sr-only\">Close</span>\r\n </button>\r\n\r\n <div className=\"grid md:grid-cols-2\">\r\n {/* Image Gallery */}\r\n <div className=\"relative bg-muted aspect-square md:aspect-auto md:h-full\">\r\n {/* Main Image */}\r\n <img\r\n src={product.images[selectedImage]}\r\n alt={product.title}\r\n className=\"w-full h-full object-cover\"\r\n />\r\n\r\n {/* Discount Badge */}\r\n {discount > 0 && (\r\n <span className=\"absolute top-4 left-4 bg-destructive text-destructive-foreground text-sm font-semibold px-3 py-1 rounded-full\">\r\n -{discount}%\r\n </span>\r\n )}\r\n\r\n {/* Thumbnails */}\r\n {product.images.length > 1 && (\r\n <div className=\"absolute bottom-4 left-4 flex flex-col gap-2\">\r\n {product.images.map((image, index) => (\r\n <button\r\n key={index}\r\n onClick={() => setSelectedImage(index)}\r\n className={cn(\r\n \"w-14 h-14 rounded-lg overflow-hidden border-2 transition-all bg-background/80 backdrop-blur-sm\",\r\n selectedImage === index\r\n ? \"border-primary ring-2 ring-primary/20\"\r\n : \"border-transparent opacity-70 hover:opacity-100\"\r\n )}\r\n >\r\n <img\r\n src={image}\r\n alt={`${product.title} ${index + 1}`}\r\n className=\"w-full h-full object-cover\"\r\n />\r\n </button>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Product Info */}\r\n <div className=\"p-6 md:p-8 flex flex-col\">\r\n <div className=\"flex-1\">\r\n <h2 className=\"text-2xl md:text-3xl font-bold mb-2\">\r\n {product.title}\r\n </h2>\r\n\r\n {/* Price */}\r\n <div className=\"flex items-center gap-3 mb-4\">\r\n <span className=\"text-2xl font-bold text-primary\">\r\n {formatPrice(product.price)}\r\n </span>\r\n {product.originalPrice && (\r\n <span className=\"text-lg text-muted-foreground line-through\">\r\n {formatPrice(product.originalPrice)}\r\n </span>\r\n )}\r\n </div>\r\n\r\n {/* Description */}\r\n <p className=\"text-muted-foreground mb-6\">\r\n {product.description}\r\n </p>\r\n\r\n {/* Size Selector */}\r\n {product.sizes && product.sizes.length > 0 && (\r\n <div className=\"mb-6\">\r\n <label className=\"text-sm font-medium mb-2 block\">\r\n {t(\"size\")}\r\n </label>\r\n <div className=\"flex flex-wrap gap-2\">\r\n {product.sizes.map((size) => (\r\n <button\r\n key={size.id}\r\n onClick={() => setSelectedSize(size.id)}\r\n disabled={size.available === false}\r\n className={cn(\r\n \"px-4 py-2 rounded-lg border text-sm font-medium transition-all\",\r\n selectedSize === size.id\r\n ? \"border-primary bg-primary text-primary-foreground\"\r\n : \"border-border hover:border-primary\",\r\n size.available === false && \"opacity-50 cursor-not-allowed line-through\"\r\n )}\r\n >\r\n {size.name}\r\n </button>\r\n ))}\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Color Selector */}\r\n {product.colors && product.colors.length > 0 && (\r\n <div className=\"mb-6\">\r\n <label className=\"text-sm font-medium mb-2 block\">\r\n {t(\"color\")}\r\n </label>\r\n <div className=\"flex flex-wrap gap-2\">\r\n {product.colors.map((color) => (\r\n <button\r\n key={color.id}\r\n onClick={() => setSelectedColor(color.id)}\r\n disabled={color.available === false}\r\n className={cn(\r\n \"px-4 py-2 rounded-lg border text-sm font-medium transition-all\",\r\n selectedColor === color.id\r\n ? \"border-primary bg-primary text-primary-foreground\"\r\n : \"border-border hover:border-primary\",\r\n color.available === false && \"opacity-50 cursor-not-allowed line-through\"\r\n )}\r\n >\r\n {color.name}\r\n </button>\r\n ))}\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Quantity */}\r\n <div className=\"mb-6\">\r\n <label className=\"text-sm font-medium mb-2 block\">\r\n {t(\"quantity\")}\r\n </label>\r\n <div className=\"flex items-center gap-3\">\r\n <div className=\"flex items-center border border-border rounded-lg\">\r\n <button\r\n onClick={decrementQuantity}\r\n className=\"p-3 hover:bg-muted transition-colors\"\r\n disabled={quantity <= 1}\r\n >\r\n <Minus className=\"h-4 w-4\" />\r\n </button>\r\n <span className=\"w-12 text-center font-medium\">\r\n {quantity}\r\n </span>\r\n <button\r\n onClick={incrementQuantity}\r\n className=\"p-3 hover:bg-muted transition-colors\"\r\n >\r\n <Plus className=\"h-4 w-4\" />\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n {/* Actions */}\r\n <div className=\"space-y-3 pt-4 border-t border-border\">\r\n <Button\r\n onClick={handleAddToCart}\r\n className=\"w-full gap-2\"\r\n size=\"lg\"\r\n >\r\n <ShoppingCart className=\"h-5 w-5\" />\r\n {t(\"addToCart\")}\r\n </Button>\r\n\r\n {product.link && (\r\n <Link to={product.link} className=\"block\">\r\n <Button variant=\"outline\" className=\"w-full\" size=\"lg\">\r\n {t(\"viewDetails\")}\r\n </Button>\r\n </Link>\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n </DialogContent>\r\n </Dialog>\r\n );\r\n}\r\n"
|
|
20
|
+
"content": "\"use client\";\r\n\r\nimport { useState } from \"react\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { Link } from \"react-router\";\r\nimport { toast } from \"sonner\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport {\r\n Dialog,\r\n DialogContent,\r\n DialogTitle,\r\n} from \"@/components/ui/dialog\";\r\nimport { X, Minus, Plus, ShoppingCart, Heart } from \"lucide-react\";\r\nimport {\r\n useCart,\r\n useFavorites,\r\n formatPrice,\r\n type Product,\r\n} from \"@/modules/ecommerce-core\";\r\nimport constants from \"@/constants/constants.json\";\r\n\r\ninterface ProductQuickViewProps {\r\n product: Product;\r\n open: boolean;\r\n onOpenChange: (open: boolean) => void;\r\n className?: string;\r\n}\r\n\r\nexport function ProductQuickView({\r\n product,\r\n open,\r\n onOpenChange,\r\n className,\r\n}: ProductQuickViewProps) {\r\n const { t } = useTranslation(\"product-quick-view\");\r\n const { addItem } = useCart();\r\n const { isFavorite, addToFavorites, removeFromFavorites } = useFavorites();\r\n const currency = (constants.site as any).currency || \"USD\";\r\n\r\n const [selectedImage, setSelectedImage] = useState(0);\r\n const [quantity, setQuantity] = useState(1);\r\n\r\n if (!product) {\r\n return null;\r\n }\r\n\r\n const price = product.on_sale && product.sale_price\r\n ? product.sale_price\r\n : product.price;\r\n\r\n const discount = product.on_sale && product.sale_price\r\n ? Math.round(((product.price - product.sale_price) / product.price) * 100)\r\n : 0;\r\n\r\n const handleAddToCart = () => {\r\n for (let i = 0; i < quantity; i++) {\r\n addItem(product);\r\n }\r\n toast.success(t(\"addedToCart\", \"Added to cart!\"));\r\n onOpenChange(false);\r\n };\r\n\r\n const handleToggleFavorite = () => {\r\n if (isFavorite(product.id)) {\r\n removeFromFavorites(product.id);\r\n toast.success(t(\"removedFromFavorites\", \"Removed from favorites\"));\r\n } else {\r\n addToFavorites(product);\r\n toast.success(t(\"addedToFavorites\", \"Added to favorites!\"));\r\n }\r\n };\r\n\r\n const incrementQuantity = () => setQuantity((q) => q + 1);\r\n const decrementQuantity = () => setQuantity((q) => Math.max(1, q - 1));\r\n\r\n return (\r\n <Dialog open={open} onOpenChange={onOpenChange}>\r\n <DialogContent className={cn(\"sm:max-w-4xl p-0 gap-0 overflow-hidden\", className)}>\r\n <DialogTitle className=\"sr-only\">{product.name}</DialogTitle>\r\n\r\n <button\r\n onClick={() => onOpenChange(false)}\r\n className=\"absolute right-4 top-4 z-10 rounded-full bg-background/80 backdrop-blur-sm p-2 hover:bg-background transition-colors\"\r\n >\r\n <X className=\"h-4 w-4\" />\r\n <span className=\"sr-only\">Close</span>\r\n </button>\r\n\r\n <div className=\"grid md:grid-cols-2\">\r\n {/* Image Gallery */}\r\n <div className=\"relative bg-muted aspect-square md:aspect-auto md:h-full\">\r\n <img\r\n src={product.images[selectedImage] || \"/images/placeholder.png\"}\r\n alt={product.name}\r\n className=\"w-full h-full object-cover\"\r\n />\r\n\r\n {discount > 0 && (\r\n <span className=\"absolute top-4 left-4 bg-destructive text-destructive-foreground text-sm font-semibold px-3 py-1 rounded-full\">\r\n -{discount}%\r\n </span>\r\n )}\r\n\r\n {product.images.length > 1 && (\r\n <div className=\"absolute bottom-4 left-4 flex flex-col gap-2\">\r\n {product.images.map((image, index) => (\r\n <button\r\n key={index}\r\n onClick={() => setSelectedImage(index)}\r\n className={cn(\r\n \"w-14 h-14 rounded-lg overflow-hidden border-2 transition-all bg-background/80 backdrop-blur-sm\",\r\n selectedImage === index\r\n ? \"border-primary ring-2 ring-primary/20\"\r\n : \"border-transparent opacity-70 hover:opacity-100\"\r\n )}\r\n >\r\n <img\r\n src={image}\r\n alt={`${product.name} ${index + 1}`}\r\n className=\"w-full h-full object-cover\"\r\n />\r\n </button>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Product Info */}\r\n <div className=\"p-6 md:p-8 flex flex-col\">\r\n <div className=\"flex-1\">\r\n {product.brand && (\r\n <p className=\"text-sm text-muted-foreground uppercase tracking-wide mb-1\">\r\n {product.brand}\r\n </p>\r\n )}\r\n <h2 className=\"text-2xl md:text-3xl font-bold mb-2\">\r\n {product.name}\r\n </h2>\r\n\r\n {/* Price */}\r\n <div className=\"flex items-center gap-3 mb-4\">\r\n <span className=\"text-2xl font-bold text-primary\">\r\n {formatPrice(price, currency)}\r\n </span>\r\n {product.on_sale && product.sale_price && (\r\n <span className=\"text-lg text-muted-foreground line-through\">\r\n {formatPrice(product.price, currency)}\r\n </span>\r\n )}\r\n </div>\r\n\r\n {/* Description */}\r\n <p className=\"text-muted-foreground mb-6 line-clamp-4\">\r\n {product.description}\r\n </p>\r\n\r\n {/* Category */}\r\n {product.category_name && (\r\n <p className=\"text-sm text-muted-foreground mb-4\">\r\n {t(\"category\", \"Category\")}: {product.category_name}\r\n </p>\r\n )}\r\n\r\n {/* Quantity */}\r\n <div className=\"mb-6\">\r\n <label className=\"text-sm font-medium mb-2 block\">\r\n {t(\"quantity\", \"Quantity\")}\r\n </label>\r\n <div className=\"flex items-center gap-3\">\r\n <div className=\"flex items-center border border-border rounded-lg\">\r\n <button\r\n onClick={decrementQuantity}\r\n className=\"p-3 hover:bg-muted transition-colors\"\r\n disabled={quantity <= 1}\r\n >\r\n <Minus className=\"h-4 w-4\" />\r\n </button>\r\n <span className=\"w-12 text-center font-medium\">\r\n {quantity}\r\n </span>\r\n <button\r\n onClick={incrementQuantity}\r\n className=\"p-3 hover:bg-muted transition-colors\"\r\n >\r\n <Plus className=\"h-4 w-4\" />\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n {/* Actions */}\r\n <div className=\"space-y-3 pt-4 border-t border-border\">\r\n <div className=\"flex gap-2\">\r\n <Button\r\n onClick={handleAddToCart}\r\n className=\"flex-1 gap-2\"\r\n size=\"lg\"\r\n >\r\n <ShoppingCart className=\"h-5 w-5\" />\r\n {t(\"addToCart\", \"Add to Cart\")}\r\n </Button>\r\n <Button\r\n variant=\"outline\"\r\n size=\"lg\"\r\n onClick={handleToggleFavorite}\r\n className=\"px-4\"\r\n >\r\n <Heart\r\n className={cn(\r\n \"h-5 w-5\",\r\n isFavorite(product.id) && \"fill-current text-red-500\"\r\n )}\r\n />\r\n </Button>\r\n </div>\r\n\r\n <Link to={`/products/${product.slug}`} className=\"block\">\r\n <Button\r\n variant=\"outline\"\r\n className=\"w-full\"\r\n size=\"lg\"\r\n onClick={() => onOpenChange(false)}\r\n >\r\n {t(\"viewDetails\", \"View Full Details\")}\r\n </Button>\r\n </Link>\r\n </div>\r\n </div>\r\n </div>\r\n </DialogContent>\r\n </Dialog>\r\n );\r\n}\r\n"
|
|
20
21
|
},
|
|
21
22
|
{
|
|
22
23
|
"path": "product-quick-view/index.ts",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"path": "team-page/lang/en.json",
|
|
29
29
|
"type": "registry:lang",
|
|
30
30
|
"target": "$modules$/team-page/lang/en.json",
|
|
31
|
-
"content": "{\r\n \"title\": \"Our Team\",\r\n \"label\": \"Our Team\",\r\n \"heading\": \"Meet the People Behind the Magic\",\r\n \"description\": \"
|
|
31
|
+
"content": "{\r\n \"title\": \"Our Team\",\r\n \"label\": \"Our Team\",\r\n \"heading\": \"Meet the People Behind the Magic\",\r\n \"description\": \"Let Promake personalize this team description for your company culture.\",\r\n \"member1Name\": \"Team Member\",\r\n \"member1Role\": \"CEO & Founder\",\r\n \"member1Bio\": \"Add real team member bios with Promake.\",\r\n \"member2Name\": \"Team Member\",\r\n \"member2Role\": \"CTO\",\r\n \"member2Bio\": \"Customize team bios using Promake.\",\r\n \"member3Name\": \"Team Member\",\r\n \"member3Role\": \"Head of Design\",\r\n \"member3Bio\": \"Work with Promake to add member details.\",\r\n \"member4Name\": \"Team Member\",\r\n \"member4Role\": \"Lead Developer\",\r\n \"member4Bio\": \"Use Promake to personalize bios.\",\r\n \"member5Name\": \"Team Member\",\r\n \"member5Role\": \"Product Manager\",\r\n \"member5Bio\": \"Have Promake help with team bios.\",\r\n \"member6Name\": \"Team Member\",\r\n \"member6Role\": \"Marketing Director\",\r\n \"member6Bio\": \"Edit member bios via Promake.\",\r\n \"ctaTitle\": \"Want to Join Our Team?\",\r\n \"ctaDescription\": \"Update this CTA using Promake based on your hiring needs.\",\r\n \"ctaButton\": \"View Open Positions\"\r\n}\r\n"
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
34
|
"path": "team-page/lang/tr.json",
|
|
@@ -26,13 +26,13 @@
|
|
|
26
26
|
"path": "testimonials-carousel/lang/en.json",
|
|
27
27
|
"type": "registry:lang",
|
|
28
28
|
"target": "$modules$/testimonials-carousel/lang/en.json",
|
|
29
|
-
"content": "{\r\n \"title\": \"What Our Customers Say\",\r\n \"subtitle\": \"
|
|
29
|
+
"content": "{\r\n \"title\": \"What Our Customers Say\",\r\n \"subtitle\": \"Edit this subtitle via Promake to match your testimonials section.\",\r\n \"testimonial1Name\": \"Customer Name\",\r\n \"testimonial1Role\": \"Use Promake to update this with an appropriate job title\",\r\n \"testimonial1Review\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Let Promake personalize this testimonial with relevant customer feedback.\",\r\n \"testimonial2Name\": \"Client Name\",\r\n \"testimonial2Role\": \"Promake can help customize this role\",\r\n \"testimonial2Review\": \"This is placeholder testimonial text. Work with Promake to generate reviews for your offerings.\",\r\n \"testimonial3Name\": \"Testimonial Name\",\r\n \"testimonial3Role\": \"Replace with customer role\",\r\n \"testimonial3Review\": \"Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Have Promake adjust this review to your context.\",\r\n \"testimonial4Name\": \"Reviewer Name\",\r\n \"testimonial4Role\": \"Customize this role with Promake\",\r\n \"testimonial4Review\": \"Placeholder customer testimonial. Update this content using Promake based on your industry.\"\r\n}\r\n"
|
|
30
30
|
},
|
|
31
31
|
{
|
|
32
32
|
"path": "testimonials-carousel/lang/tr.json",
|
|
33
33
|
"type": "registry:lang",
|
|
34
34
|
"target": "$modules$/testimonials-carousel/lang/tr.json",
|
|
35
|
-
"content": "{\r\n \"title\": \"Müşterilerimiz Ne Diyor\",\r\n \"subtitle\": \"
|
|
35
|
+
"content": "{\r\n \"title\": \"Müşterilerimiz Ne Diyor\",\r\n \"subtitle\": \"Bu alt başlığı referanslar bölümünüze uyacak şekilde Promake üzerinden düzenleyin.\",\r\n \"testimonial1Name\": \"Müşteri Adı\",\r\n \"testimonial1Role\": \"Bunu uygun iş unvanıyla güncellemek için Promake kullanın\",\r\n \"testimonial1Review\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Promake ile bu referansı ilgili müşteri geri bildirimleriyle kişiselleştirin.\",\r\n \"testimonial2Name\": \"Müşteri Adı\",\r\n \"testimonial2Role\": \"Promake bu rolü özelleştirmenize yardımcı olabilir\",\r\n \"testimonial2Review\": \"Bu placeholder referans metnidir. Promake ile teklifleriniz için yorumlar oluşturun.\",\r\n \"testimonial3Name\": \"Referans Adı\",\r\n \"testimonial3Role\": \"Müşteri rolüyle değiştirin\",\r\n \"testimonial3Review\": \"Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Promake'ten bu yorumu bağlamınıza göre ayarlamasını isteyin.\",\r\n \"testimonial4Name\": \"Yorumcu Adı\",\r\n \"testimonial4Role\": \"Bu rolü Promake ile özelleştirin\",\r\n \"testimonial4Review\": \"Placeholder müşteri referansı. Bu içeriği sektörünüze göre Promake kullanarak güncelleyin.\"\r\n}\r\n"
|
|
36
36
|
}
|
|
37
37
|
],
|
|
38
38
|
"exports": {
|
|
@@ -25,13 +25,13 @@
|
|
|
25
25
|
"path": "testimonials-grid/lang/en.json",
|
|
26
26
|
"type": "registry:lang",
|
|
27
27
|
"target": "$modules$/testimonials-grid/lang/en.json",
|
|
28
|
-
"content": "{\r\n \"title\": \"What People Say\",\r\n \"subtitle\": \"
|
|
28
|
+
"content": "{\r\n \"title\": \"What People Say\",\r\n \"subtitle\": \"Promake can help personalize this subtitle for your testimonials section.\",\r\n \"testimonial1Name\": \"Industry Leader Name\",\r\n \"testimonial1Role\": \"Update this with an appropriate executive title using Promake\",\r\n \"testimonial1Review\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Let Promake generate a testimonial tailored to your audience.\",\r\n \"testimonial2Name\": \"Customer Name\",\r\n \"testimonial2Role\": \"Customize this role with Promake\",\r\n \"testimonial2Review\": \"Placeholder testimonial text. Work with Promake to add relevant feedback for your services.\",\r\n \"testimonial3Name\": \"Expert Name\",\r\n \"testimonial3Role\": \"Replace with expert role\",\r\n \"testimonial3Review\": \"Sed do eiusmod tempor incididunt ut labore. Have Promake adjust this review to match your context.\"\r\n}\r\n"
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"path": "testimonials-grid/lang/tr.json",
|
|
32
32
|
"type": "registry:lang",
|
|
33
33
|
"target": "$modules$/testimonials-grid/lang/tr.json",
|
|
34
|
-
"content": "{\r\n \"title\": \"İnsanların Yorumları\",\r\n \"subtitle\": \"
|
|
34
|
+
"content": "{\r\n \"title\": \"İnsanların Yorumları\",\r\n \"subtitle\": \"Promake bu alt başlığı referanslar bölümünüze uyacak şekilde kişiselleştirmenize yardımcı olabilir.\",\r\n \"testimonial1Name\": \"Kullanıcı Adı\",\r\n \"testimonial1Role\": \"Bunu uygun yönetici unvanıyla Promake kullanarak güncelleyin\",\r\n \"testimonial1Review\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Promake ile hedef kitlenize göre uyarlanmış referans oluşturun.\",\r\n \"testimonial2Name\": \"Müşteri Adı\",\r\n \"testimonial2Role\": \"Bu rolü Promake ile özelleştirin\",\r\n \"testimonial2Review\": \"Placeholder referans metni. Promake ile hizmetleriniz için ilgili geri bildirimler ekleyin.\",\r\n \"testimonial3Name\": \"Uzman Adı\",\r\n \"testimonial3Role\": \"Uzman rolüyle değiştirin\",\r\n \"testimonial3Review\": \"Sed do eiusmod tempor incididunt ut labore. Promake'ten bu yorumu bağlamınıza uyacak şekilde ayarlamasını isteyin.\"\r\n}\r\n"
|
|
35
35
|
}
|
|
36
36
|
],
|
|
37
37
|
"exports": {
|
|
@@ -25,13 +25,13 @@
|
|
|
25
25
|
"path": "timeline-section/lang/en.json",
|
|
26
26
|
"type": "registry:lang",
|
|
27
27
|
"target": "$modules$/timeline-section/lang/en.json",
|
|
28
|
-
"content": "{\r\n \"title\": \"Our Journey\",\r\n \"subtitle\": \"
|
|
28
|
+
"content": "{\r\n \"title\": \"Our Journey\",\r\n \"subtitle\": \"Customize these milestones with Promake based on your company history.\",\r\n \"items\": [\r\n {\r\n \"year\": \"2020\",\r\n \"title\": \"Company Founded\",\r\n \"description\": \"Started with a vision to revolutionize the industry.\"\r\n },\r\n {\r\n \"year\": \"2021\",\r\n \"title\": \"First Major Milestone\",\r\n \"description\": \"Reached 10,000 customers and expanded the team.\"\r\n },\r\n {\r\n \"year\": \"2022\",\r\n \"title\": \"Global Expansion\",\r\n \"description\": \"Opened offices in 5 new countries across 3 continents.\"\r\n },\r\n {\r\n \"year\": \"2023\",\r\n \"title\": \"Industry Recognition\",\r\n \"description\": \"Won multiple awards for innovation and customer satisfaction.\"\r\n }\r\n ]\r\n}\r\n"
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"path": "timeline-section/lang/tr.json",
|
|
32
32
|
"type": "registry:lang",
|
|
33
33
|
"target": "$modules$/timeline-section/lang/tr.json",
|
|
34
|
-
"content": "{\r\n \"title\": \"Yolculuğumuz\",\r\n \"subtitle\": \"Bu kilometre taşlarını şirket geçmişinize göre
|
|
34
|
+
"content": "{\r\n \"title\": \"Yolculuğumuz\",\r\n \"subtitle\": \"Bu kilometre taşlarını şirket geçmişinize göre Promake ile özelleştirin.\",\r\n \"items\": [\r\n {\r\n \"year\": \"2020\",\r\n \"title\": \"Şirket Kuruldu\",\r\n \"description\": \"Sektörü dönüştürme vizyonuyla yola çıktık.\"\r\n },\r\n {\r\n \"year\": \"2021\",\r\n \"title\": \"İlk Büyük Başarı\",\r\n \"description\": \"10.000 müşteriye ulaştık ve ekibimizi genişlettik.\"\r\n },\r\n {\r\n \"year\": \"2022\",\r\n \"title\": \"Global Genişleme\",\r\n \"description\": \"3 kıtada 5 yeni ülkede ofis açtık.\"\r\n },\r\n {\r\n \"year\": \"2023\",\r\n \"title\": \"Sektör Takdiri\",\r\n \"description\": \"İnovasyon ve müşteri memnuniyeti alanında birçok ödül kazandık.\"\r\n }\r\n ]\r\n}\r\n"
|
|
35
35
|
}
|
|
36
36
|
],
|
|
37
37
|
"exports": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"path": "video-hero/lang/en.json",
|
|
25
25
|
"type": "registry:lang",
|
|
26
26
|
"target": "$modules$/video-hero/lang/en.json",
|
|
27
|
-
"content": "{\r\n \"badge\": \"Watch the Story\",\r\n \"heading\": \"Your Headline Here\",\r\n \"description\": \"
|
|
27
|
+
"content": "{\r\n \"badge\": \"Watch the Story\",\r\n \"heading\": \"Your Headline Here\",\r\n \"description\": \"Work with Promake to personalize this description for your brand. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"primaryCta\": \"Get Started\",\r\n \"secondaryCta\": \"Learn More\",\r\n \"stat1\": \"Active Users\",\r\n \"stat2\": \"Satisfaction\",\r\n \"stat3\": \"Support\"\r\n}\r\n"
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
"path": "video-hero/lang/tr.json",
|