@promakeai/cli 0.1.2 → 0.2.0

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.
Files changed (69) hide show
  1. package/dist/index.js +412 -201
  2. package/dist/registry/about-page.json +2 -2
  3. package/dist/registry/about-section.json +2 -2
  4. package/dist/registry/announcement-bar.json +2 -2
  5. package/dist/registry/blog-list-page.json +1 -1
  6. package/dist/registry/blog-section.json +6 -4
  7. package/dist/registry/cards-carousel-section.json +1 -1
  8. package/dist/registry/cart-drawer.json +5 -4
  9. package/dist/registry/case-study-page.json +2 -2
  10. package/dist/registry/coming-soon-page-minimal.json +1 -1
  11. package/dist/registry/coming-soon-page.json +1 -1
  12. package/dist/registry/contact-info-grid.json +2 -2
  13. package/dist/registry/contact-page-centered.json +2 -2
  14. package/dist/registry/contact-page-split.json +2 -2
  15. package/dist/registry/contact-page.json +2 -2
  16. package/dist/registry/cta-section.json +2 -2
  17. package/dist/registry/docs/blog-section.md +3 -1
  18. package/dist/registry/docs/cart-drawer.md +9 -9
  19. package/dist/registry/docs/favorites-blog-block.md +10 -3
  20. package/dist/registry/docs/favorites-blog-page.md +38 -0
  21. package/dist/registry/docs/favorites-ecommerce-block.md +10 -3
  22. package/dist/registry/docs/favorites-ecommerce-page.md +38 -0
  23. package/dist/registry/docs/login-page.md +6 -16
  24. package/dist/registry/docs/payment-success-block.md +8 -1
  25. package/dist/registry/docs/post-detail-page.md +39 -0
  26. package/dist/registry/docs/product-card-detailed.md +7 -11
  27. package/dist/registry/docs/product-detail-page.md +39 -0
  28. package/dist/registry/docs/product-detail-section.md +7 -13
  29. package/dist/registry/docs/product-quick-view.md +4 -2
  30. package/dist/registry/ecommerce-core.json +2 -2
  31. package/dist/registry/faq-categorized.json +2 -2
  32. package/dist/registry/faq-simple.json +2 -2
  33. package/dist/registry/favorites-blog-block.json +1 -1
  34. package/dist/registry/favorites-blog-page.json +48 -0
  35. package/dist/registry/favorites-ecommerce-block.json +1 -1
  36. package/dist/registry/favorites-ecommerce-page.json +48 -0
  37. package/dist/registry/feature-section.json +2 -2
  38. package/dist/registry/footer.json +2 -2
  39. package/dist/registry/header-ecommerce.json +1 -1
  40. package/dist/registry/hero-carousel.json +3 -3
  41. package/dist/registry/hero-cta.json +2 -2
  42. package/dist/registry/hero-gradient.json +2 -2
  43. package/dist/registry/hero.json +2 -2
  44. package/dist/registry/index.json +9 -0
  45. package/dist/registry/landing-page-app.json +1 -1
  46. package/dist/registry/landing-page-saas.json +1 -1
  47. package/dist/registry/login-page-split.json +1 -1
  48. package/dist/registry/login-page.json +8 -6
  49. package/dist/registry/logo-cloud.json +1 -1
  50. package/dist/registry/payment-success-block.json +7 -3
  51. package/dist/registry/portfolio-page.json +2 -2
  52. package/dist/registry/post-card.json +1 -1
  53. package/dist/registry/post-detail-page.json +48 -0
  54. package/dist/registry/pricing-page.json +1 -1
  55. package/dist/registry/pricing-section.json +2 -2
  56. package/dist/registry/product-card-detailed.json +5 -4
  57. package/dist/registry/product-detail-page.json +48 -0
  58. package/dist/registry/product-detail-section.json +5 -4
  59. package/dist/registry/product-quick-view.json +5 -4
  60. package/dist/registry/products-page.json +1 -1
  61. package/dist/registry/reading-progress.json +1 -1
  62. package/dist/registry/team-page.json +1 -1
  63. package/dist/registry/testimonials-carousel.json +2 -2
  64. package/dist/registry/testimonials-grid.json +2 -2
  65. package/dist/registry/timeline-section.json +2 -2
  66. package/dist/registry/video-hero.json +1 -1
  67. package/package.json +53 -51
  68. package/template/eslint.config.js +5 -4
  69. package/template/src/components/ui/sidebar.tsx +2 -4
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "favorites-ecommerce-page",
3
+ "type": "registry:page",
4
+ "title": "Favorites Ecommerce Page",
5
+ "description": "Product favorites page that displays user's favorite products with grid layout. Uses useFavorites hook from ecommerce-core for state management. Includes empty state, clear all functionality, and responsive product grid.",
6
+ "registryDependencies": [
7
+ "ecommerce-core",
8
+ "product-card"
9
+ ],
10
+ "usage": "import FavoritesEcommercePage from '@/modules/favorites-ecommerce-page';\n\n<Route path=\"/favorites\" element={<FavoritesEcommercePage />} />\n\n• Uses useFavorites() from ecommerce-core\n• Shows empty state when no favorites\n• Grid layout with ProductCard components",
11
+ "route": {
12
+ "path": "/favorites",
13
+ "componentName": "FavoritesEcommercePage"
14
+ },
15
+ "files": [
16
+ {
17
+ "path": "favorites-ecommerce-page/index.ts",
18
+ "type": "registry:index",
19
+ "target": "$modules$/favorites-ecommerce-page/index.ts",
20
+ "content": "export * from './favorites-ecommerce-page';\r\nexport { FavoritesEcommercePage as default } from './favorites-ecommerce-page';\r\n"
21
+ },
22
+ {
23
+ "path": "favorites-ecommerce-page/favorites-ecommerce-page.tsx",
24
+ "type": "registry:page",
25
+ "target": "$modules$/favorites-ecommerce-page/favorites-ecommerce-page.tsx",
26
+ "content": "import { Link } from \"react-router\";\r\nimport { Heart, ShoppingBag } from \"lucide-react\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { ProductCard } from \"@/modules/product-card/product-card\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { useFavorites } from \"@/modules/ecommerce-core\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\n\r\nexport function FavoritesEcommercePage() {\r\n const { t } = useTranslation(\"favorites-ecommerce-page\");\r\n const { favorites, clearFavorites } = useFavorites();\r\n usePageTitle({ title: t(\"title\", \"My Favorites\") });\r\n\r\n // Empty State\r\n if (favorites.length === 0) {\r\n return (\r\n <Layout>\r\n <div className=\"min-h-screen bg-muted/30 py-12\">\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n <div className=\"text-center max-w-md mx-auto\">\r\n <Heart className=\"w-16 h-16 text-muted-foreground mx-auto mb-6\" />\r\n <h1 className=\"text-3xl font-bold text-foreground mb-4\">\r\n {t(\"noFavoritesYet\", \"No Favorites Yet\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-8\">\r\n {t(\r\n \"noFavoritesDescription\",\r\n \"Start browsing our products and add items to your favorites by clicking the heart icon.\"\r\n )}\r\n </p>\r\n <Button asChild size=\"lg\">\r\n <Link to=\"/products\">\r\n <ShoppingBag className=\"w-5 h-5 mr-2\" />\r\n {t(\"browseProducts\", \"Browse Products\")}\r\n </Link>\r\n </Button>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n // Favorites Grid\r\n return (\r\n <Layout>\r\n <div className=\"min-h-screen bg-muted/30 py-12\">\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n {/* Header */}\r\n <div className=\"flex items-center justify-between mb-8\">\r\n <div>\r\n <h1 className=\"text-3xl font-bold text-foreground mb-2\">\r\n {t(\"title\", \"My Favorites\")}\r\n </h1>\r\n <p className=\"text-muted-foreground\">\r\n {favorites.length}{\" \"}\r\n {t(\r\n \"itemsInFavorites\",\r\n `item${favorites.length !== 1 ? \"s\" : \"\"} in your favorites`\r\n )}\r\n </p>\r\n </div>\r\n <Button variant=\"outline\" onClick={clearFavorites}>\r\n {t(\"clearAll\", \"Clear All\")}\r\n </Button>\r\n </div>\r\n\r\n {/* Products Grid */}\r\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6\">\r\n {favorites.map((product) => (\r\n <ProductCard key={product.id} product={product} variant=\"grid\" />\r\n ))}\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default FavoritesEcommercePage;\r\n"
27
+ },
28
+ {
29
+ "path": "favorites-ecommerce-page/lang/en.json",
30
+ "type": "registry:lang",
31
+ "target": "$modules$/favorites-ecommerce-page/lang/en.json",
32
+ "content": "{\r\n \"title\": \"My Favorites\",\r\n \"noFavoritesYet\": \"No Favorites Yet\",\r\n \"noFavoritesDescription\": \"Start browsing our products and add items to your favorites by clicking the heart icon.\",\r\n \"browseProducts\": \"Browse Products\",\r\n \"itemsInFavorites\": \"items in your favorites\",\r\n \"clearAll\": \"Clear All\"\r\n}\r\n"
33
+ },
34
+ {
35
+ "path": "favorites-ecommerce-page/lang/tr.json",
36
+ "type": "registry:lang",
37
+ "target": "$modules$/favorites-ecommerce-page/lang/tr.json",
38
+ "content": "{\r\n \"title\": \"Favorilerim\",\r\n \"noFavoritesYet\": \"Henüz Favori Yok\",\r\n \"noFavoritesDescription\": \"Ürünlerimize göz atın ve kalp ikonuna tıklayarak favorilerinize ekleyin.\",\r\n \"browseProducts\": \"Ürünlere Göz At\",\r\n \"itemsInFavorites\": \"ürün favorilerinizde\",\r\n \"clearAll\": \"Tümünü Temizle\"\r\n}\r\n"
39
+ }
40
+ ],
41
+ "exports": {
42
+ "types": [],
43
+ "variables": [
44
+ "FavoritesEcommercePage",
45
+ "default"
46
+ ]
47
+ }
48
+ }
@@ -22,13 +22,13 @@
22
22
  "path": "feature-section/lang/en.json",
23
23
  "type": "registry:lang",
24
24
  "target": "$modules$/feature-section/lang/en.json",
25
- "content": "{\r\n \"heading\": \"Your Site Title Here\",\r\n \"description\": \"This is where your main site description will appear. Ask Promake to customize this content based on your specific goals and audience.\",\r\n \"feature1\": \"Key feature or benefit #1\",\r\n \"feature2\": \"Key feature or benefit #2\",\r\n \"feature3\": \"Key feature or benefit #3\",\r\n \"primaryButton\": \"Learn More\",\r\n \"secondaryButton\": \"Get Started\",\r\n \"imageAlt\": \"Site Preview\"\r\n}\r\n"
25
+ "content": "{\r\n \"heading\": \"Your Site Title Here\",\r\n \"description\": \"This is where your main site description will appear. Use Promake to personalize this content for your audience.\",\r\n \"feature1\": \"Key feature or benefit #1\",\r\n \"feature2\": \"Key feature or benefit #2\",\r\n \"feature3\": \"Key feature or benefit #3\",\r\n \"primaryButton\": \"Learn More\",\r\n \"secondaryButton\": \"Get Started\",\r\n \"imageAlt\": \"Site Preview\"\r\n}\r\n"
26
26
  },
27
27
  {
28
28
  "path": "feature-section/lang/tr.json",
29
29
  "type": "registry:lang",
30
30
  "target": "$modules$/feature-section/lang/tr.json",
31
- "content": "{\r\n \"heading\": \"Site Başlığınız\",\r\n \"description\": \"Ana site açıklamanız burada görünecek. AI, içeriklerinizi hedef kitlenize ve içeriğinize göre özelleştirecektir.\",\r\n \"feature1\": \"Anahtar özellik veya avantaj #1\",\r\n \"feature2\": \"Anahtar özellik veya avantaj #2\",\r\n \"feature3\": \"Anahtar özellik veya avantaj #3\",\r\n \"primaryButton\": \"Daha Fazla Öğren\",\r\n \"secondaryButton\": \"Başlayın\",\r\n \"imageAlt\": \"Site Önizlemesi\"\r\n}\r\n"
31
+ "content": "{\r\n \"heading\": \"Site Başlığınız\",\r\n \"description\": \"Ana site açıklamanız burada görünecek. Promake kullanarak içeriğinizi hedef kitlenize göre kişiselleştirin.\",\r\n \"feature1\": \"Anahtar özellik veya avantaj #1\",\r\n \"feature2\": \"Anahtar özellik veya avantaj #2\",\r\n \"feature3\": \"Anahtar özellik veya avantaj #3\",\r\n \"primaryButton\": \"Daha Fazla Öğren\",\r\n \"secondaryButton\": \"Başlayın\",\r\n \"imageAlt\": \"Site Önizlemesi\"\r\n}\r\n"
32
32
  }
33
33
  ],
34
34
  "exports": {
@@ -22,13 +22,13 @@
22
22
  "path": "footer/lang/en.json",
23
23
  "type": "registry:lang",
24
24
  "target": "$modules$/footer/lang/en.json",
25
- "content": "{\r\n \"description\": \"Ask Promake to customize this footer description based on your site's purpose and brand.\",\r\n \"quickLinks\": \"Quick Links\",\r\n \"about\": \"About\",\r\n \"contact\": \"Contact\",\r\n \"legal\": \"Legal\",\r\n \"privacy\": \"Privacy Policy\",\r\n \"terms\": \"Terms of Service\",\r\n \"cookies\": \"Cookie Policy\",\r\n \"contactTitle\": \"Contact\",\r\n \"allRightsReserved\": \"All rights reserved.\"\r\n}\r\n"
25
+ "content": "{\r\n \"description\": \"Let Promake personalize this footer description for your brand and purpose.\",\r\n \"quickLinks\": \"Quick Links\",\r\n \"about\": \"About\",\r\n \"contact\": \"Contact\",\r\n \"legal\": \"Legal\",\r\n \"privacy\": \"Privacy Policy\",\r\n \"terms\": \"Terms of Service\",\r\n \"cookies\": \"Cookie Policy\",\r\n \"contactTitle\": \"Contact\",\r\n \"allRightsReserved\": \"All rights reserved.\"\r\n}\r\n"
26
26
  },
27
27
  {
28
28
  "path": "footer/lang/tr.json",
29
29
  "type": "registry:lang",
30
30
  "target": "$modules$/footer/lang/tr.json",
31
- "content": "{\r\n \"description\": \"AI bu footer açıklamasını sitenizin amacı ve markasına göre özelleştirecektir.\",\r\n \"quickLinks\": \"Hızlı Bağlantılar\",\r\n \"about\": \"Hakkımızda\",\r\n \"contact\": \"İletişim\",\r\n \"legal\": \"Yasal\",\r\n \"privacy\": \"Gizlilik Politikası\",\r\n \"terms\": \"Kullanım Şartları\",\r\n \"cookies\": \"Çerez Politikası\",\r\n \"contactTitle\": \"İletişim\",\r\n \"allRightsReserved\": \"Tüm hakları saklıdır.\"\r\n}\r\n"
31
+ "content": "{\r\n \"description\": \"Promake ile bu footer açıklamasını markanız ve amacınız için kişiselleştirin.\",\r\n \"quickLinks\": \"Hızlı Bağlantılar\",\r\n \"about\": \"Hakkımızda\",\r\n \"contact\": \"İletişim\",\r\n \"legal\": \"Yasal\",\r\n \"privacy\": \"Gizlilik Politikası\",\r\n \"terms\": \"Kullanım Şartları\",\r\n \"cookies\": \"Çerez Politikası\",\r\n \"contactTitle\": \"İletişim\",\r\n \"allRightsReserved\": \"Tüm hakları saklıdır.\"\r\n}\r\n"
32
32
  }
33
33
  ],
34
34
  "exports": {
@@ -20,7 +20,7 @@
20
20
  "path": "header-ecommerce/header-ecommerce.tsx",
21
21
  "type": "registry:component",
22
22
  "target": "$modules$/header-ecommerce/header-ecommerce.tsx",
23
- "content": "import { useState } from \"react\";\nimport { Link, useNavigate } from \"react-router\";\nimport { ShoppingCart, Menu, Search, Heart, Package, User, LogOut } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Badge } from \"@/components/ui/badge\";\nimport {\n Sheet,\n SheetHeader,\n SheetTitle,\n SheetContent,\n SheetTrigger,\n} from \"@/components/ui/sheet\";\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from \"@/components/ui/dialog\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { Logo } from \"@/components/Logo\";\nimport { useAuth } from \"@/modules/auth-core\";\nimport { CartDrawer } from \"@/modules/cart-drawer\";\nimport { toast } from \"sonner\";\nimport { useTranslation } from \"react-i18next\";\nimport constants from \"@/constants/constants.json\";\nimport type { Product } from \"@/modules/ecommerce-core/types\";\nimport {\n useCart,\n useFavorites,\n useSearch,\n formatPrice,\n} from \"@/modules/ecommerce-core\";\n\nexport function HeaderEcommerce() {\n const [mobileMenuOpen, setMobileMenuOpen] = useState(false);\n const [mobileSearchOpen, setMobileSearchOpen] = useState(false);\n const [desktopSearchOpen, setDesktopSearchOpen] = useState(false);\n const [showResults, setShowResults] = useState(false);\n const { itemCount, state, removeItem, isDrawerOpen, setDrawerOpen } = useCart();\n const { favoriteCount } = useFavorites();\n const { isAuthenticated, user, logout } = useAuth();\n const navigate = useNavigate();\n const { t } = useTranslation(\"header-ecommerce\");\n\n const handleLogout = () => {\n logout();\n toast.success(t(\"logoutToastTitle\", \"Goodbye!\"), {\n description: t(\"logoutToastDesc\", \"You have been logged out successfully.\"),\n });\n };\n\n const {\n searchTerm,\n setSearchTerm,\n results: searchResults,\n clearSearch,\n } = useSearch();\n\n const handleSearchSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (searchTerm.trim()) {\n navigate(`/products?search=${encodeURIComponent(searchTerm)}`);\n setShowResults(false);\n setDesktopSearchOpen(false);\n clearSearch();\n }\n };\n\n const handleSearchFocus = () => {\n setShowResults(true);\n };\n\n const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n setSearchTerm(e.target.value);\n setShowResults(true);\n };\n\n const navigation = [\n { name: t(\"home\"), href: \"/\" },\n { name: t(\"products\"), href: \"/products\" },\n { name: t(\"about\"), href: \"/about\" },\n { name: t(\"contact\"), href: \"/contact\" },\n ];\n\n return (\n <header className=\"sticky top-0 z-50 w-full border-b border-border/20 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-3 sm:px-4 lg:px-8\">\n <div className=\"flex h-14 sm:h-16 md:h-20 items-center justify-between gap-2\">\n {/* Logo */}\n <div className=\"flex-shrink-0 min-w-0\">\n <Logo size=\"sm\" className=\"text-base sm:text-xl lg:text-2xl\" />\n </div>\n\n {/* Desktop Navigation - Centered */}\n <nav className=\"hidden lg:flex items-center space-x-12 absolute left-1/2 transform -translate-x-1/2\">\n {navigation.map((item) => (\n <Link\n key={item.name}\n to={item.href}\n className=\"text-base font-medium transition-colors hover:text-primary relative group py-2\"\n >\n {item.name}\n <span className=\"absolute -bottom-1 left-0 w-0 h-0.5 bg-primary transition-all duration-300 group-hover:w-full\"></span>\n </Link>\n ))}\n </nav>\n\n {/* Search & Actions - Right Aligned */}\n <div className=\"flex items-center space-x-1 sm:space-x-2 lg:space-x-4 flex-shrink-0\">\n {/* Desktop Search - Modal */}\n <Dialog\n open={desktopSearchOpen}\n onOpenChange={setDesktopSearchOpen}\n >\n <DialogTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"hidden lg:flex h-10 w-10\"\n >\n <Search className=\"h-5 w-5\" />\n </Button>\n </DialogTrigger>\n <DialogContent className=\"sm:max-w-2xl\">\n <DialogHeader>\n <DialogTitle>\n {t(\"searchProducts\", \"Search Products\")}\n </DialogTitle>\n </DialogHeader>\n <div className=\"space-y-4\">\n <form onSubmit={handleSearchSubmit}>\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-5 w-5\" />\n <Input\n type=\"search\"\n placeholder={t(\n \"searchPlaceholder\",\n \"Search for products...\"\n )}\n value={searchTerm}\n onChange={handleSearchChange}\n className=\"pl-11 h-12 text-base\"\n autoFocus\n />\n </div>\n </form>\n\n {/* Desktop Search Results */}\n {searchTerm.trim() && (\n <div className=\"max-h-[400px] overflow-y-auto rounded-lg border bg-card\">\n {searchResults.length > 0 ? (\n <div className=\"divide-y\">\n <div className=\"px-4 py-3 bg-muted/50\">\n <p className=\"text-sm font-medium text-muted-foreground\">\n {searchResults.length}{\" \"}\n {searchResults.length === 1\n ? \"result\"\n : \"results\"}{\" \"}\n found\n </p>\n </div>\n {searchResults.slice(0, 8).map((product: Product) => (\n <Link\n key={product.id}\n to={`/products/${product.slug}`}\n onClick={() => {\n setDesktopSearchOpen(false);\n clearSearch();\n }}\n className=\"flex items-center gap-4 p-4 hover:bg-muted/50 transition-colors\"\n >\n <img\n src={\n product.images[0] || \"/images/placeholder.png\"\n }\n alt={product.name}\n className=\"w-16 h-16 object-cover rounded flex-shrink-0\"\n />\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"font-medium text-base line-clamp-1\">\n {product.name}\n </h4>\n <p className=\"text-sm text-muted-foreground capitalize\">\n {product.category}\n </p>\n <p className=\"text-base font-semibold text-primary mt-1\">\n {formatPrice(\n product.price,\n constants.site.currency\n )}\n </p>\n </div>\n </Link>\n ))}\n {searchResults.length > 8 && (\n <div className=\"px-4 py-3 bg-muted/30 text-center\">\n <button\n onClick={() => {\n navigate(\n `/products?search=${encodeURIComponent(\n searchTerm\n )}`\n );\n setDesktopSearchOpen(false);\n clearSearch();\n }}\n className=\"text-sm font-medium text-primary hover:underline\"\n >\n {t(\n \"viewAllResults\",\n `View all ${searchResults.length} results`\n )}\n </button>\n </div>\n )}\n </div>\n ) : (\n <div className=\"p-8 text-center\">\n <Search className=\"h-12 w-12 text-muted-foreground mx-auto mb-3 opacity-50\" />\n <p className=\"text-base text-muted-foreground\">\n {t(\"noResults\", \"No products found\")}\n </p>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {t(\n \"tryDifferentKeywords\",\n \"Try different keywords\"\n )}\n </p>\n </div>\n )}\n </div>\n )}\n </div>\n </DialogContent>\n </Dialog>\n\n {/* Search - Mobile (Hidden - moved to hamburger menu) */}\n <Dialog open={mobileSearchOpen} onOpenChange={setMobileSearchOpen}>\n <DialogTrigger asChild>\n <Button variant=\"ghost\" size=\"icon\" className=\"hidden\">\n <Search className=\"h-4 w-4 sm:h-5 sm:w-5\" />\n </Button>\n </DialogTrigger>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>{t(\"searchProducts\")}</DialogTitle>\n </DialogHeader>\n <form\n onSubmit={(e) => {\n e.preventDefault();\n if (searchTerm.trim()) {\n navigate(\n `/products?search=${encodeURIComponent(searchTerm)}`\n );\n setMobileSearchOpen(false);\n clearSearch();\n }\n }}\n className=\"space-y-4\"\n >\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4\" />\n <Input\n type=\"search\"\n placeholder={t(\"searchPlaceholder\")}\n value={searchTerm}\n onChange={(e) => setSearchTerm(e.target.value)}\n className=\"pl-10\"\n autoFocus\n />\n </div>\n <div className=\"flex gap-2\">\n <Button type=\"submit\" className=\"flex-1\">\n {t(\"searchButton\", \"Search\")}\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => {\n clearSearch();\n setMobileSearchOpen(false);\n }}\n >\n {t(\"cancel\", \"Cancel\")}\n </Button>\n </div>\n </form>\n\n {/* Mobile Search Results */}\n {searchTerm.trim() && (\n <div className=\"mt-4 max-h-64 overflow-y-auto\">\n {searchResults.length > 0 ? (\n <div className=\"space-y-2\">\n <p className=\"text-sm text-muted-foreground mb-2\">\n {searchResults.length} result\n {searchResults.length !== 1 ? \"s\" : \"\"} found\n </p>\n {searchResults.slice(0, 5).map((product: Product) => (\n <Link\n key={product.id}\n to={`/products/${product.slug}`}\n onClick={() => {\n setMobileSearchOpen(false);\n clearSearch();\n }}\n className=\"block p-2 rounded hover:bg-muted/50 transition-colors\"\n >\n <div className=\"flex items-center gap-3\">\n <img\n src={\n product.images[0] || \"/images/placeholder.png\"\n }\n alt={product.name}\n className=\"w-10 h-10 object-cover rounded\"\n />\n <div className=\"flex-1\">\n <h4 className=\"font-medium text-sm\">\n {product.name}\n </h4>\n <p className=\"text-xs text-muted-foreground\">\n {product.category}\n </p>\n <p className=\"text-sm font-medium\">\n {formatPrice(\n product.price,\n constants.site.currency\n )}\n </p>\n </div>\n </div>\n </Link>\n ))}\n </div>\n ) : (\n <p className=\"text-sm text-muted-foreground\">\n {t(\"noResults\")}\n </p>\n )}\n </div>\n )}\n </DialogContent>\n </Dialog>\n\n {/* Wishlist - Desktop Only */}\n <Link to=\"/favorites\" className=\"hidden lg:block\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"relative h-10 w-10\"\n >\n <Heart className=\"h-5 w-5\" />\n {favoriteCount > 0 && (\n <Badge\n variant=\"destructive\"\n className=\"absolute -top-1 -right-1 h-4 w-4 flex items-center justify-center p-0 text-[10px]\"\n >\n {favoriteCount}\n </Badge>\n )}\n </Button>\n </Link>\n\n {/* Cart - Desktop Only */}\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"relative h-10 w-10 hidden lg:flex\"\n asChild\n >\n <Link to=\"/cart\">\n <ShoppingCart className=\"h-5 w-5\" />\n {itemCount > 0 && (\n <Badge\n variant=\"destructive\"\n className=\"absolute -top-1 -right-1 h-4 w-4 flex items-center justify-center p-0 text-[10px]\"\n >\n {itemCount}\n </Badge>\n )}\n </Link>\n </Button>\n\n {/* Auth - Desktop Only */}\n <div className=\"hidden lg:flex\">\n {isAuthenticated ? (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\" size=\"icon\" className=\"h-10 w-10\">\n <User className=\"h-5 w-5\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-56\">\n <DropdownMenuLabel className=\"font-normal\">\n <div className=\"flex flex-col space-y-1\">\n <p className=\"text-sm font-medium\">{user?.username}</p>\n {user?.email && (\n <p className=\"text-xs text-muted-foreground\">{user.email}</p>\n )}\n </div>\n </DropdownMenuLabel>\n <DropdownMenuSeparator />\n <DropdownMenuItem asChild className=\"cursor-pointer\">\n <Link to=\"/my-orders\" className=\"flex items-center\">\n <Package className=\"mr-2 h-4 w-4\" />\n {t(\"myOrders\", \"My Orders\")}\n </Link>\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n onClick={handleLogout}\n className=\"text-red-600 focus:text-red-600 focus:bg-red-50 cursor-pointer\"\n >\n <LogOut className=\"mr-2 h-4 w-4\" />\n {t(\"logout\", \"Logout\")}\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n ) : (\n <Link to=\"/login\">\n <Button variant=\"ghost\" size=\"icon\" className=\"h-10 w-10\">\n <User className=\"h-5 w-5\" />\n </Button>\n </Link>\n )}\n </div>\n\n {/* Mobile Menu */}\n <Sheet open={mobileMenuOpen} onOpenChange={setMobileMenuOpen}>\n <SheetTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"lg:hidden h-8 w-8 sm:h-10 sm:w-10\"\n >\n <Menu className=\"h-4 w-4 sm:h-5 sm:w-5\" />\n </Button>\n </SheetTrigger>\n <SheetContent side=\"right\" className=\"w-[300px] sm:w-[400px] px-6\">\n <SheetHeader>\n <SheetTitle>{t(\"menu\")}</SheetTitle>\n </SheetHeader>\n\n {/* Mobile Search in Hamburger */}\n <div className=\"mt-6 pb-4 border-b\">\n <form onSubmit={handleSearchSubmit}>\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4\" />\n <Input\n type=\"search\"\n placeholder={t(\"searchPlaceholder\")}\n value={searchTerm}\n onChange={handleSearchChange}\n onFocus={handleSearchFocus}\n className=\"pl-10 h-11\"\n />\n </div>\n </form>\n\n {/* Search Results in Hamburger */}\n {showResults && searchTerm && (\n <div className=\"mt-3 max-h-[300px] overflow-y-auto rounded-lg border bg-card\">\n {searchResults.length > 0 ? (\n <div className=\"divide-y\">\n <div className=\"px-3 py-2 bg-muted/50\">\n <p className=\"text-xs font-medium text-muted-foreground\">\n {searchResults.length}{\" \"}\n {searchResults.length === 1\n ? \"result\"\n : \"results\"}\n </p>\n </div>\n {searchResults.slice(0, 5).map((product: Product) => (\n <Link\n key={product.id}\n to={`/products/${product.slug}`}\n onClick={() => {\n setMobileMenuOpen(false);\n clearSearch();\n setShowResults(false);\n }}\n className=\"flex items-center gap-3 p-3 hover:bg-muted/50 transition-colors\"\n >\n <img\n src={\n product.images[0] || \"/images/placeholder.png\"\n }\n alt={product.name}\n className=\"w-14 h-14 object-cover rounded flex-shrink-0\"\n />\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"font-medium text-sm line-clamp-1\">\n {product.name}\n </h4>\n <p className=\"text-xs text-muted-foreground capitalize\">\n {product.category}\n </p>\n <p className=\"text-sm font-semibold text-primary mt-1\">\n {formatPrice(\n product.price,\n constants.site.currency\n )}\n </p>\n </div>\n </Link>\n ))}\n {searchResults.length > 5 && (\n <div className=\"px-3 py-2 bg-muted/30 text-center\">\n <button\n onClick={() => {\n navigate(\n `/products?search=${encodeURIComponent(\n searchTerm\n )}`\n );\n setMobileMenuOpen(false);\n clearSearch();\n setShowResults(false);\n }}\n className=\"text-xs font-medium text-primary hover:underline\"\n >\n {t(\n \"viewAllResults\",\n `View all ${searchResults.length} results`\n )}\n </button>\n </div>\n )}\n </div>\n ) : (\n <div className=\"p-6 text-center\">\n <Search className=\"h-8 w-8 text-muted-foreground mx-auto mb-2 opacity-50\" />\n <p className=\"text-sm text-muted-foreground\">\n {t(\"noResults\", \"No results found\")}\n </p>\n </div>\n )}\n </div>\n )}\n </div>\n\n <div className=\"flex flex-col space-y-4 mt-6\">\n {navigation.map((item) => (\n <Link\n key={item.name}\n to={item.href}\n className=\"text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n {item.name}\n </Link>\n ))}\n <div className=\"border-t pt-4 space-y-4\">\n <Link\n to=\"/favorites\"\n className=\"flex items-center justify-between text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n <div className=\"flex items-center space-x-2\">\n <Heart className=\"h-5 w-5\" />\n <span>{t(\"favorites\")}</span>\n </div>\n <Badge variant=\"secondary\">{favoriteCount}</Badge>\n </Link>\n <Link\n to=\"/cart\"\n className=\"flex items-center justify-between w-full text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n <div className=\"flex items-center space-x-2\">\n <ShoppingCart className=\"h-5 w-5\" />\n <span>{t(\"cart\")}</span>\n </div>\n <div className=\"flex flex-col items-end\">\n <Badge variant=\"secondary\">{itemCount}</Badge>\n <span className=\"text-xs text-muted-foreground\">\n {formatPrice(state.total, constants.site.currency)}\n </span>\n </div>\n </Link>\n\n {/* Auth - Mobile */}\n {isAuthenticated ? (\n <div className=\"space-y-3\">\n <div className=\"flex items-center space-x-3 p-3 bg-muted/50 rounded-lg\">\n <div className=\"h-10 w-10 rounded-full bg-primary/10 flex items-center justify-center\">\n <User className=\"h-5 w-5 text-primary\" />\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium truncate\">{user?.username}</p>\n {user?.email && (\n <p className=\"text-xs text-muted-foreground truncate\">{user.email}</p>\n )}\n </div>\n </div>\n <Link\n to=\"/my-orders\"\n className=\"flex items-center space-x-2 text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n <Package className=\"h-5 w-5\" />\n <span>{t(\"myOrders\", \"My Orders\")}</span>\n </Link>\n <button\n onClick={() => {\n handleLogout();\n setMobileMenuOpen(false);\n }}\n className=\"flex items-center space-x-2 text-lg font-medium text-red-600 hover:text-red-700 transition-colors w-full\"\n >\n <LogOut className=\"h-5 w-5\" />\n <span>{t(\"logout\", \"Logout\")}</span>\n </button>\n </div>\n ) : (\n <Link\n to=\"/login\"\n className=\"flex items-center space-x-2 text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n <User className=\"h-5 w-5\" />\n <span>{t(\"login\", \"Login\")}</span>\n </Link>\n )}\n </div>\n </div>\n </SheetContent>\n </Sheet>\n </div>\n </div>\n </div>\n {/* Cart Drawer */}\n <CartDrawer\n open={isDrawerOpen}\n onOpenChange={setDrawerOpen}\n items={state.items.map((item) => ({\n id: item.id,\n name: item.product.name,\n href: `/products/${item.product.slug}`,\n price: item.product.on_sale && item.product.sale_price\n ? item.product.sale_price\n : item.product.price,\n quantity: item.quantity,\n image: item.product.images[0] || \"/images/placeholder.png\",\n imageAlt: item.product.name,\n }))}\n currency={constants.site.currency}\n onRemove={(id) => removeItem(id)}\n />\n </header>\n );\n}\n"
23
+ "content": "import { useState } from \"react\";\nimport { Link, useNavigate } from \"react-router\";\nimport { ShoppingCart, Menu, Search, Heart, Package, User, LogOut } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Badge } from \"@/components/ui/badge\";\nimport {\n Sheet,\n SheetHeader,\n SheetTitle,\n SheetContent,\n SheetTrigger,\n} from \"@/components/ui/sheet\";\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from \"@/components/ui/dialog\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { Logo } from \"@/components/Logo\";\nimport { useAuth } from \"@/modules/auth-core\";\nimport { CartDrawer } from \"@/modules/cart-drawer\";\nimport { toast } from \"sonner\";\nimport { useTranslation } from \"react-i18next\";\nimport constants from \"@/constants/constants.json\";\nimport type { Product } from \"@/modules/ecommerce-core/types\";\nimport {\n useCart,\n useFavorites,\n useSearch,\n formatPrice,\n} from \"@/modules/ecommerce-core\";\n\nexport function HeaderEcommerce() {\n const [mobileMenuOpen, setMobileMenuOpen] = useState(false);\n const [mobileSearchOpen, setMobileSearchOpen] = useState(false);\n const [desktopSearchOpen, setDesktopSearchOpen] = useState(false);\n const [showResults, setShowResults] = useState(false);\n const { itemCount, state } = useCart();\n const { favoriteCount } = useFavorites();\n const { isAuthenticated, user, logout } = useAuth();\n const navigate = useNavigate();\n const { t } = useTranslation(\"header-ecommerce\");\n\n const handleLogout = () => {\n logout();\n toast.success(t(\"logoutToastTitle\", \"Goodbye!\"), {\n description: t(\"logoutToastDesc\", \"You have been logged out successfully.\"),\n });\n };\n\n const {\n searchTerm,\n setSearchTerm,\n results: searchResults,\n clearSearch,\n } = useSearch();\n\n const handleSearchSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (searchTerm.trim()) {\n navigate(`/products?search=${encodeURIComponent(searchTerm)}`);\n setShowResults(false);\n setDesktopSearchOpen(false);\n clearSearch();\n }\n };\n\n const handleSearchFocus = () => {\n setShowResults(true);\n };\n\n const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n setSearchTerm(e.target.value);\n setShowResults(true);\n };\n\n const navigation = [\n { name: t(\"home\"), href: \"/\" },\n { name: t(\"products\"), href: \"/products\" },\n { name: t(\"about\"), href: \"/about\" },\n { name: t(\"contact\"), href: \"/contact\" },\n ];\n\n return (\n <header className=\"sticky top-0 z-50 w-full border-b border-border/20 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-3 sm:px-4 lg:px-8\">\n <div className=\"flex h-14 sm:h-16 md:h-20 items-center justify-between gap-2\">\n {/* Logo */}\n <div className=\"flex-shrink-0 min-w-0\">\n <Logo size=\"sm\" className=\"text-base sm:text-xl lg:text-2xl\" />\n </div>\n\n {/* Desktop Navigation - Centered */}\n <nav className=\"hidden lg:flex items-center space-x-12 absolute left-1/2 transform -translate-x-1/2\">\n {navigation.map((item) => (\n <Link\n key={item.name}\n to={item.href}\n className=\"text-base font-medium transition-colors hover:text-primary relative group py-2\"\n >\n {item.name}\n <span className=\"absolute -bottom-1 left-0 w-0 h-0.5 bg-primary transition-all duration-300 group-hover:w-full\"></span>\n </Link>\n ))}\n </nav>\n\n {/* Search & Actions - Right Aligned */}\n <div className=\"flex items-center space-x-1 sm:space-x-2 lg:space-x-4 flex-shrink-0\">\n {/* Desktop Search - Modal */}\n <Dialog\n open={desktopSearchOpen}\n onOpenChange={setDesktopSearchOpen}\n >\n <DialogTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"hidden lg:flex h-10 w-10\"\n >\n <Search className=\"h-5 w-5\" />\n </Button>\n </DialogTrigger>\n <DialogContent className=\"sm:max-w-2xl\">\n <DialogHeader>\n <DialogTitle>\n {t(\"searchProducts\", \"Search Products\")}\n </DialogTitle>\n </DialogHeader>\n <div className=\"space-y-4\">\n <form onSubmit={handleSearchSubmit}>\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-5 w-5\" />\n <Input\n type=\"search\"\n placeholder={t(\n \"searchPlaceholder\",\n \"Search for products...\"\n )}\n value={searchTerm}\n onChange={handleSearchChange}\n className=\"pl-11 h-12 text-base\"\n autoFocus\n />\n </div>\n </form>\n\n {/* Desktop Search Results */}\n {searchTerm.trim() && (\n <div className=\"max-h-[400px] overflow-y-auto rounded-lg border bg-card\">\n {searchResults.length > 0 ? (\n <div className=\"divide-y\">\n <div className=\"px-4 py-3 bg-muted/50\">\n <p className=\"text-sm font-medium text-muted-foreground\">\n {searchResults.length}{\" \"}\n {searchResults.length === 1\n ? \"result\"\n : \"results\"}{\" \"}\n found\n </p>\n </div>\n {searchResults.slice(0, 8).map((product: Product) => (\n <Link\n key={product.id}\n to={`/products/${product.slug}`}\n onClick={() => {\n setDesktopSearchOpen(false);\n clearSearch();\n }}\n className=\"flex items-center gap-4 p-4 hover:bg-muted/50 transition-colors\"\n >\n <img\n src={\n product.images[0] || \"/images/placeholder.png\"\n }\n alt={product.name}\n className=\"w-16 h-16 object-cover rounded flex-shrink-0\"\n />\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"font-medium text-base line-clamp-1\">\n {product.name}\n </h4>\n <p className=\"text-sm text-muted-foreground capitalize\">\n {product.category}\n </p>\n <p className=\"text-base font-semibold text-primary mt-1\">\n {formatPrice(\n product.price,\n constants.site.currency\n )}\n </p>\n </div>\n </Link>\n ))}\n {searchResults.length > 8 && (\n <div className=\"px-4 py-3 bg-muted/30 text-center\">\n <button\n onClick={() => {\n navigate(\n `/products?search=${encodeURIComponent(\n searchTerm\n )}`\n );\n setDesktopSearchOpen(false);\n clearSearch();\n }}\n className=\"text-sm font-medium text-primary hover:underline\"\n >\n {t(\n \"viewAllResults\",\n `View all ${searchResults.length} results`\n )}\n </button>\n </div>\n )}\n </div>\n ) : (\n <div className=\"p-8 text-center\">\n <Search className=\"h-12 w-12 text-muted-foreground mx-auto mb-3 opacity-50\" />\n <p className=\"text-base text-muted-foreground\">\n {t(\"noResults\", \"No products found\")}\n </p>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {t(\n \"tryDifferentKeywords\",\n \"Try different keywords\"\n )}\n </p>\n </div>\n )}\n </div>\n )}\n </div>\n </DialogContent>\n </Dialog>\n\n {/* Search - Mobile (Hidden - moved to hamburger menu) */}\n <Dialog open={mobileSearchOpen} onOpenChange={setMobileSearchOpen}>\n <DialogTrigger asChild>\n <Button variant=\"ghost\" size=\"icon\" className=\"hidden\">\n <Search className=\"h-4 w-4 sm:h-5 sm:w-5\" />\n </Button>\n </DialogTrigger>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>{t(\"searchProducts\")}</DialogTitle>\n </DialogHeader>\n <form\n onSubmit={(e) => {\n e.preventDefault();\n if (searchTerm.trim()) {\n navigate(\n `/products?search=${encodeURIComponent(searchTerm)}`\n );\n setMobileSearchOpen(false);\n clearSearch();\n }\n }}\n className=\"space-y-4\"\n >\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4\" />\n <Input\n type=\"search\"\n placeholder={t(\"searchPlaceholder\")}\n value={searchTerm}\n onChange={(e) => setSearchTerm(e.target.value)}\n className=\"pl-10\"\n autoFocus\n />\n </div>\n <div className=\"flex gap-2\">\n <Button type=\"submit\" className=\"flex-1\">\n {t(\"searchButton\", \"Search\")}\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => {\n clearSearch();\n setMobileSearchOpen(false);\n }}\n >\n {t(\"cancel\", \"Cancel\")}\n </Button>\n </div>\n </form>\n\n {/* Mobile Search Results */}\n {searchTerm.trim() && (\n <div className=\"mt-4 max-h-64 overflow-y-auto\">\n {searchResults.length > 0 ? (\n <div className=\"space-y-2\">\n <p className=\"text-sm text-muted-foreground mb-2\">\n {searchResults.length} result\n {searchResults.length !== 1 ? \"s\" : \"\"} found\n </p>\n {searchResults.slice(0, 5).map((product: Product) => (\n <Link\n key={product.id}\n to={`/products/${product.slug}`}\n onClick={() => {\n setMobileSearchOpen(false);\n clearSearch();\n }}\n className=\"block p-2 rounded hover:bg-muted/50 transition-colors\"\n >\n <div className=\"flex items-center gap-3\">\n <img\n src={\n product.images[0] || \"/images/placeholder.png\"\n }\n alt={product.name}\n className=\"w-10 h-10 object-cover rounded\"\n />\n <div className=\"flex-1\">\n <h4 className=\"font-medium text-sm\">\n {product.name}\n </h4>\n <p className=\"text-xs text-muted-foreground\">\n {product.category}\n </p>\n <p className=\"text-sm font-medium\">\n {formatPrice(\n product.price,\n constants.site.currency\n )}\n </p>\n </div>\n </div>\n </Link>\n ))}\n </div>\n ) : (\n <p className=\"text-sm text-muted-foreground\">\n {t(\"noResults\")}\n </p>\n )}\n </div>\n )}\n </DialogContent>\n </Dialog>\n\n {/* Wishlist - Desktop Only */}\n <Link to=\"/favorites\" className=\"hidden lg:block\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"relative h-10 w-10\"\n >\n <Heart className=\"h-5 w-5\" />\n {favoriteCount > 0 && (\n <Badge\n variant=\"destructive\"\n className=\"absolute -top-1 -right-1 h-4 w-4 flex items-center justify-center p-0 text-[10px]\"\n >\n {favoriteCount}\n </Badge>\n )}\n </Button>\n </Link>\n\n {/* Cart - Desktop Only (Goes to Cart Page) */}\n <Link to=\"/cart\" className=\"hidden lg:block\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"relative h-10 w-10\"\n >\n <ShoppingCart className=\"h-5 w-5\" />\n {itemCount > 0 && (\n <Badge\n variant=\"destructive\"\n className=\"absolute -top-1 -right-1 h-4 w-4 flex items-center justify-center p-0 text-[10px]\"\n >\n {itemCount}\n </Badge>\n )}\n </Button>\n </Link>\n\n {/* Auth - Desktop Only */}\n <div className=\"hidden lg:flex\">\n {isAuthenticated ? (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\" size=\"icon\" className=\"h-10 w-10\">\n <User className=\"h-5 w-5\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-56\">\n <DropdownMenuLabel className=\"font-normal\">\n <div className=\"flex flex-col space-y-1\">\n <p className=\"text-sm font-medium\">{user?.username}</p>\n {user?.email && (\n <p className=\"text-xs text-muted-foreground\">{user.email}</p>\n )}\n </div>\n </DropdownMenuLabel>\n <DropdownMenuSeparator />\n <DropdownMenuItem asChild className=\"cursor-pointer\">\n <Link to=\"/my-orders\" className=\"flex items-center\">\n <Package className=\"mr-2 h-4 w-4\" />\n {t(\"myOrders\", \"My Orders\")}\n </Link>\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n onClick={handleLogout}\n className=\"text-red-600 focus:text-red-600 focus:bg-red-50 cursor-pointer\"\n >\n <LogOut className=\"mr-2 h-4 w-4\" />\n {t(\"logout\", \"Logout\")}\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n ) : (\n <Link to=\"/login\">\n <Button variant=\"ghost\" size=\"icon\" className=\"h-10 w-10\">\n <User className=\"h-5 w-5\" />\n </Button>\n </Link>\n )}\n </div>\n\n {/* Mobile Menu */}\n <Sheet open={mobileMenuOpen} onOpenChange={setMobileMenuOpen}>\n <SheetTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"lg:hidden h-8 w-8 sm:h-10 sm:w-10\"\n >\n <Menu className=\"h-4 w-4 sm:h-5 sm:w-5\" />\n </Button>\n </SheetTrigger>\n <SheetContent side=\"right\" className=\"w-[300px] sm:w-[400px] px-6\">\n <SheetHeader>\n <SheetTitle>{t(\"menu\")}</SheetTitle>\n </SheetHeader>\n\n {/* Mobile Search in Hamburger */}\n <div className=\"mt-6 pb-4 border-b\">\n <form onSubmit={handleSearchSubmit}>\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4\" />\n <Input\n type=\"search\"\n placeholder={t(\"searchPlaceholder\")}\n value={searchTerm}\n onChange={handleSearchChange}\n onFocus={handleSearchFocus}\n className=\"pl-10 h-11\"\n />\n </div>\n </form>\n\n {/* Search Results in Hamburger */}\n {showResults && searchTerm && (\n <div className=\"mt-3 max-h-[300px] overflow-y-auto rounded-lg border bg-card\">\n {searchResults.length > 0 ? (\n <div className=\"divide-y\">\n <div className=\"px-3 py-2 bg-muted/50\">\n <p className=\"text-xs font-medium text-muted-foreground\">\n {searchResults.length}{\" \"}\n {searchResults.length === 1\n ? \"result\"\n : \"results\"}\n </p>\n </div>\n {searchResults.slice(0, 5).map((product: Product) => (\n <Link\n key={product.id}\n to={`/products/${product.slug}`}\n onClick={() => {\n setMobileMenuOpen(false);\n clearSearch();\n setShowResults(false);\n }}\n className=\"flex items-center gap-3 p-3 hover:bg-muted/50 transition-colors\"\n >\n <img\n src={\n product.images[0] || \"/images/placeholder.png\"\n }\n alt={product.name}\n className=\"w-14 h-14 object-cover rounded flex-shrink-0\"\n />\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"font-medium text-sm line-clamp-1\">\n {product.name}\n </h4>\n <p className=\"text-xs text-muted-foreground capitalize\">\n {product.category}\n </p>\n <p className=\"text-sm font-semibold text-primary mt-1\">\n {formatPrice(\n product.price,\n constants.site.currency\n )}\n </p>\n </div>\n </Link>\n ))}\n {searchResults.length > 5 && (\n <div className=\"px-3 py-2 bg-muted/30 text-center\">\n <button\n onClick={() => {\n navigate(\n `/products?search=${encodeURIComponent(\n searchTerm\n )}`\n );\n setMobileMenuOpen(false);\n clearSearch();\n setShowResults(false);\n }}\n className=\"text-xs font-medium text-primary hover:underline\"\n >\n {t(\n \"viewAllResults\",\n `View all ${searchResults.length} results`\n )}\n </button>\n </div>\n )}\n </div>\n ) : (\n <div className=\"p-6 text-center\">\n <Search className=\"h-8 w-8 text-muted-foreground mx-auto mb-2 opacity-50\" />\n <p className=\"text-sm text-muted-foreground\">\n {t(\"noResults\", \"No results found\")}\n </p>\n </div>\n )}\n </div>\n )}\n </div>\n\n <div className=\"flex flex-col space-y-4 mt-6\">\n {navigation.map((item) => (\n <Link\n key={item.name}\n to={item.href}\n className=\"text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n {item.name}\n </Link>\n ))}\n <div className=\"border-t pt-4 space-y-4\">\n <Link\n to=\"/favorites\"\n className=\"flex items-center justify-between text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n <div className=\"flex items-center space-x-2\">\n <Heart className=\"h-5 w-5\" />\n <span>{t(\"favorites\")}</span>\n </div>\n <Badge variant=\"secondary\">{favoriteCount}</Badge>\n </Link>\n <Link\n to=\"/cart\"\n className=\"flex items-center justify-between w-full text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n <div className=\"flex items-center space-x-2\">\n <ShoppingCart className=\"h-5 w-5\" />\n <span>{t(\"cart\")}</span>\n </div>\n <div className=\"flex flex-col items-end\">\n <Badge variant=\"secondary\">{itemCount}</Badge>\n <span className=\"text-xs text-muted-foreground\">\n {formatPrice(state.total, constants.site.currency)}\n </span>\n </div>\n </Link>\n\n {/* Auth - Mobile */}\n {isAuthenticated ? (\n <div className=\"space-y-3\">\n <div className=\"flex items-center space-x-3 p-3 bg-muted/50 rounded-lg\">\n <div className=\"h-10 w-10 rounded-full bg-primary/10 flex items-center justify-center\">\n <User className=\"h-5 w-5 text-primary\" />\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium truncate\">{user?.username}</p>\n {user?.email && (\n <p className=\"text-xs text-muted-foreground truncate\">{user.email}</p>\n )}\n </div>\n </div>\n <Link\n to=\"/my-orders\"\n className=\"flex items-center space-x-2 text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n <Package className=\"h-5 w-5\" />\n <span>{t(\"myOrders\", \"My Orders\")}</span>\n </Link>\n <button\n onClick={() => {\n handleLogout();\n setMobileMenuOpen(false);\n }}\n className=\"flex items-center space-x-2 text-lg font-medium text-red-600 hover:text-red-700 transition-colors w-full\"\n >\n <LogOut className=\"h-5 w-5\" />\n <span>{t(\"logout\", \"Logout\")}</span>\n </button>\n </div>\n ) : (\n <Link\n to=\"/login\"\n className=\"flex items-center space-x-2 text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n <User className=\"h-5 w-5\" />\n <span>{t(\"login\", \"Login\")}</span>\n </Link>\n )}\n </div>\n </div>\n </SheetContent>\n </Sheet>\n </div>\n </div>\n </div>\n {/* Cart Drawer */}\n <CartDrawer showTrigger={false} />\n </header>\n );\n}\n"
24
24
  },
25
25
  {
26
26
  "path": "header-ecommerce/lang/en.json",
@@ -15,7 +15,7 @@
15
15
  "path": "hero-carousel/hero-carousel.tsx",
16
16
  "type": "registry:component",
17
17
  "target": "$modules$/hero-carousel/hero-carousel.tsx",
18
- "content": "\"use client\";\n\nimport { useState, useEffect, useCallback, useRef } from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { Link } from \"react-router\";\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/button\";\nimport { ChevronLeft, ChevronRight } from \"lucide-react\";\n\ninterface Slide {\n image: string;\n title: string;\n description: string;\n primaryButton?: {\n text: string;\n link: string;\n };\n secondaryButton?: {\n text: string;\n link: string;\n };\n}\n\ninterface HeroCarouselProps {\n slides?: Slide[];\n autoPlay?: boolean;\n interval?: number;\n showDots?: boolean;\n showArrows?: boolean;\n pauseOnHover?: boolean;\n className?: string;\n}\n\nexport function HeroCarousel({\n slides,\n autoPlay = true,\n interval = 5000,\n showDots = true,\n showArrows = true,\n pauseOnHover = true,\n className,\n}: HeroCarouselProps) {\n const { t } = useTranslation(\"hero-carousel\");\n const [currentSlide, setCurrentSlide] = useState(0);\n const [isPaused, setIsPaused] = useState(false);\n const [dragStart, setDragStart] = useState<number | null>(null);\n const [isDragging, setIsDragging] = useState(false);\n const containerRef = useRef<HTMLElement>(null);\n\n const defaultSlides: Slide[] = [\n {\n image: \"/images/placeholder.png\",\n title: t(\"slides.0.title\"),\n description: t(\"slides.0.description\"),\n primaryButton: { text: t(\"slides.0.primaryButton\"), link: \"/get-started\" },\n secondaryButton: { text: t(\"slides.0.secondaryButton\"), link: \"/learn-more\" },\n },\n {\n image: \"/images/placeholder.png\",\n title: t(\"slides.1.title\"),\n description: t(\"slides.1.description\"),\n primaryButton: { text: t(\"slides.1.primaryButton\"), link: \"/features\" },\n },\n {\n image: \"/images/placeholder.png\",\n title: t(\"slides.2.title\"),\n description: t(\"slides.2.description\"),\n primaryButton: { text: t(\"slides.2.primaryButton\"), link: \"/contact\" },\n secondaryButton: { text: t(\"slides.2.secondaryButton\"), link: \"/demo\" },\n },\n ];\n\n const displaySlides = slides ?? defaultSlides;\n\n const goToSlide = useCallback((index: number) => {\n setCurrentSlide(index);\n }, []);\n\n const nextSlide = useCallback(() => {\n setCurrentSlide((prev) => (prev + 1) % displaySlides.length);\n }, [displaySlides.length]);\n\n const prevSlide = useCallback(() => {\n setCurrentSlide((prev) => (prev - 1 + displaySlides.length) % displaySlides.length);\n }, [displaySlides.length]);\n\n // Auto-play\n useEffect(() => {\n if (!autoPlay || isPaused) return;\n\n const timer = setInterval(nextSlide, interval);\n return () => clearInterval(timer);\n }, [autoPlay, interval, isPaused, nextSlide]);\n\n // Mouse drag handlers\n const handleMouseDown = (e: React.MouseEvent) => {\n setIsDragging(true);\n setDragStart(e.clientX);\n };\n\n const handleMouseMove = (e: React.MouseEvent) => {\n if (!isDragging || dragStart === null) return;\n e.preventDefault();\n };\n\n const handleMouseUp = (e: React.MouseEvent) => {\n if (!isDragging || dragStart === null) {\n setIsDragging(false);\n return;\n }\n\n const diff = dragStart - e.clientX;\n\n if (Math.abs(diff) > 50) {\n if (diff > 0) {\n nextSlide();\n } else {\n prevSlide();\n }\n }\n\n setIsDragging(false);\n setDragStart(null);\n };\n\n const handleMouseLeave = () => {\n if (isDragging) {\n setIsDragging(false);\n setDragStart(null);\n }\n };\n\n // Touch handlers for swipe\n const handleTouchStart = (e: React.TouchEvent) => {\n setDragStart(e.touches[0].clientX);\n };\n\n const handleTouchEnd = (e: React.TouchEvent) => {\n if (dragStart === null) return;\n\n const touchEnd = e.changedTouches[0].clientX;\n const diff = dragStart - touchEnd;\n\n if (Math.abs(diff) > 50) {\n if (diff > 0) {\n nextSlide();\n } else {\n prevSlide();\n }\n }\n\n setDragStart(null);\n };\n\n // Wheel handler for trackpad two-finger swipe\n const handleWheel = useCallback((e: WheelEvent) => {\n // Only handle horizontal scroll (trackpad two-finger swipe)\n if (Math.abs(e.deltaX) > Math.abs(e.deltaY) && Math.abs(e.deltaX) > 30) {\n e.preventDefault();\n if (e.deltaX > 0) {\n nextSlide();\n } else {\n prevSlide();\n }\n }\n }, [nextSlide, prevSlide]);\n\n // Add wheel event listener\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n let lastWheelTime = 0;\n const throttledWheel = (e: WheelEvent) => {\n const now = Date.now();\n if (now - lastWheelTime > 500) {\n handleWheel(e);\n lastWheelTime = now;\n }\n };\n\n container.addEventListener(\"wheel\", throttledWheel, { passive: false });\n return () => container.removeEventListener(\"wheel\", throttledWheel);\n }, [handleWheel]);\n\n return (\n <section\n ref={containerRef}\n className={cn(\n \"relative w-full h-[500px] md:h-[600px] lg:h-[700px] overflow-hidden select-none\",\n isDragging ? \"cursor-grabbing\" : \"cursor-grab\",\n className\n )}\n onMouseEnter={() => pauseOnHover && setIsPaused(true)}\n onMouseLeave={() => {\n pauseOnHover && setIsPaused(false);\n handleMouseLeave();\n }}\n onMouseDown={handleMouseDown}\n onMouseMove={handleMouseMove}\n onMouseUp={handleMouseUp}\n onTouchStart={handleTouchStart}\n onTouchEnd={handleTouchEnd}\n >\n {/* Slides */}\n {displaySlides.map((slide, index) => (\n <div\n key={index}\n className={cn(\n \"absolute inset-0 transition-opacity duration-700\",\n index === currentSlide ? \"opacity-100 z-10\" : \"opacity-0 z-0\"\n )}\n >\n {/* Background Image */}\n <img\n src={slide.image}\n alt={slide.title}\n className=\"absolute inset-0 w-full h-full object-cover pointer-events-none\"\n draggable={false}\n />\n\n {/* Overlay */}\n <div className=\"absolute inset-0 bg-black/50\" />\n\n {/* Content */}\n <div className=\"relative z-10 h-full flex items-center\">\n <div className=\"container max-w-[var(--container-max-width)] mx-auto px-6 md:px-24 lg:px-32\">\n <div\n className={cn(\n \"max-w-2xl transition-all duration-700 delay-200\",\n index === currentSlide\n ? \"translate-y-0 opacity-100\"\n : \"translate-y-8 opacity-0\"\n )}\n >\n <h1 className=\"text-4xl md:text-5xl lg:text-6xl font-bold text-white mb-4 md:mb-6\">\n {slide.title}\n </h1>\n <p className=\"text-lg md:text-xl text-white/90 mb-6 md:mb-8\">\n {slide.description}\n </p>\n <div className=\"flex flex-wrap gap-4\">\n {slide.primaryButton && (\n <Link to={slide.primaryButton.link}>\n <Button size=\"lg\" className=\"text-base\">\n {slide.primaryButton.text}\n </Button>\n </Link>\n )}\n {slide.secondaryButton && (\n <Link to={slide.secondaryButton.link}>\n <Button size=\"lg\" variant=\"outline\" className=\"text-base bg-white/10 border-white/30 text-white hover:bg-white/20\">\n {slide.secondaryButton.text}\n </Button>\n </Link>\n )}\n </div>\n </div>\n </div>\n </div>\n </div>\n ))}\n\n {/* Navigation Arrows */}\n {showArrows && (\n <>\n <button\n onClick={prevSlide}\n className=\"absolute left-4 top-1/2 -translate-y-1/2 z-20 w-12 h-12 rounded-full bg-white/20 backdrop-blur-sm flex items-center justify-center text-white hover:bg-white/30 transition-colors\"\n aria-label=\"Previous slide\"\n >\n <ChevronLeft className=\"w-6 h-6\" />\n </button>\n <button\n onClick={nextSlide}\n className=\"absolute right-4 top-1/2 -translate-y-1/2 z-20 w-12 h-12 rounded-full bg-white/20 backdrop-blur-sm flex items-center justify-center text-white hover:bg-white/30 transition-colors\"\n aria-label=\"Next slide\"\n >\n <ChevronRight className=\"w-6 h-6\" />\n </button>\n </>\n )}\n\n {/* Dots */}\n {showDots && (\n <div className=\"absolute bottom-6 left-1/2 -translate-x-1/2 z-20 flex gap-2\">\n {displaySlides.map((_, index) => (\n <button\n key={index}\n onClick={() => goToSlide(index)}\n className={cn(\n \"w-3 h-3 rounded-full transition-all duration-300\",\n index === currentSlide\n ? \"bg-white w-8\"\n : \"bg-white/50 hover:bg-white/70\"\n )}\n aria-label={`Go to slide ${index + 1}`}\n />\n ))}\n </div>\n )}\n </section>\n );\n}\n"
18
+ "content": "\"use client\";\n\nimport { useState, useEffect, useCallback, useRef } from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { Link } from \"react-router\";\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/button\";\nimport { ChevronLeft, ChevronRight } from \"lucide-react\";\n\ninterface Slide {\n image: string;\n title: string;\n description: string;\n primaryButton?: {\n text: string;\n link: string;\n };\n secondaryButton?: {\n text: string;\n link: string;\n };\n}\n\ninterface HeroCarouselProps {\n slides?: Slide[];\n autoPlay?: boolean;\n interval?: number;\n showDots?: boolean;\n showArrows?: boolean;\n pauseOnHover?: boolean;\n className?: string;\n}\n\nexport function HeroCarousel({\n slides,\n autoPlay = true,\n interval = 5000,\n showDots = true,\n showArrows = true,\n pauseOnHover = true,\n className,\n}: HeroCarouselProps) {\n const { t } = useTranslation(\"hero-carousel\");\n const [currentSlide, setCurrentSlide] = useState(0);\n const [isPaused, setIsPaused] = useState(false);\n const [dragStart, setDragStart] = useState<number | null>(null);\n const [isDragging, setIsDragging] = useState(false);\n const containerRef = useRef<HTMLElement>(null);\n\n const defaultSlides: Slide[] = [\n {\n image: \"/images/placeholder.png\",\n title: t(\"slides.0.title\"),\n description: t(\"slides.0.description\"),\n primaryButton: { text: t(\"slides.0.primaryButton\"), link: \"/get-started\" },\n secondaryButton: { text: t(\"slides.0.secondaryButton\"), link: \"/learn-more\" },\n },\n {\n image: \"/images/placeholder.png\",\n title: t(\"slides.1.title\"),\n description: t(\"slides.1.description\"),\n primaryButton: { text: t(\"slides.1.primaryButton\"), link: \"/features\" },\n },\n {\n image: \"/images/placeholder.png\",\n title: t(\"slides.2.title\"),\n description: t(\"slides.2.description\"),\n primaryButton: { text: t(\"slides.2.primaryButton\"), link: \"/contact\" },\n secondaryButton: { text: t(\"slides.2.secondaryButton\"), link: \"/demo\" },\n },\n ];\n\n const displaySlides = slides ?? defaultSlides;\n\n const goToSlide = useCallback((index: number) => {\n setCurrentSlide(index);\n }, []);\n\n const nextSlide = useCallback(() => {\n setCurrentSlide((prev) => (prev + 1) % displaySlides.length);\n }, [displaySlides.length]);\n\n const prevSlide = useCallback(() => {\n setCurrentSlide((prev) => (prev - 1 + displaySlides.length) % displaySlides.length);\n }, [displaySlides.length]);\n\n // Auto-play\n useEffect(() => {\n if (!autoPlay || isPaused) return;\n\n const timer = setInterval(nextSlide, interval);\n return () => clearInterval(timer);\n }, [autoPlay, interval, isPaused, nextSlide]);\n\n // Mouse drag handlers\n const handleMouseDown = (e: React.MouseEvent) => {\n setIsDragging(true);\n setDragStart(e.clientX);\n };\n\n const handleMouseMove = (e: React.MouseEvent) => {\n if (!isDragging || dragStart === null) return;\n e.preventDefault();\n };\n\n const handleMouseUp = (e: React.MouseEvent) => {\n if (!isDragging || dragStart === null) {\n setIsDragging(false);\n return;\n }\n\n const diff = dragStart - e.clientX;\n\n if (Math.abs(diff) > 50) {\n if (diff > 0) {\n nextSlide();\n } else {\n prevSlide();\n }\n }\n\n setIsDragging(false);\n setDragStart(null);\n };\n\n const handleMouseLeave = () => {\n if (isDragging) {\n setIsDragging(false);\n setDragStart(null);\n }\n };\n\n // Touch handlers for swipe\n const handleTouchStart = (e: React.TouchEvent) => {\n setDragStart(e.touches[0].clientX);\n };\n\n const handleTouchEnd = (e: React.TouchEvent) => {\n if (dragStart === null) return;\n\n const touchEnd = e.changedTouches[0].clientX;\n const diff = dragStart - touchEnd;\n\n if (Math.abs(diff) > 50) {\n if (diff > 0) {\n nextSlide();\n } else {\n prevSlide();\n }\n }\n\n setDragStart(null);\n };\n\n // Wheel handler for trackpad two-finger swipe\n const handleWheel = useCallback((e: WheelEvent) => {\n // Only handle horizontal scroll (trackpad two-finger swipe)\n if (Math.abs(e.deltaX) > Math.abs(e.deltaY) && Math.abs(e.deltaX) > 30) {\n e.preventDefault();\n if (e.deltaX > 0) {\n nextSlide();\n } else {\n prevSlide();\n }\n }\n }, [nextSlide, prevSlide]);\n\n // Add wheel event listener\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n let lastWheelTime = 0;\n const throttledWheel = (e: WheelEvent) => {\n const now = Date.now();\n if (now - lastWheelTime > 500) {\n handleWheel(e);\n lastWheelTime = now;\n }\n };\n\n container.addEventListener(\"wheel\", throttledWheel, { passive: false });\n return () => container.removeEventListener(\"wheel\", throttledWheel);\n }, [handleWheel]);\n\n return (\n <section\n ref={containerRef}\n className={cn(\n \"relative w-full h-[500px] md:h-[600px] lg:h-[700px] overflow-hidden select-none\",\n isDragging ? \"cursor-grabbing\" : \"cursor-grab\",\n className\n )}\n onMouseEnter={() => { if (pauseOnHover) setIsPaused(true); }}\n onMouseLeave={() => {\n if (pauseOnHover) setIsPaused(false);\n handleMouseLeave();\n }}\n onMouseDown={handleMouseDown}\n onMouseMove={handleMouseMove}\n onMouseUp={handleMouseUp}\n onTouchStart={handleTouchStart}\n onTouchEnd={handleTouchEnd}\n >\n {/* Slides */}\n {displaySlides.map((slide, index) => (\n <div\n key={index}\n className={cn(\n \"absolute inset-0 transition-opacity duration-700\",\n index === currentSlide ? \"opacity-100 z-10\" : \"opacity-0 z-0\"\n )}\n >\n {/* Background Image */}\n <img\n src={slide.image}\n alt={slide.title}\n className=\"absolute inset-0 w-full h-full object-cover pointer-events-none\"\n draggable={false}\n />\n\n {/* Overlay */}\n <div className=\"absolute inset-0 bg-black/50\" />\n\n {/* Content */}\n <div className=\"relative z-10 h-full flex items-center\">\n <div className=\"container max-w-[var(--container-max-width)] mx-auto px-6 md:px-24 lg:px-32\">\n <div\n className={cn(\n \"max-w-2xl transition-all duration-700 delay-200\",\n index === currentSlide\n ? \"translate-y-0 opacity-100\"\n : \"translate-y-8 opacity-0\"\n )}\n >\n <h1 className=\"text-4xl md:text-5xl lg:text-6xl font-bold text-white mb-4 md:mb-6\">\n {slide.title}\n </h1>\n <p className=\"text-lg md:text-xl text-white/90 mb-6 md:mb-8\">\n {slide.description}\n </p>\n <div className=\"flex flex-wrap gap-4\">\n {slide.primaryButton && (\n <Link to={slide.primaryButton.link}>\n <Button size=\"lg\" className=\"text-base\">\n {slide.primaryButton.text}\n </Button>\n </Link>\n )}\n {slide.secondaryButton && (\n <Link to={slide.secondaryButton.link}>\n <Button size=\"lg\" variant=\"outline\" className=\"text-base bg-white/10 border-white/30 text-white hover:bg-white/20\">\n {slide.secondaryButton.text}\n </Button>\n </Link>\n )}\n </div>\n </div>\n </div>\n </div>\n </div>\n ))}\n\n {/* Navigation Arrows */}\n {showArrows && (\n <>\n <button\n onClick={prevSlide}\n className=\"absolute left-4 top-1/2 -translate-y-1/2 z-20 w-12 h-12 rounded-full bg-white/20 backdrop-blur-sm flex items-center justify-center text-white hover:bg-white/30 transition-colors\"\n aria-label=\"Previous slide\"\n >\n <ChevronLeft className=\"w-6 h-6\" />\n </button>\n <button\n onClick={nextSlide}\n className=\"absolute right-4 top-1/2 -translate-y-1/2 z-20 w-12 h-12 rounded-full bg-white/20 backdrop-blur-sm flex items-center justify-center text-white hover:bg-white/30 transition-colors\"\n aria-label=\"Next slide\"\n >\n <ChevronRight className=\"w-6 h-6\" />\n </button>\n </>\n )}\n\n {/* Dots */}\n {showDots && (\n <div className=\"absolute bottom-6 left-1/2 -translate-x-1/2 z-20 flex gap-2\">\n {displaySlides.map((_, index) => (\n <button\n key={index}\n onClick={() => goToSlide(index)}\n className={cn(\n \"w-3 h-3 rounded-full transition-all duration-300\",\n index === currentSlide\n ? \"bg-white w-8\"\n : \"bg-white/50 hover:bg-white/70\"\n )}\n aria-label={`Go to slide ${index + 1}`}\n />\n ))}\n </div>\n )}\n </section>\n );\n}\n"
19
19
  },
20
20
  {
21
21
  "path": "hero-carousel/index.ts",
@@ -27,13 +27,13 @@
27
27
  "path": "hero-carousel/lang/en.json",
28
28
  "type": "registry:lang",
29
29
  "target": "$modules$/hero-carousel/lang/en.json",
30
- "content": "{\r\n \"slides\": [\r\n {\r\n \"title\": \"Build Something Amazing\",\r\n \"description\": \"Ask Promake to customize these slides based on your brand message and call-to-action.\",\r\n \"primaryButton\": \"Get Started\",\r\n \"secondaryButton\": \"Learn More\"\r\n },\r\n {\r\n \"title\": \"Powerful Features\",\r\n \"description\": \"Discover tools and features that help you achieve more with less effort.\",\r\n \"primaryButton\": \"Explore Features\"\r\n },\r\n {\r\n \"title\": \"Ready to Transform?\",\r\n \"description\": \"Join thousands of satisfied customers who have already made the switch.\",\r\n \"primaryButton\": \"Contact Us\",\r\n \"secondaryButton\": \"See Demo\"\r\n }\r\n ]\r\n}\r\n"
30
+ "content": "{\r\n \"slides\": [\r\n {\r\n \"title\": \"Build Something Amazing\",\r\n \"description\": \"Edit these slides via Promake to match your brand message and call-to-action.\",\r\n \"primaryButton\": \"Get Started\",\r\n \"secondaryButton\": \"Learn More\"\r\n },\r\n {\r\n \"title\": \"Powerful Features\",\r\n \"description\": \"Discover tools and features that help you achieve more with less effort.\",\r\n \"primaryButton\": \"Explore Features\"\r\n },\r\n {\r\n \"title\": \"Ready to Transform?\",\r\n \"description\": \"Join thousands of satisfied customers who have already made the switch.\",\r\n \"primaryButton\": \"Contact Us\",\r\n \"secondaryButton\": \"See Demo\"\r\n }\r\n ]\r\n}\r\n"
31
31
  },
32
32
  {
33
33
  "path": "hero-carousel/lang/tr.json",
34
34
  "type": "registry:lang",
35
35
  "target": "$modules$/hero-carousel/lang/tr.json",
36
- "content": "{\r\n \"slides\": [\r\n {\r\n \"title\": \"Harika Bir Şey İnşa Edin\",\r\n \"description\": \"Bu slaytları marka mesajınıza ve eylem çağrınıza göre özelleştirmek için Promake'e sorun.\",\r\n \"primaryButton\": \"Başlayın\",\r\n \"secondaryButton\": \"Daha Fazla Bilgi\"\r\n },\r\n {\r\n \"title\": \"Güçlü Özellikler\",\r\n \"description\": \"Daha az çabayla daha fazlasını başarmanıza yardımcı olan araçları ve özellikleri keşfedin.\",\r\n \"primaryButton\": \"Özellikleri Keşfet\"\r\n },\r\n {\r\n \"title\": \"Dönüşüme Hazır mısınız?\",\r\n \"description\": \"Geçiş yapan binlerce memnun müşteriye katılın.\",\r\n \"primaryButton\": \"Bize Ulaşın\",\r\n \"secondaryButton\": \"Demo İzle\"\r\n }\r\n ]\r\n}\r\n"
36
+ "content": "{\r\n \"slides\": [\r\n {\r\n \"title\": \"Harika Bir Şey İnşa Edin\",\r\n \"description\": \"Bu slaytları marka mesajınıza ve eylem çağrınıza uyacak şekilde Promake üzerinden düzenleyin.\",\r\n \"primaryButton\": \"Başlayın\",\r\n \"secondaryButton\": \"Daha Fazla Bilgi\"\r\n },\r\n {\r\n \"title\": \"Güçlü Özellikler\",\r\n \"description\": \"Daha az çabayla daha fazlasını başarmanıza yardımcı olan araçları ve özellikleri keşfedin.\",\r\n \"primaryButton\": \"Özellikleri Keşfet\"\r\n },\r\n {\r\n \"title\": \"Dönüşüme Hazır mısınız?\",\r\n \"description\": \"Geçiş yapan binlerce memnun müşteriye katılın.\",\r\n \"primaryButton\": \"Bize Ulaşın\",\r\n \"secondaryButton\": \"Demo İzle\"\r\n }\r\n ]\r\n}\r\n"
37
37
  }
38
38
  ],
39
39
  "exports": {
@@ -24,13 +24,13 @@
24
24
  "path": "hero-cta/lang/en.json",
25
25
  "type": "registry:lang",
26
26
  "target": "$modules$/hero-cta/lang/en.json",
27
- "content": "{\r\n \"heading\": \"Ask Promake to customize this hero heading based on your site\",\r\n \"description\": \"This is where your hero section description will appear. Ask Promake to replace this text with content relevant to your industry and services. Lorem ipsum dolor sit amet.\",\r\n \"reviews\": \"Ask Promake to customize this review count\",\r\n \"primaryCta\": \"Get Started\",\r\n \"secondaryCta\": \"Learn More\",\r\n \"imageAlt\": \"Hero image\"\r\n}\r\n"
27
+ "content": "{\r\n \"heading\": \"Customize this hero heading with Promake\",\r\n \"description\": \"This is where your hero section description will appear. Let Promake tailor this text to your industry and services. Lorem ipsum dolor sit amet.\",\r\n \"reviews\": \"Update this review count using Promake\",\r\n \"primaryCta\": \"Get Started\",\r\n \"secondaryCta\": \"Learn More\",\r\n \"imageAlt\": \"Hero image\"\r\n}\r\n"
28
28
  },
29
29
  {
30
30
  "path": "hero-cta/lang/tr.json",
31
31
  "type": "registry:lang",
32
32
  "target": "$modules$/hero-cta/lang/tr.json",
33
- "content": "{\r\n \"heading\": \"AI bu hero başlığını sitenize göre özelleştirecektir\",\r\n \"description\": \"Hero bölümü açıklamanız burada görünecektir. AI bu metni hedef kitlenize ve içeriğinize göre ilgili içerikle değiştirecektir. Lorem ipsum dolor sit amet.\",\r\n \"reviews\": \"AI bu yorum sayısını özelleştirecektir\",\r\n \"primaryCta\": \"Başlayın\",\r\n \"secondaryCta\": \"Daha Fazla Bilgi\",\r\n \"imageAlt\": \"Hero görseli\"\r\n}\r\n"
33
+ "content": "{\r\n \"heading\": \"Bu hero başlığını Promake ile özelleştirin\",\r\n \"description\": \"Hero bölümü açıklamanız burada görünecektir. Promake ile bu metni sektörünüze ve hizmetlerinize göre uyarlayın. Lorem ipsum dolor sit amet.\",\r\n \"reviews\": \"Bu yorum sayısını Promake kullanarak güncelleyin\",\r\n \"primaryCta\": \"Başlayın\",\r\n \"secondaryCta\": \"Daha Fazla Bilgi\",\r\n \"imageAlt\": \"Hero görseli\"\r\n}\r\n"
34
34
  }
35
35
  ],
36
36
  "exports": {
@@ -22,13 +22,13 @@
22
22
  "path": "hero-gradient/lang/en.json",
23
23
  "type": "registry:lang",
24
24
  "target": "$modules$/hero-gradient/lang/en.json",
25
- "content": "{\r\n \"badge\": \"Ask Promake to customize this badge text\",\r\n \"headingLine1\": \"Ask Promake to replace this heading\",\r\n \"headingLine2\": \"with your site headline\",\r\n \"description\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ask Promake to customize this description based on your site and audience. Sed do eiusmod tempor incididunt ut labore.\",\r\n \"primaryCta\": \"Get Started\",\r\n \"secondaryCta\": \"Contact Sales\",\r\n \"users\": \"Active Users\",\r\n \"uptime\": \"Uptime\",\r\n \"support\": \"Support\"\r\n}\r\n"
25
+ "content": "{\r\n \"badge\": \"Work with Promake to personalize this badge text\",\r\n \"headingLine1\": \"Have Promake update this heading\",\r\n \"headingLine2\": \"with your site headline\",\r\n \"description\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Promake can help tailor this description for your audience. Sed do eiusmod tempor incididunt ut labore.\",\r\n \"primaryCta\": \"Get Started\",\r\n \"secondaryCta\": \"Contact Sales\",\r\n \"users\": \"Active Users\",\r\n \"uptime\": \"Uptime\",\r\n \"support\": \"Support\"\r\n}\r\n"
26
26
  },
27
27
  {
28
28
  "path": "hero-gradient/lang/tr.json",
29
29
  "type": "registry:lang",
30
30
  "target": "$modules$/hero-gradient/lang/tr.json",
31
- "content": "{\r\n \"badge\": \"AI bu rozet metnini özelleştirecektir\",\r\n \"headingLine1\": \"AI bu başlığı\",\r\n \"headingLine2\": \"site başlığınızla değiştirecektir\",\r\n \"description\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. AI bu açıklamayı sitenize ve hedef kitlenize göre özelleştirecektir. Sed do eiusmod tempor incididunt ut labore.\",\r\n \"primaryCta\": \"Başlayın\",\r\n \"secondaryCta\": \"Satış ile İletişim\",\r\n \"users\": \"Aktif Kullanıcı\",\r\n \"uptime\": \"Çalışma Süresi\",\r\n \"support\": \"Destek\"\r\n}\r\n"
31
+ "content": "{\r\n \"badge\": \"Promake ile bu rozet metnini kişiselleştirin\",\r\n \"headingLine1\": \"Promake'ten bu başlığı\",\r\n \"headingLine2\": \"site başlığınızla güncellemesini isteyin\",\r\n \"description\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Promake bu açıklamayı hedef kitleniz için uyarlamanıza yardımcı olabilir. Sed do eiusmod tempor incididunt ut labore.\",\r\n \"primaryCta\": \"Başlayın\",\r\n \"secondaryCta\": \"Satış ile İletişim\",\r\n \"users\": \"Aktif Kullanıcı\",\r\n \"uptime\": \"Çalışma Süresi\",\r\n \"support\": \"Destek\"\r\n}\r\n"
32
32
  }
33
33
  ],
34
34
  "exports": {
@@ -22,13 +22,13 @@
22
22
  "path": "hero/lang/en.json",
23
23
  "type": "registry:lang",
24
24
  "target": "$modules$/hero/lang/en.json",
25
- "content": "{\r\n \"discover\": \"Discover\",\r\n \"amazing\": \"Inspiring\",\r\n \"content\": \"Stories\",\r\n \"subtitle\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ask Promake to replace this content with your actual blog description.\",\r\n \"readLatest\": \"Start Reading\",\r\n \"exploreTopics\": \"Explore Topics\"\r\n}\r\n"
25
+ "content": "{\r\n \"discover\": \"Discover\",\r\n \"amazing\": \"Inspiring\",\r\n \"content\": \"Stories\",\r\n \"subtitle\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Let Promake personalize this with your actual blog description.\",\r\n \"readLatest\": \"Start Reading\",\r\n \"exploreTopics\": \"Explore Topics\"\r\n}\r\n"
26
26
  },
27
27
  {
28
28
  "path": "hero/lang/tr.json",
29
29
  "type": "registry:lang",
30
30
  "target": "$modules$/hero/lang/tr.json",
31
- "content": "{\r\n \"discover\": \"Keşfedin\",\r\n \"amazing\": \"İlham Verici\",\r\n \"content\": \"Hikayeler\",\r\n \"subtitle\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore. AI bu içeriği gerçek blog açıklamanızla değiştirecektir.\",\r\n \"readLatest\": \"Okumaya Başlayın\",\r\n \"exploreTopics\": \"Konuları Keşfedin\"\r\n}\r\n"
31
+ "content": "{\r\n \"discover\": \"Keşfedin\",\r\n \"amazing\": \"İlham Verici\",\r\n \"content\": \"Hikayeler\",\r\n \"subtitle\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore. Promake ile gerçek blog açıklamanızla kişiselleştirin.\",\r\n \"readLatest\": \"Okumaya Başlayın\",\r\n \"exploreTopics\": \"Konuları Keşfedin\"\r\n}\r\n"
32
32
  }
33
33
  ],
34
34
  "exports": {
@@ -33,13 +33,16 @@
33
33
  "faq-categorized",
34
34
  "faq-simple",
35
35
  "favorites-blog-block",
36
+ "favorites-blog-page",
36
37
  "favorites-ecommerce-block",
38
+ "favorites-ecommerce-page",
37
39
  "feature-section",
38
40
  "featured-products",
39
41
  "footer",
40
42
  "footer-detailed",
41
43
  "footer-minimal",
42
44
  "forgot-password-page",
45
+ "forgot-password-page-split",
43
46
  "google-adsense",
44
47
  "google-map",
45
48
  "header-centered-pill",
@@ -59,14 +62,17 @@
59
62
  "login-page-split",
60
63
  "logo-cloud",
61
64
  "masonry-grid",
65
+ "my-orders-page",
62
66
  "newsletter-section",
63
67
  "order-card-compact",
68
+ "order-confirmation-page",
64
69
  "order-detail-block",
65
70
  "orders-list-block",
66
71
  "payment-success-block",
67
72
  "portfolio-page",
68
73
  "post-card",
69
74
  "post-detail-block",
75
+ "post-detail-page",
70
76
  "pricing-card",
71
77
  "pricing-page",
72
78
  "pricing-section",
@@ -75,13 +81,16 @@
75
81
  "product-card-detailed",
76
82
  "product-card-hover",
77
83
  "product-detail-block",
84
+ "product-detail-page",
78
85
  "product-detail-section",
79
86
  "product-quick-view",
80
87
  "products-page",
81
88
  "reading-progress",
82
89
  "register-page",
90
+ "register-page-split",
83
91
  "related-posts-block",
84
92
  "related-products-block",
93
+ "reset-password-page-split",
85
94
  "service-card",
86
95
  "share-buttons",
87
96
  "skill-card",
@@ -28,7 +28,7 @@
28
28
  "path": "landing-page-app/lang/en.json",
29
29
  "type": "registry:lang",
30
30
  "target": "$modules$/landing-page-app/lang/en.json",
31
- "content": "{\r\n \"title\": \"Mobile App\",\r\n \"badge\": \"Available on iOS & Android\",\r\n \"heroTitle\": \"Your App Name Here\",\r\n \"heroDescription\": \"Ask Promake to customize this description based on your app. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"downloadOn\": \"Download on the\",\r\n \"getItOn\": \"Get it on\",\r\n \"downloads\": \"Downloads\",\r\n \"inCategory\": \"In Category\",\r\n \"taskComplete\": \"Task Complete!\",\r\n \"newMessage\": \"New Message\",\r\n \"featuresLabel\": \"Features\",\r\n \"featuresTitle\": \"Why You'll Love It\",\r\n \"feature1Title\": \"Feature One\",\r\n \"feature1Desc\": \"Ask Promake to customize this feature description.\",\r\n \"feature2Title\": \"Feature Two\",\r\n \"feature2Desc\": \"Ask Promake to customize this feature description.\",\r\n \"feature3Title\": \"Feature Three\",\r\n \"feature3Desc\": \"Ask Promake to customize this feature description.\",\r\n \"feature4Title\": \"Feature Four\",\r\n \"feature4Desc\": \"Ask Promake to customize this feature description.\",\r\n \"screenshotsLabel\": \"Screenshots\",\r\n \"screenshotsTitle\": \"See It in Action\",\r\n \"reviewsLabel\": \"Reviews\",\r\n \"reviewsTitle\": \"What Users Say\",\r\n \"review1\": \"Ask Promake to add real user reviews here. Lorem ipsum dolor sit amet.\",\r\n \"review2\": \"Ask Promake to add real user reviews here. Consectetur adipiscing elit.\",\r\n \"review3\": \"Ask Promake to add real user reviews here. Sed do eiusmod tempor.\",\r\n \"ctaTitle\": \"Download Now\",\r\n \"ctaDescription\": \"Ask Promake to customize this CTA description. Available for free on iOS and Android.\"\r\n}\r\n"
31
+ "content": "{\r\n \"title\": \"Mobile App\",\r\n \"badge\": \"Available on iOS & Android\",\r\n \"heroTitle\": \"Your App Name Here\",\r\n \"heroDescription\": \"Let Promake tailor this description to your app. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"downloadOn\": \"Download on the\",\r\n \"getItOn\": \"Get it on\",\r\n \"downloads\": \"Downloads\",\r\n \"inCategory\": \"In Category\",\r\n \"taskComplete\": \"Task Complete!\",\r\n \"newMessage\": \"New Message\",\r\n \"featuresLabel\": \"Features\",\r\n \"featuresTitle\": \"Why You'll Love It\",\r\n \"feature1Title\": \"Feature One\",\r\n \"feature1Desc\": \"Customize this feature description with Promake.\",\r\n \"feature2Title\": \"Feature Two\",\r\n \"feature2Desc\": \"Use Promake to personalize this feature.\",\r\n \"feature3Title\": \"Feature Three\",\r\n \"feature3Desc\": \"Work with Promake to update this feature.\",\r\n \"feature4Title\": \"Feature Four\",\r\n \"feature4Desc\": \"Have Promake adjust this feature description.\",\r\n \"screenshotsLabel\": \"Screenshots\",\r\n \"screenshotsTitle\": \"See It in Action\",\r\n \"reviewsLabel\": \"Reviews\",\r\n \"reviewsTitle\": \"What Users Say\",\r\n \"review1\": \"Add real user reviews here with Promake. Lorem ipsum dolor sit amet.\",\r\n \"review2\": \"Let Promake help add user reviews. Consectetur adipiscing elit.\",\r\n \"review3\": \"Edit this via Promake with actual reviews. Sed do eiusmod tempor.\",\r\n \"ctaTitle\": \"Download Now\",\r\n \"ctaDescription\": \"Update this CTA description using Promake. Available for free on iOS and Android.\"\r\n}\r\n"
32
32
  },
33
33
  {
34
34
  "path": "landing-page-app/lang/tr.json",
@@ -28,7 +28,7 @@
28
28
  "path": "landing-page-saas/lang/en.json",
29
29
  "type": "registry:lang",
30
30
  "target": "$modules$/landing-page-saas/lang/en.json",
31
- "content": "{\r\n \"title\": \"SaaS Landing\",\r\n \"badge\": \"New Feature Available\",\r\n \"heroTitle\": \"Your Product Name Here\",\r\n \"heroDescription\": \"Ask Promake to customize this description based on your product. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"startFree\": \"Start Free Trial\",\r\n \"watchDemo\": \"Watch Demo\",\r\n \"noCreditCard\": \"No credit card required\",\r\n \"stat1Label\": \"Active Users\",\r\n \"stat2Label\": \"Uptime\",\r\n \"stat3Label\": \"Tasks Done\",\r\n \"stat4Label\": \"Rating\",\r\n \"featuresLabel\": \"Features\",\r\n \"featuresTitle\": \"Everything You Need\",\r\n \"featuresDescription\": \"Ask Promake to customize this features description. Sed do eiusmod tempor incididunt ut labore.\",\r\n \"feature1Title\": \"Feature One\",\r\n \"feature1Desc\": \"Ask Promake to customize this feature description.\",\r\n \"feature2Title\": \"Feature Two\",\r\n \"feature2Desc\": \"Ask Promake to customize this feature description.\",\r\n \"feature3Title\": \"Feature Three\",\r\n \"feature3Desc\": \"Ask Promake to customize this feature description.\",\r\n \"feature4Title\": \"Feature Four\",\r\n \"feature4Desc\": \"Ask Promake to customize this feature description.\",\r\n \"feature5Title\": \"Feature Five\",\r\n \"feature5Desc\": \"Ask Promake to customize this feature description.\",\r\n \"feature6Title\": \"Feature Six\",\r\n \"feature6Desc\": \"Ask Promake to customize this feature description.\",\r\n \"testimonialsLabel\": \"Testimonials\",\r\n \"testimonialsTitle\": \"What Our Customers Say\",\r\n \"testimonial1\": \"Ask Promake to add real customer testimonials here. Lorem ipsum dolor sit amet.\",\r\n \"testimonial2\": \"Ask Promake to add real customer testimonials here. Consectetur adipiscing elit.\",\r\n \"testimonial3\": \"Ask Promake to add real customer testimonials here. Sed do eiusmod tempor.\",\r\n \"pricingLabel\": \"Pricing\",\r\n \"pricingTitle\": \"Simple, Transparent Pricing\",\r\n \"starterPlan\": \"Starter\",\r\n \"proPlan\": \"Pro\",\r\n \"enterprisePlan\": \"Enterprise\",\r\n \"custom\": \"Custom\",\r\n \"mostPopular\": \"Most Popular\",\r\n \"getStarted\": \"Get Started\",\r\n \"viewAllPlans\": \"View all pricing plans\",\r\n \"ctaTitle\": \"Ready to Get Started?\",\r\n \"ctaDescription\": \"Ask Promake to customize this CTA description based on your goals. Lorem ipsum dolor sit amet.\",\r\n \"ctaButton\": \"Get Started\"\r\n}\r\n"
31
+ "content": "{\r\n \"title\": \"SaaS Landing\",\r\n \"badge\": \"New Feature Available\",\r\n \"heroTitle\": \"Your Product Name Here\",\r\n \"heroDescription\": \"Customize this description with Promake for your product. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"startFree\": \"Start Free Trial\",\r\n \"watchDemo\": \"Watch Demo\",\r\n \"noCreditCard\": \"No credit card required\",\r\n \"stat1Label\": \"Active Users\",\r\n \"stat2Label\": \"Uptime\",\r\n \"stat3Label\": \"Tasks Done\",\r\n \"stat4Label\": \"Rating\",\r\n \"featuresLabel\": \"Features\",\r\n \"featuresTitle\": \"Everything You Need\",\r\n \"featuresDescription\": \"Let Promake personalize this features section. Sed do eiusmod tempor incididunt ut labore.\",\r\n \"feature1Title\": \"Feature One\",\r\n \"feature1Desc\": \"Update this feature description using Promake.\",\r\n \"feature2Title\": \"Feature Two\",\r\n \"feature2Desc\": \"Work with Promake to tailor this feature.\",\r\n \"feature3Title\": \"Feature Three\",\r\n \"feature3Desc\": \"Promake can help customize this feature.\",\r\n \"feature4Title\": \"Feature Four\",\r\n \"feature4Desc\": \"Have Promake adjust this feature description.\",\r\n \"feature5Title\": \"Feature Five\",\r\n \"feature5Desc\": \"Edit this via Promake for your product.\",\r\n \"feature6Title\": \"Feature Six\",\r\n \"feature6Desc\": \"Use Promake to personalize this feature.\",\r\n \"testimonialsLabel\": \"Testimonials\",\r\n \"testimonialsTitle\": \"What Our Customers Say\",\r\n \"testimonial1\": \"Add real customer testimonials with Promake. Lorem ipsum dolor sit amet.\",\r\n \"testimonial2\": \"Let Promake help add testimonials here. Consectetur adipiscing elit.\",\r\n \"testimonial3\": \"Customize this testimonial with Promake. Sed do eiusmod tempor.\",\r\n \"pricingLabel\": \"Pricing\",\r\n \"pricingTitle\": \"Simple, Transparent Pricing\",\r\n \"starterPlan\": \"Starter\",\r\n \"proPlan\": \"Pro\",\r\n \"enterprisePlan\": \"Enterprise\",\r\n \"custom\": \"Custom\",\r\n \"mostPopular\": \"Most Popular\",\r\n \"getStarted\": \"Get Started\",\r\n \"viewAllPlans\": \"View all pricing plans\",\r\n \"ctaTitle\": \"Ready to Get Started?\",\r\n \"ctaDescription\": \"Promake can help tailor this CTA to your goals. Lorem ipsum dolor sit amet.\",\r\n \"ctaButton\": \"Get Started\"\r\n}\r\n"
32
32
  },
33
33
  {
34
34
  "path": "landing-page-saas/lang/tr.json",
@@ -26,7 +26,7 @@
26
26
  "path": "login-page-split/login-page-split.tsx",
27
27
  "type": "registry:page",
28
28
  "target": "$modules$/login-page-split/login-page-split.tsx",
29
- "content": "import { useState } from \"react\";\r\nimport { Link, useNavigate, useLocation } from \"react-router\";\r\nimport { toast } from \"sonner\";\r\nimport { Logo } from \"@/components/Logo\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { Checkbox } from \"@/components/ui/checkbox\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { useAuthStore } from \"@/modules/auth-core/auth-store\";\r\nimport { customerClient } from \"@/modules/api/customer-client\";\r\nimport { getErrorMessage } from \"@/modules/api/get-error-message\";\r\n\r\ninterface LoginPageSplitProps {\r\n image?: string;\r\n}\r\n\r\nexport function LoginPageSplit({\r\n image = \"/images/placeholder.png\",\r\n}: LoginPageSplitProps) {\r\n const { t } = useTranslation(\"login-page-split\");\r\n const navigate = useNavigate();\r\n const location = useLocation();\r\n const setAuth = useAuthStore((state) => state.setAuth);\r\n\r\n const [email, setEmail] = useState(\"\");\r\n const [password, setPassword] = useState(\"\");\r\n const [rememberMe, setRememberMe] = useState(false);\r\n const [isLoading, setIsLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n // Get redirect URL from location state or default to home\r\n const from = (location.state as { from?: string })?.from || \"/\";\r\n\r\n const handleSubmit = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setError(null);\r\n setIsLoading(true);\r\n\r\n try {\r\n const response = await customerClient.auth.login({\r\n username: email,\r\n password,\r\n expiresInMins: rememberMe ? 60 * 24 * 7 : undefined, // 7 days if remember me\r\n });\r\n\r\n // Set auth state\r\n setAuth(\r\n { username: email, email: (response as any).email || email },\r\n {\r\n accessToken: response.accessToken,\r\n refreshToken: response.refreshToken,\r\n idToken: response.idToken,\r\n encryptionKey: response.encryptionKey,\r\n expiresAt: response.expiresIn\r\n ? Date.now() + response.expiresIn * 1000\r\n : undefined,\r\n }\r\n );\r\n\r\n // Set token for API client\r\n customerClient.setToken(response.accessToken);\r\n\r\n toast.success(t(\"loginSuccess\", \"Login successful!\"));\r\n navigate(from, { replace: true });\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(\r\n err,\r\n t(\"loginError\", \"Login failed. Please check your credentials.\")\r\n );\r\n setError(errorMessage);\r\n } finally {\r\n setIsLoading(false);\r\n }\r\n };\r\n\r\n return (\r\n <section className=\"w-full md:grid md:min-h-screen md:grid-cols-2\">\r\n <div className=\"flex items-center justify-center px-4 py-12\">\r\n <div className=\"mx-auto grid w-full max-w-sm gap-6\">\r\n <Logo />\r\n <hr />\r\n <div>\r\n <h1 className=\"text-xl font-bold tracking-tight\">\r\n {t(\"title\", \"Login\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"subtitle\", \"Enter your details below to login\")}\r\n </p>\r\n </div>\r\n\r\n {error && (\r\n <div className=\"p-3 text-sm text-red-600 bg-red-50 dark:bg-red-950 dark:text-red-400 rounded-md\">\r\n {error}\r\n </div>\r\n )}\r\n\r\n <form onSubmit={handleSubmit} className=\"grid gap-4\">\r\n <div className=\"grid gap-2\">\r\n <Label htmlFor=\"email-split\">{t(\"email\", \"Email\")}</Label>\r\n <Input\r\n required\r\n id=\"email-split\"\r\n type=\"email\"\r\n autoComplete=\"username\"\r\n placeholder={t(\"emailPlaceholder\", \"email@example.com\")}\r\n value={email}\r\n onChange={(e) => setEmail(e.target.value)}\r\n disabled={isLoading}\r\n />\r\n </div>\r\n <div className=\"grid gap-2\">\r\n <Label htmlFor=\"password-split\">{t(\"password\", \"Password\")}</Label>\r\n <Input\r\n required\r\n id=\"password-split\"\r\n type=\"password\"\r\n placeholder=\"••••••••••\"\r\n autoComplete=\"current-password\"\r\n value={password}\r\n onChange={(e) => setPassword(e.target.value)}\r\n disabled={isLoading}\r\n />\r\n </div>\r\n\r\n <div className=\"flex items-center gap-2\">\r\n <Checkbox\r\n id=\"remember-me\"\r\n checked={rememberMe}\r\n onCheckedChange={(checked) => setRememberMe(checked as boolean)}\r\n disabled={isLoading}\r\n />\r\n <Label htmlFor=\"remember-me\" className=\"text-sm font-normal cursor-pointer\">\r\n {t(\"rememberMe\", \"Remember me\")}\r\n </Label>\r\n </div>\r\n\r\n <Button type=\"submit\" className=\"w-full\" disabled={isLoading}>\r\n {isLoading ? (\r\n <>\r\n <div className=\"w-4 h-4 border-2 border-primary-foreground border-t-transparent rounded-full animate-spin mr-2\" />\r\n {t(\"loggingIn\", \"Logging in...\")}\r\n </>\r\n ) : (\r\n t(\"login\", \"Login\")\r\n )}\r\n </Button>\r\n </form>\r\n\r\n <div className=\"flex flex-col gap-4 text-sm\">\r\n <p>\r\n {t(\"noAccount\", \"Don't have an account?\")}{\" \"}\r\n <Link to=\"/register\" className=\"underline\">\r\n {t(\"signUp\", \"Sign up\")}\r\n </Link>\r\n </p>\r\n <Link to=\"/forgot-password\" className=\"underline\">\r\n {t(\"forgotPassword\", \"Forgot your password?\")}\r\n </Link>\r\n </div>\r\n <hr />\r\n <p className=\"text-sm text-muted-foreground\">\r\n © {new Date().getFullYear()} {t(\"copyright\", \"All rights reserved.\")}\r\n </p>\r\n </div>\r\n </div>\r\n <div className=\"hidden p-4 md:block\">\r\n <img\r\n loading=\"lazy\"\r\n decoding=\"async\"\r\n width=\"1920\"\r\n height=\"1080\"\r\n alt={t(\"imageAlt\", \"Login background\")}\r\n src={image}\r\n className=\"size-full rounded-lg border bg-muted object-cover object-center\"\r\n />\r\n </div>\r\n </section>\r\n );\r\n}\r\n\r\nexport default LoginPageSplit;\r\n"
29
+ "content": "import { useState } from \"react\";\r\nimport { Link, useNavigate, useLocation } from \"react-router\";\r\nimport { toast } from \"sonner\";\r\nimport { Logo } from \"@/components/Logo\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { useAuthStore } from \"@/modules/auth-core/auth-store\";\r\nimport { customerClient } from \"@/modules/api/customer-client\";\r\nimport { getErrorMessage } from \"@/modules/api/get-error-message\";\r\n\r\ninterface LoginPageSplitProps {\r\n image?: string;\r\n}\r\n\r\nexport function LoginPageSplit({\r\n image = \"/images/placeholder.png\",\r\n}: LoginPageSplitProps) {\r\n const { t } = useTranslation(\"login-page-split\");\r\n const navigate = useNavigate();\r\n const location = useLocation();\r\n const setAuth = useAuthStore((state) => state.setAuth);\r\n\r\n const [email, setEmail] = useState(\"\");\r\n const [password, setPassword] = useState(\"\");\r\n const [isLoading, setIsLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n // Get redirect URL from location state or default to home\r\n const from = (location.state as { from?: string })?.from || \"/\";\r\n\r\n const handleSubmit = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setError(null);\r\n setIsLoading(true);\r\n\r\n try {\r\n const response = await customerClient.auth.login({\r\n username: email,\r\n password,\r\n });\r\n\r\n // Set auth state\r\n setAuth(\r\n { username: email, email: (response as any).email || email },\r\n {\r\n accessToken: response.accessToken,\r\n refreshToken: response.refreshToken,\r\n idToken: response.idToken,\r\n encryptionKey: response.encryptionKey,\r\n expiresAt: response.expiresIn\r\n ? Date.now() + response.expiresIn * 1000\r\n : undefined,\r\n }\r\n );\r\n\r\n // Set token for API client\r\n customerClient.setToken(response.accessToken);\r\n\r\n toast.success(t(\"loginSuccess\", \"Login successful!\"));\r\n navigate(from, { replace: true });\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(\r\n err,\r\n t(\"loginError\", \"Login failed. Please check your credentials.\")\r\n );\r\n setError(errorMessage);\r\n } finally {\r\n setIsLoading(false);\r\n }\r\n };\r\n\r\n return (\r\n <section className=\"w-full md:grid md:min-h-screen md:grid-cols-2\">\r\n <div className=\"flex items-center justify-center px-4 py-12\">\r\n <div className=\"mx-auto grid w-full max-w-sm gap-6\">\r\n <Logo />\r\n <hr />\r\n <div>\r\n <h1 className=\"text-xl font-bold tracking-tight\">\r\n {t(\"title\", \"Login\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"subtitle\", \"Enter your details below to login\")}\r\n </p>\r\n </div>\r\n\r\n {error && (\r\n <div className=\"p-3 text-sm text-red-600 bg-red-50 dark:bg-red-950 dark:text-red-400 rounded-md\">\r\n {error}\r\n </div>\r\n )}\r\n\r\n <form onSubmit={handleSubmit} className=\"grid gap-4\">\r\n <div className=\"grid gap-2\">\r\n <Label htmlFor=\"email-split\">{t(\"email\", \"Email\")}</Label>\r\n <Input\r\n required\r\n id=\"email-split\"\r\n type=\"email\"\r\n autoComplete=\"username\"\r\n placeholder={t(\"emailPlaceholder\", \"email@example.com\")}\r\n value={email}\r\n onChange={(e) => setEmail(e.target.value)}\r\n disabled={isLoading}\r\n />\r\n </div>\r\n <div className=\"grid gap-2\">\r\n <Label htmlFor=\"password-split\">{t(\"password\", \"Password\")}</Label>\r\n <Input\r\n required\r\n id=\"password-split\"\r\n type=\"password\"\r\n placeholder=\"••••••••••\"\r\n autoComplete=\"current-password\"\r\n value={password}\r\n onChange={(e) => setPassword(e.target.value)}\r\n disabled={isLoading}\r\n />\r\n </div>\r\n\r\n <Button type=\"submit\" className=\"w-full\" disabled={isLoading}>\r\n {isLoading ? (\r\n <>\r\n <div className=\"w-4 h-4 border-2 border-primary-foreground border-t-transparent rounded-full animate-spin mr-2\" />\r\n {t(\"loggingIn\", \"Logging in...\")}\r\n </>\r\n ) : (\r\n t(\"login\", \"Login\")\r\n )}\r\n </Button>\r\n </form>\r\n\r\n <div className=\"flex flex-col gap-4 text-sm\">\r\n <p>\r\n {t(\"noAccount\", \"Don't have an account?\")}{\" \"}\r\n <Link to=\"/register\" className=\"underline\">\r\n {t(\"signUp\", \"Sign up\")}\r\n </Link>\r\n </p>\r\n <Link to=\"/forgot-password\" className=\"underline\">\r\n {t(\"forgotPassword\", \"Forgot your password?\")}\r\n </Link>\r\n </div>\r\n <hr />\r\n <p className=\"text-sm text-muted-foreground\">\r\n © {new Date().getFullYear()} {t(\"copyright\", \"All rights reserved.\")}\r\n </p>\r\n </div>\r\n </div>\r\n <div className=\"hidden p-4 md:block\">\r\n <img\r\n loading=\"lazy\"\r\n decoding=\"async\"\r\n width=\"1920\"\r\n height=\"1080\"\r\n alt={t(\"imageAlt\", \"Login background\")}\r\n src={image}\r\n className=\"size-full rounded-lg border bg-muted object-cover object-center\"\r\n />\r\n </div>\r\n </section>\r\n );\r\n}\r\n\r\nexport default LoginPageSplit;\r\n"
30
30
  },
31
31
  {
32
32
  "path": "login-page-split/lang/en.json",