@promakeai/cli 0.0.5 → 0.1.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 (161) hide show
  1. package/dist/index.js +214 -135
  2. package/dist/registry/about-page.json +5 -3
  3. package/dist/registry/about-section.json +2 -2
  4. package/dist/registry/announcement-bar.json +43 -0
  5. package/dist/registry/api.json +55 -0
  6. package/dist/registry/auth-core.json +43 -0
  7. package/dist/registry/auth.json +70 -0
  8. package/dist/registry/bento-grid-section.json +1 -1
  9. package/dist/registry/blog-list-page.json +3 -2
  10. package/dist/registry/blog-section.json +2 -2
  11. package/dist/registry/cart-drawer.json +1 -1
  12. package/dist/registry/cart-page.json +5 -4
  13. package/dist/registry/case-study-page.json +48 -0
  14. package/dist/registry/category-section.json +1 -1
  15. package/dist/registry/checkout-page.json +7 -5
  16. package/dist/registry/coming-soon-page-minimal.json +45 -0
  17. package/dist/registry/coming-soon-page.json +47 -0
  18. package/dist/registry/contact-info-grid.json +2 -2
  19. package/dist/registry/contact-page-centered.json +2 -2
  20. package/dist/registry/contact-page-map-overlay.json +4 -3
  21. package/dist/registry/contact-page-map-split.json +4 -3
  22. package/dist/registry/contact-page-split.json +3 -3
  23. package/dist/registry/contact-page.json +5 -3
  24. package/dist/registry/cookie-consent.json +43 -0
  25. package/dist/registry/cookies-page.json +4 -2
  26. package/dist/registry/cta-section.json +2 -2
  27. package/dist/registry/db.json +129 -0
  28. package/dist/registry/docs/about-page.md +5 -0
  29. package/dist/registry/docs/announcement-bar.md +38 -0
  30. package/dist/registry/docs/auth-core.md +64 -0
  31. package/dist/registry/docs/blog-list-page.md +1 -0
  32. package/dist/registry/docs/cart-page.md +1 -0
  33. package/dist/registry/docs/case-study-page.md +39 -0
  34. package/dist/registry/docs/checkout-page.md +3 -1
  35. package/dist/registry/docs/coming-soon-page-minimal.md +32 -0
  36. package/dist/registry/docs/coming-soon-page.md +37 -0
  37. package/dist/registry/docs/contact-page-map-overlay.md +2 -2
  38. package/dist/registry/docs/contact-page-map-split.md +2 -2
  39. package/dist/registry/docs/contact-page.md +5 -0
  40. package/dist/registry/docs/cookie-consent.md +37 -0
  41. package/dist/registry/docs/cookies-page.md +5 -0
  42. package/dist/registry/docs/ecommerce-core.md +4 -3
  43. package/dist/registry/docs/forgot-password-page-split.md +45 -0
  44. package/dist/registry/docs/forgot-password-page.md +46 -0
  45. package/dist/registry/docs/header-ecommerce.md +2 -0
  46. package/dist/registry/docs/hero-carousel.md +37 -0
  47. package/dist/registry/docs/landing-page-app.md +43 -0
  48. package/dist/registry/docs/landing-page-saas.md +41 -0
  49. package/dist/registry/docs/login-page-split.md +13 -4
  50. package/dist/registry/docs/login-page.md +17 -4
  51. package/dist/registry/docs/logo-cloud.md +33 -0
  52. package/dist/registry/docs/masonry-grid.md +37 -0
  53. package/dist/registry/docs/my-orders-page.md +44 -0
  54. package/dist/registry/docs/order-confirmation-page.md +41 -0
  55. package/dist/registry/docs/portfolio-page.md +38 -0
  56. package/dist/registry/docs/pricing-page.md +38 -0
  57. package/dist/registry/docs/privacy-page.md +5 -0
  58. package/dist/registry/docs/product-quick-view.md +37 -0
  59. package/dist/registry/docs/products-page.md +1 -0
  60. package/dist/registry/docs/reading-progress.md +31 -0
  61. package/dist/registry/docs/register-page-split.md +45 -0
  62. package/dist/registry/docs/register-page.md +46 -0
  63. package/dist/registry/docs/reset-password-page-split.md +45 -0
  64. package/dist/registry/docs/reset-password-page.md +36 -0
  65. package/dist/registry/docs/share-buttons.md +37 -0
  66. package/dist/registry/docs/team-page.md +38 -0
  67. package/dist/registry/docs/terms-page.md +5 -0
  68. package/dist/registry/docs/timeline-section.md +37 -0
  69. package/dist/registry/docs/video-hero.md +41 -0
  70. package/dist/registry/ecommerce-core.json +18 -2
  71. package/dist/registry/empty-page.json +1 -1
  72. package/dist/registry/faq-categorized.json +2 -2
  73. package/dist/registry/faq-simple.json +2 -2
  74. package/dist/registry/favorites-blog-block.json +1 -1
  75. package/dist/registry/favorites-ecommerce-block.json +1 -1
  76. package/dist/registry/feature-section.json +2 -2
  77. package/dist/registry/featured-products.json +1 -1
  78. package/dist/registry/footer-detailed.json +1 -1
  79. package/dist/registry/footer-minimal.json +3 -3
  80. package/dist/registry/footer.json +2 -2
  81. package/dist/registry/forgot-password-page-split.json +50 -0
  82. package/dist/registry/forgot-password-page.json +51 -0
  83. package/dist/registry/header-ecommerce.json +4 -2
  84. package/dist/registry/header-mega.json +2 -2
  85. package/dist/registry/header-minimal.json +1 -1
  86. package/dist/registry/header-simple.json +1 -1
  87. package/dist/registry/hero-carousel.json +45 -0
  88. package/dist/registry/hero-cta.json +2 -2
  89. package/dist/registry/hero-gradient.json +2 -2
  90. package/dist/registry/hero-profile.json +1 -1
  91. package/dist/registry/hero.json +2 -2
  92. package/dist/registry/index.json +24 -1
  93. package/dist/registry/landing-page-app.json +47 -0
  94. package/dist/registry/landing-page-saas.json +47 -0
  95. package/dist/registry/login-page-split.json +11 -7
  96. package/dist/registry/login-page.json +4 -4
  97. package/dist/registry/logo-cloud.json +41 -0
  98. package/dist/registry/masonry-grid.json +43 -0
  99. package/dist/registry/my-orders-page.json +52 -0
  100. package/dist/registry/order-confirmation-page.json +49 -0
  101. package/dist/registry/orders-list-block.json +1 -1
  102. package/dist/registry/payment-success-block.json +1 -1
  103. package/dist/registry/portfolio-page.json +45 -0
  104. package/dist/registry/post-detail-block.json +1 -1
  105. package/dist/registry/pricing-page.json +47 -0
  106. package/dist/registry/pricing-section.json +2 -2
  107. package/dist/registry/privacy-page.json +4 -2
  108. package/dist/registry/product-detail-block.json +1 -1
  109. package/dist/registry/product-quick-view.json +46 -0
  110. package/dist/registry/products-page.json +5 -4
  111. package/dist/registry/reading-progress.json +43 -0
  112. package/dist/registry/register-page-split.json +50 -0
  113. package/dist/registry/register-page.json +51 -0
  114. package/dist/registry/related-posts-block.json +1 -1
  115. package/dist/registry/reset-password-page-split.json +50 -0
  116. package/dist/registry/reset-password-page.json +39 -0
  117. package/dist/registry/share-buttons.json +46 -0
  118. package/dist/registry/team-page.json +47 -0
  119. package/dist/registry/terms-page.json +4 -2
  120. package/dist/registry/testimonials-carousel.json +2 -2
  121. package/dist/registry/testimonials-grid.json +2 -2
  122. package/dist/registry/timeline-section.json +43 -0
  123. package/dist/registry/video-hero.json +42 -0
  124. package/package.json +1 -1
  125. package/template/index.html +5 -5
  126. package/template/src/App.tsx +7 -24
  127. package/template/src/components/GoogleAnalytics.tsx +34 -0
  128. package/template/src/components/Layout.tsx +1 -5
  129. package/template/src/components/ScriptInjector.tsx +62 -0
  130. package/template/src/constants/constants.json +8 -2
  131. package/template/src/index.css +1 -0
  132. package/template/src/lang/en/index.json +1 -28
  133. package/template/src/lang/tr/index.json +1 -28
  134. package/template/src/pages/Index.tsx +1 -98
  135. package/template/src/components/Footer.tsx +0 -100
  136. package/template/src/components/Header.tsx +0 -79
  137. package/template/src/components/Hero.tsx +0 -69
  138. package/template/src/modules/api/USAGE.md +0 -515
  139. package/template/src/modules/api/customer-client.ts +0 -20
  140. package/template/src/modules/api/get-error-message.ts +0 -18
  141. package/template/src/modules/api/validation/en.json +0 -29
  142. package/template/src/modules/api/validation/tr.json +0 -29
  143. package/template/src/modules/auth/USAGE.md +0 -248
  144. package/template/src/modules/auth/auth-header-menu.tsx +0 -123
  145. package/template/src/modules/auth/auth-store.ts +0 -57
  146. package/template/src/modules/auth/forgot-password-page.tsx +0 -371
  147. package/template/src/modules/auth/login-page.tsx +0 -183
  148. package/template/src/modules/auth/register-page.tsx +0 -252
  149. package/template/src/modules/auth/use-auth.ts +0 -273
  150. package/template/src/modules/db/adapters/IDataAdapter.ts +0 -26
  151. package/template/src/modules/db/adapters/SqliteAdapter.ts +0 -364
  152. package/template/src/modules/db/adapters/index.ts +0 -2
  153. package/template/src/modules/db/config.ts +0 -59
  154. package/template/src/modules/db/core/DataManager.ts +0 -125
  155. package/template/src/modules/db/core/types.ts +0 -101
  156. package/template/src/modules/db/index.ts +0 -42
  157. package/template/src/modules/db/react/QueryProvider.tsx +0 -16
  158. package/template/src/modules/db/react/index.ts +0 -23
  159. package/template/src/modules/db/react/queryClient.ts +0 -64
  160. package/template/src/modules/db/react/useRepository.ts +0 -400
  161. package/template/src/modules/db/utils/parsers.ts +0 -96
@@ -16,13 +16,13 @@
16
16
  "path": "feature-section/feature-section.tsx",
17
17
  "type": "registry:component",
18
18
  "target": "$modules$/feature-section/feature-section.tsx",
19
- "content": "import { Link } from \"react-router\";\nimport { Button } from \"@/components/ui/button\";\nimport { useTranslation } from \"react-i18next\";\n\nexport function FeatureSection() {\n const { t } = useTranslation(\"feature-section\");\n\n return (\n <section className=\"py-20 bg-muted/30\">\n <div className=\"container mx-auto px-4\">\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-12 items-center\">\n <div>\n <h2 className=\"text-3xl lg:text-4xl font-bold mb-6 text-foreground\">\n {t(\"heading\", \"Your Business Title Here\")}\n </h2>\n <p className=\"text-lg text-muted-foreground mb-6\">\n {t(\n \"description\",\n \"This is where your main business description will appear. AI will customize this content based on your specific industry and services.\"\n )}\n </p>\n <div className=\"space-y-4 mb-8\">\n <div className=\"flex items-center gap-3\">\n <div className=\"w-2 h-2 bg-primary rounded-full\"></div>\n <span className=\"text-foreground\">\n {t(\"feature1\", \"Key feature or benefit #1\")}\n </span>\n </div>\n <div className=\"flex items-center gap-3\">\n <div className=\"w-2 h-2 bg-primary rounded-full\"></div>\n <span className=\"text-foreground\">\n {t(\"feature2\", \"Key feature or benefit #2\")}\n </span>\n </div>\n <div className=\"flex items-center gap-3\">\n <div className=\"w-2 h-2 bg-primary rounded-full\"></div>\n <span className=\"text-foreground\">\n {t(\"feature3\", \"Key feature or benefit #3\")}\n </span>\n </div>\n </div>\n <div className=\"flex gap-4\">\n <Button asChild>\n <Link to=\"/about\">{t(\"primaryButton\", \"Learn More\")}</Link>\n </Button>\n <Button variant=\"outline\" asChild>\n <Link to=\"/contact\">{t(\"secondaryButton\", \"Get Started\")}</Link>\n </Button>\n </div>\n </div>\n <div>\n <div className=\"aspect-square bg-gradient-to-br from-primary/10 to-primary/5 rounded-2xl overflow-hidden\">\n <img\n src=\"/images/placeholder.png\"\n alt={t(\"imageAlt\", \"Business Solutions\")}\n className=\"w-full h-full object-cover\"\n />\n </div>\n </div>\n </div>\n </div>\n </section>\n );\n}\n"
19
+ "content": "import { Link } from \"react-router\";\nimport { Button } from \"@/components/ui/button\";\nimport { useTranslation } from \"react-i18next\";\n\nexport function FeatureSection() {\n const { t } = useTranslation(\"feature-section\");\n\n return (\n <section className=\"py-20 bg-muted/30\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-12 items-center\">\n <div>\n <h2 className=\"text-3xl lg:text-4xl font-bold mb-6 text-foreground\">\n {t(\"heading\", \"Your Business Title Here\")}\n </h2>\n <p className=\"text-lg text-muted-foreground mb-6\">\n {t(\n \"description\",\n \"This is where your main business description will appear. Ask Promake to customize this content based on your specific industry and services.\"\n )}\n </p>\n <div className=\"space-y-4 mb-8\">\n <div className=\"flex items-center gap-3\">\n <div className=\"w-2 h-2 bg-primary rounded-full\"></div>\n <span className=\"text-foreground\">\n {t(\"feature1\", \"Key feature or benefit #1\")}\n </span>\n </div>\n <div className=\"flex items-center gap-3\">\n <div className=\"w-2 h-2 bg-primary rounded-full\"></div>\n <span className=\"text-foreground\">\n {t(\"feature2\", \"Key feature or benefit #2\")}\n </span>\n </div>\n <div className=\"flex items-center gap-3\">\n <div className=\"w-2 h-2 bg-primary rounded-full\"></div>\n <span className=\"text-foreground\">\n {t(\"feature3\", \"Key feature or benefit #3\")}\n </span>\n </div>\n </div>\n <div className=\"flex gap-4\">\n <Button asChild>\n <Link to=\"/about\">{t(\"primaryButton\", \"Learn More\")}</Link>\n </Button>\n <Button variant=\"outline\" asChild>\n <Link to=\"/contact\">{t(\"secondaryButton\", \"Get Started\")}</Link>\n </Button>\n </div>\n </div>\n <div>\n <div className=\"aspect-square bg-gradient-to-br from-primary/10 to-primary/5 rounded-2xl overflow-hidden\">\n <img\n src=\"/images/placeholder.png\"\n alt={t(\"imageAlt\", \"Business Solutions\")}\n className=\"w-full h-full object-cover\"\n />\n </div>\n </div>\n </div>\n </div>\n </section>\n );\n}\n"
20
20
  },
21
21
  {
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. AI will 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. 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"
26
26
  },
27
27
  {
28
28
  "path": "feature-section/lang/tr.json",
@@ -19,7 +19,7 @@
19
19
  "path": "featured-products/featured-products.tsx",
20
20
  "type": "registry:component",
21
21
  "target": "$modules$/featured-products/featured-products.tsx",
22
- "content": "import { Link } from \"react-router\";\nimport { ArrowRight } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { ProductCard } from \"@/modules/product-card/product-card\";\nimport { useTranslation } from \"react-i18next\";\nimport type { Product } from \"@/modules/ecommerce-core/types\";\n\ninterface FeaturedProductsProps {\n products: Product[];\n loading?: boolean;\n}\n\nexport function FeaturedProducts({\n products,\n loading = false,\n}: FeaturedProductsProps) {\n const { t } = useTranslation(\"featured-products\");\n\n return (\n <section className=\"py-8 sm:py-12 md:py-16 lg:py-20 bg-background border-t border-border/20 relative\">\n <div className=\"absolute top-0 left-1/2 transform -translate-x-1/2 w-16 sm:w-24 h-px bg-primary/30\"></div>\n <div className=\"container mx-auto px-3 sm:px-4 lg:px-8\">\n <div className=\"text-center mb-6 sm:mb-8 md:mb-12 lg:mb-16 px-2\">\n <h2 className=\"text-xl sm:text-2xl md:text-3xl lg:text-4xl xl:text-5xl font-bold mb-2 sm:mb-3 md:mb-4 bg-gradient-to-r from-primary to-primary/80 bg-clip-text text-transparent leading-normal pb-1\">\n {t('title', 'Featured Products')}\n </h2>\n <div className=\"w-12 sm:w-16 md:w-20 h-1 bg-gradient-to-r from-primary/50 to-primary/20 mx-auto mb-3 sm:mb-4 md:mb-6 rounded-full\"></div>\n <p className=\"text-xs sm:text-sm md:text-base lg:text-lg xl:text-xl text-muted-foreground max-w-2xl mx-auto leading-relaxed\">\n {t('subtitle', 'Hand-picked favorites from our collection')}\n </p>\n </div>\n\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6 lg:gap-8 xl:gap-10\">\n {loading ? (\n [...Array(3)].map((_, i) => (\n <div key={i} className=\"animate-pulse group\">\n <div className=\"aspect-square bg-gradient-to-br from-muted to-muted/50 rounded-2xl mb-6\"></div>\n <div className=\"space-y-3\">\n <div className=\"h-6 bg-muted rounded-lg w-3/4\"></div>\n <div className=\"h-4 bg-muted rounded w-1/2\"></div>\n <div className=\"h-5 bg-muted rounded w-2/3\"></div>\n </div>\n </div>\n ))\n ) : (\n products.map((product) => (\n <ProductCard\n key={product.id}\n product={product}\n variant=\"featured\"\n />\n ))\n )}\n </div>\n\n <div className=\"text-center mt-8 sm:mt-12 lg:mt-16\">\n <Button size=\"lg\" asChild className=\"px-6 sm:px-8 py-3 sm:py-4 text-base sm:text-lg\">\n <Link to=\"/products\">\n {t('viewAll', 'View All Products')}\n <ArrowRight className=\"w-4 h-4 sm:w-5 sm:h-5 ml-2\" />\n </Link>\n </Button>\n </div>\n </div>\n </section>\n );\n}\n"
22
+ "content": "import { Link } from \"react-router\";\nimport { ArrowRight } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { ProductCard } from \"@/modules/product-card/product-card\";\nimport { useTranslation } from \"react-i18next\";\nimport { useFeaturedProducts } from \"@/modules/ecommerce-core\";\nimport type { Product } from \"@/modules/ecommerce-core/types\";\n\ninterface FeaturedProductsProps {\n products?: Product[];\n loading?: boolean;\n}\n\nexport function FeaturedProducts({\n products: propProducts,\n loading: propLoading,\n}: FeaturedProductsProps) {\n const { t } = useTranslation(\"featured-products\");\n const { products: hookProducts, loading: hookLoading } = useFeaturedProducts();\n\n const products = propProducts ?? hookProducts;\n const loading = propLoading ?? hookLoading;\n\n return (\n <section className=\"py-8 sm:py-12 md:py-16 lg:py-20 bg-background border-t border-border/20 relative\">\n <div className=\"absolute top-0 left-1/2 transform -translate-x-1/2 w-16 sm:w-24 h-px bg-primary/30\"></div>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-3 sm:px-4 lg:px-8\">\n <div className=\"text-center mb-6 sm:mb-8 md:mb-12 lg:mb-16 px-2\">\n <h2 className=\"text-xl sm:text-2xl md:text-3xl lg:text-4xl xl:text-5xl font-bold mb-2 sm:mb-3 md:mb-4 bg-gradient-to-r from-primary to-primary/80 bg-clip-text text-transparent leading-normal pb-1\">\n {t('title', 'Featured Products')}\n </h2>\n <div className=\"w-12 sm:w-16 md:w-20 h-1 bg-gradient-to-r from-primary/50 to-primary/20 mx-auto mb-3 sm:mb-4 md:mb-6 rounded-full\"></div>\n <p className=\"text-xs sm:text-sm md:text-base lg:text-lg xl:text-xl text-muted-foreground max-w-2xl mx-auto leading-relaxed\">\n {t('subtitle', 'Hand-picked favorites from our collection')}\n </p>\n </div>\n\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6 lg:gap-8 xl:gap-10\">\n {loading ? (\n [...Array(3)].map((_, i) => (\n <div key={i} className=\"animate-pulse group\">\n <div className=\"aspect-square bg-gradient-to-br from-muted to-muted/50 rounded-2xl mb-6\"></div>\n <div className=\"space-y-3\">\n <div className=\"h-6 bg-muted rounded-lg w-3/4\"></div>\n <div className=\"h-4 bg-muted rounded w-1/2\"></div>\n <div className=\"h-5 bg-muted rounded w-2/3\"></div>\n </div>\n </div>\n ))\n ) : (\n products.map((product) => (\n <ProductCard\n key={product.id}\n product={product}\n variant=\"featured\"\n />\n ))\n )}\n </div>\n\n <div className=\"text-center mt-8 sm:mt-12 lg:mt-16\">\n <Button size=\"lg\" asChild className=\"px-6 sm:px-8 py-3 sm:py-4 text-base sm:text-lg\">\n <Link to=\"/products\">\n {t('viewAll', 'View All Products')}\n <ArrowRight className=\"w-4 h-4 sm:w-5 sm:h-5 ml-2\" />\n </Link>\n </Button>\n </div>\n </div>\n </section>\n );\n}\n"
23
23
  },
24
24
  {
25
25
  "path": "featured-products/lang/en.json",
@@ -19,7 +19,7 @@
19
19
  "path": "footer-detailed/footer-detailed.tsx",
20
20
  "type": "registry:component",
21
21
  "target": "$modules$/footer-detailed/footer-detailed.tsx",
22
- "content": "import { useState, useMemo } from \"react\";\r\nimport { Link } from \"react-router\";\r\nimport {\r\n Facebook,\r\n Twitter,\r\n Instagram,\r\n Linkedin,\r\n Youtube,\r\n Mail,\r\n Phone,\r\n ArrowUp,\r\n Send,\r\n CreditCard,\r\n} from \"lucide-react\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Logo } from \"@/components/Logo\";\r\nimport constants from \"@/constants/constants.json\";\r\n\r\nconst socialIcons: Record<string, React.ElementType> = {\r\n facebook: Facebook,\r\n twitter: Twitter,\r\n instagram: Instagram,\r\n linkedin: Linkedin,\r\n youtube: Youtube,\r\n};\r\n\r\ninterface FooterDetailedProps {\r\n className?: string;\r\n}\r\n\r\nexport function FooterDetailed({ className }: FooterDetailedProps) {\r\n const { t } = useTranslation(\"footer-detailed\");\r\n const [email, setEmail] = useState(\"\");\r\n\r\n const socialLinks = useMemo(() => {\r\n const socialMedia = constants.socialMedia as Record<string, string> | undefined;\r\n if (!socialMedia) return [];\r\n return Object.entries(socialMedia)\r\n .filter(([platform, url]) => url && socialIcons[platform])\r\n .map(([platform, url]) => ({ platform, url, Icon: socialIcons[platform] }));\r\n }, []);\r\n\r\n const handleNewsletterSubmit = (e: React.FormEvent) => {\r\n e.preventDefault();\r\n // Newsletter subscription logic\r\n console.log(\"Newsletter subscription:\", email);\r\n setEmail(\"\");\r\n };\r\n\r\n const scrollToTop = () => {\r\n window.scrollTo({ top: 0, behavior: \"smooth\" });\r\n };\r\n\r\n const currentYear = new Date().getFullYear();\r\n\r\n const linkSections = [\r\n {\r\n title: t(\"shop\", \"Shop\"),\r\n links: [\r\n { text: t(\"allProducts\", \"All Products\"), url: \"/products\" },\r\n { text: t(\"featured\", \"Featured\"), url: \"/products?featured=true\" },\r\n { text: t(\"newArrivals\", \"New Arrivals\"), url: \"/products?new=true\" },\r\n { text: t(\"onSale\", \"On Sale\"), url: \"/products?sale=true\" },\r\n ],\r\n },\r\n {\r\n title: t(\"company\", \"Company\"),\r\n links: [\r\n { text: t(\"about\", \"About Us\"), url: \"/about\" },\r\n { text: t(\"careers\", \"Careers\"), url: \"/careers\" },\r\n { text: t(\"blog\", \"Blog\"), url: \"/blog\" },\r\n { text: t(\"press\", \"Press\"), url: \"/press\" },\r\n ],\r\n },\r\n {\r\n title: t(\"support\", \"Support\"),\r\n links: [\r\n { text: t(\"helpCenter\", \"Help Center\"), url: \"/help\" },\r\n { text: t(\"faq\", \"FAQ\"), url: \"/faq\" },\r\n { text: t(\"contact\", \"Contact Us\"), url: \"/contact\" },\r\n { text: t(\"returns\", \"Returns\"), url: \"/returns\" },\r\n ],\r\n },\r\n {\r\n title: t(\"legal\", \"Legal\"),\r\n links: [\r\n { text: t(\"privacy\", \"Privacy Policy\"), url: \"/privacy\" },\r\n { text: t(\"terms\", \"Terms of Service\"), url: \"/terms\" },\r\n { text: t(\"cookies\", \"Cookie Policy\"), url: \"/cookies\" },\r\n { text: t(\"refund\", \"Refund Policy\"), url: \"/refund\" },\r\n ],\r\n },\r\n ];\r\n\r\n return (\r\n <footer className={cn(\"border-t bg-muted/30\", className)}>\r\n {/* Main Footer */}\r\n <div className=\"container mx-auto px-4 py-12 lg:py-16\">\r\n <div className=\"grid grid-cols-1 gap-10 sm:grid-cols-2 lg:grid-cols-6\">\r\n {/* Brand & Newsletter - spans 2 columns */}\r\n <div className=\"sm:col-span-2 space-y-6\">\r\n <Logo size=\"lg\" />\r\n <p className=\"text-sm text-muted-foreground leading-relaxed max-w-sm\">\r\n {t(\"description\", \"Your trusted destination for quality products. We deliver excellence with every order.\")}\r\n </p>\r\n\r\n {/* Newsletter */}\r\n <div className=\"space-y-3\">\r\n <h4 className=\"font-semibold text-sm\">\r\n {t(\"newsletter\", \"Subscribe to our newsletter\")}\r\n </h4>\r\n <form onSubmit={handleNewsletterSubmit} className=\"flex gap-2\">\r\n <Input\r\n type=\"email\"\r\n placeholder={t(\"emailPlaceholder\", \"Enter your email\")}\r\n value={email}\r\n onChange={(e) => setEmail(e.target.value)}\r\n className=\"flex-1 h-10\"\r\n required\r\n />\r\n <Button type=\"submit\" size=\"sm\" className=\"h-10 px-4\">\r\n <Send className=\"h-4 w-4\" />\r\n </Button>\r\n </form>\r\n <p className=\"text-xs text-muted-foreground\">\r\n {t(\"newsletterNote\", \"Get updates on new products and exclusive offers.\")}\r\n </p>\r\n </div>\r\n\r\n {/* Social Links */}\r\n {socialLinks.length > 0 && (\r\n <div className=\"flex gap-1 pt-2\">\r\n {socialLinks.map(({ platform, url, Icon }) => (\r\n <a\r\n key={platform}\r\n href={url}\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n className=\"h-10 w-10 flex items-center justify-center rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted transition-colors\"\r\n >\r\n <Icon className=\"h-5 w-5\" />\r\n </a>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Link Sections */}\r\n {linkSections.map((section, idx) => (\r\n <div key={idx} className=\"space-y-4\">\r\n <h4 className=\"font-semibold\">{section.title}</h4>\r\n <ul className=\"space-y-2.5\">\r\n {section.links.map((link, linkIdx) => (\r\n <li key={linkIdx}>\r\n <Link\r\n to={link.url}\r\n className=\"text-sm text-muted-foreground hover:text-foreground transition-colors\"\r\n >\r\n {link.text}\r\n </Link>\r\n </li>\r\n ))}\r\n </ul>\r\n </div>\r\n ))}\r\n </div>\r\n </div>\r\n\r\n {/* Bottom Bar */}\r\n <div className=\"border-t bg-muted/50\">\r\n <div className=\"container mx-auto px-4 py-6\">\r\n <div className=\"flex flex-col lg:flex-row justify-between items-center gap-6\">\r\n {/* Copyright & Contact */}\r\n <div className=\"flex flex-col sm:flex-row items-center gap-4 sm:gap-6 text-sm text-muted-foreground\">\r\n <span>© {currentYear} {constants.site.name}. {t(\"allRightsReserved\", \"All rights reserved.\")}</span>\r\n <div className=\"hidden sm:block w-px h-4 bg-border\" />\r\n <div className=\"flex items-center gap-4\">\r\n <a href={`tel:${constants.phone}`} className=\"flex items-center gap-1.5 hover:text-foreground transition-colors\">\r\n <Phone className=\"h-3.5 w-3.5\" />\r\n <span>{constants.phone}</span>\r\n </a>\r\n <a href={`mailto:${constants.email}`} className=\"flex items-center gap-1.5 hover:text-foreground transition-colors\">\r\n <Mail className=\"h-3.5 w-3.5\" />\r\n <span>{constants.email}</span>\r\n </a>\r\n </div>\r\n </div>\r\n\r\n {/* Payment & Back to Top */}\r\n <div className=\"flex items-center gap-6\">\r\n {/* Payment Methods */}\r\n <div className=\"flex items-center gap-2 text-muted-foreground\">\r\n <CreditCard className=\"h-5 w-5\" />\r\n <span className=\"text-xs\">{t(\"securePayment\", \"Secure Payment\")}</span>\r\n </div>\r\n\r\n {/* Back to Top */}\r\n <Button\r\n variant=\"outline\"\r\n size=\"sm\"\r\n onClick={scrollToTop}\r\n className=\"gap-2\"\r\n >\r\n <ArrowUp className=\"h-4 w-4\" />\r\n {t(\"backToTop\", \"Top\")}\r\n </Button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </footer>\r\n );\r\n}\r\n"
22
+ "content": "import { useState, useMemo } from \"react\";\r\nimport { Link } from \"react-router\";\r\nimport {\r\n Facebook,\r\n Twitter,\r\n Instagram,\r\n Linkedin,\r\n Youtube,\r\n Mail,\r\n Phone,\r\n ArrowUp,\r\n Send,\r\n CreditCard,\r\n} from \"lucide-react\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Logo } from \"@/components/Logo\";\r\nimport constants from \"@/constants/constants.json\";\r\n\r\nconst socialIcons: Record<string, React.ElementType> = {\r\n facebook: Facebook,\r\n twitter: Twitter,\r\n instagram: Instagram,\r\n linkedin: Linkedin,\r\n youtube: Youtube,\r\n};\r\n\r\ninterface FooterDetailedProps {\r\n className?: string;\r\n}\r\n\r\nexport function FooterDetailed({ className }: FooterDetailedProps) {\r\n const { t } = useTranslation(\"footer-detailed\");\r\n const [email, setEmail] = useState(\"\");\r\n\r\n const socialLinks = useMemo(() => {\r\n const socialMedia = constants.socialMedia as Record<string, string> | undefined;\r\n if (!socialMedia) return [];\r\n return Object.entries(socialMedia)\r\n .filter(([platform, url]) => url && socialIcons[platform])\r\n .map(([platform, url]) => ({ platform, url, Icon: socialIcons[platform] }));\r\n }, []);\r\n\r\n const handleNewsletterSubmit = (e: React.FormEvent) => {\r\n e.preventDefault();\r\n // Newsletter subscription logic\r\n console.log(\"Newsletter subscription:\", email);\r\n setEmail(\"\");\r\n };\r\n\r\n const scrollToTop = () => {\r\n window.scrollTo({ top: 0, behavior: \"smooth\" });\r\n };\r\n\r\n const currentYear = new Date().getFullYear();\r\n\r\n const linkSections = [\r\n {\r\n title: t(\"shop\", \"Shop\"),\r\n links: [\r\n { text: t(\"allProducts\", \"All Products\"), url: \"/products\" },\r\n { text: t(\"featured\", \"Featured\"), url: \"/products?featured=true\" },\r\n { text: t(\"newArrivals\", \"New Arrivals\"), url: \"/products?new=true\" },\r\n { text: t(\"onSale\", \"On Sale\"), url: \"/products?sale=true\" },\r\n ],\r\n },\r\n {\r\n title: t(\"company\", \"Company\"),\r\n links: [\r\n { text: t(\"about\", \"About Us\"), url: \"/about\" },\r\n { text: t(\"careers\", \"Careers\"), url: \"/careers\" },\r\n { text: t(\"blog\", \"Blog\"), url: \"/blog\" },\r\n { text: t(\"press\", \"Press\"), url: \"/press\" },\r\n ],\r\n },\r\n {\r\n title: t(\"support\", \"Support\"),\r\n links: [\r\n { text: t(\"helpCenter\", \"Help Center\"), url: \"/help\" },\r\n { text: t(\"faq\", \"FAQ\"), url: \"/faq\" },\r\n { text: t(\"contact\", \"Contact Us\"), url: \"/contact\" },\r\n { text: t(\"returns\", \"Returns\"), url: \"/returns\" },\r\n ],\r\n },\r\n {\r\n title: t(\"legal\", \"Legal\"),\r\n links: [\r\n { text: t(\"privacy\", \"Privacy Policy\"), url: \"/privacy\" },\r\n { text: t(\"terms\", \"Terms of Service\"), url: \"/terms\" },\r\n { text: t(\"cookies\", \"Cookie Policy\"), url: \"/cookies\" },\r\n { text: t(\"refund\", \"Refund Policy\"), url: \"/refund\" },\r\n ],\r\n },\r\n ];\r\n\r\n return (\r\n <footer className={cn(\"border-t bg-muted/30\", className)}>\r\n {/* Main Footer */}\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-12 lg:py-16\">\r\n <div className=\"grid grid-cols-1 gap-10 sm:grid-cols-2 lg:grid-cols-6\">\r\n {/* Brand & Newsletter - spans 2 columns */}\r\n <div className=\"sm:col-span-2 space-y-6\">\r\n <Logo size=\"lg\" />\r\n <p className=\"text-sm text-muted-foreground leading-relaxed max-w-sm\">\r\n {t(\"description\", \"Your trusted destination for quality products. We deliver excellence with every order.\")}\r\n </p>\r\n\r\n {/* Newsletter */}\r\n <div className=\"space-y-3\">\r\n <h4 className=\"font-semibold text-sm\">\r\n {t(\"newsletter\", \"Subscribe to our newsletter\")}\r\n </h4>\r\n <form onSubmit={handleNewsletterSubmit} className=\"flex gap-2\">\r\n <Input\r\n type=\"email\"\r\n placeholder={t(\"emailPlaceholder\", \"Enter your email\")}\r\n value={email}\r\n onChange={(e) => setEmail(e.target.value)}\r\n className=\"flex-1 h-10\"\r\n required\r\n />\r\n <Button type=\"submit\" size=\"sm\" className=\"h-10 px-4\">\r\n <Send className=\"h-4 w-4\" />\r\n </Button>\r\n </form>\r\n <p className=\"text-xs text-muted-foreground\">\r\n {t(\"newsletterNote\", \"Get updates on new products and exclusive offers.\")}\r\n </p>\r\n </div>\r\n\r\n {/* Social Links */}\r\n {socialLinks.length > 0 && (\r\n <div className=\"flex gap-1 pt-2\">\r\n {socialLinks.map(({ platform, url, Icon }) => (\r\n <a\r\n key={platform}\r\n href={url}\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n className=\"h-10 w-10 flex items-center justify-center rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted transition-colors\"\r\n >\r\n <Icon className=\"h-5 w-5\" />\r\n </a>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Link Sections */}\r\n {linkSections.map((section, idx) => (\r\n <div key={idx} className=\"space-y-4\">\r\n <h4 className=\"font-semibold\">{section.title}</h4>\r\n <ul className=\"space-y-2.5\">\r\n {section.links.map((link, linkIdx) => (\r\n <li key={linkIdx}>\r\n <Link\r\n to={link.url}\r\n className=\"text-sm text-muted-foreground hover:text-foreground transition-colors\"\r\n >\r\n {link.text}\r\n </Link>\r\n </li>\r\n ))}\r\n </ul>\r\n </div>\r\n ))}\r\n </div>\r\n </div>\r\n\r\n {/* Bottom Bar */}\r\n <div className=\"border-t bg-muted/50\">\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-6\">\r\n <div className=\"flex flex-col lg:flex-row justify-between items-center gap-6\">\r\n {/* Copyright & Contact */}\r\n <div className=\"flex flex-col sm:flex-row items-center gap-4 sm:gap-6 text-sm text-muted-foreground\">\r\n <span>© {currentYear} {constants.site.name}. {t(\"allRightsReserved\", \"All rights reserved.\")}</span>\r\n <div className=\"hidden sm:block w-px h-4 bg-border\" />\r\n <div className=\"flex items-center gap-4\">\r\n <a href={`tel:${constants.phone}`} className=\"flex items-center gap-1.5 hover:text-foreground transition-colors\">\r\n <Phone className=\"h-3.5 w-3.5\" />\r\n <span>{constants.phone}</span>\r\n </a>\r\n <a href={`mailto:${constants.email}`} className=\"flex items-center gap-1.5 hover:text-foreground transition-colors\">\r\n <Mail className=\"h-3.5 w-3.5\" />\r\n <span>{constants.email}</span>\r\n </a>\r\n </div>\r\n </div>\r\n\r\n {/* Payment & Back to Top */}\r\n <div className=\"flex items-center gap-6\">\r\n {/* Payment Methods */}\r\n <div className=\"flex items-center gap-2 text-muted-foreground\">\r\n <CreditCard className=\"h-5 w-5\" />\r\n <span className=\"text-xs\">{t(\"securePayment\", \"Secure Payment\")}</span>\r\n </div>\r\n\r\n {/* Back to Top */}\r\n <Button\r\n variant=\"outline\"\r\n size=\"sm\"\r\n onClick={scrollToTop}\r\n className=\"gap-2\"\r\n >\r\n <ArrowUp className=\"h-4 w-4\" />\r\n {t(\"backToTop\", \"Top\")}\r\n </Button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </footer>\r\n );\r\n}\r\n"
23
23
  },
24
24
  {
25
25
  "path": "footer-detailed/lang/en.json",
@@ -16,19 +16,19 @@
16
16
  "path": "footer-minimal/footer-minimal.tsx",
17
17
  "type": "registry:component",
18
18
  "target": "$modules$/footer-minimal/footer-minimal.tsx",
19
- "content": "import { useMemo } from \"react\";\nimport { Link } from \"react-router\";\nimport { Facebook, Twitter, Instagram, Linkedin } from \"lucide-react\";\nimport constants from \"@/constants/constants.json\";\nimport { useTranslation } from \"react-i18next\";\n\nconst socialIcons: Record<string, React.ElementType> = {\n facebook: Facebook,\n twitter: Twitter,\n instagram: Instagram,\n linkedin: Linkedin,\n};\n\nexport function FooterMinimal() {\n const { t } = useTranslation(\"footer-minimal\");\n\n const socialLinks = useMemo(() => {\n const socialMedia = constants.socialMedia as Record<string, string> | undefined;\n if (!socialMedia) return [];\n return Object.entries(socialMedia)\n .filter(([platform, url]) => url && socialIcons[platform])\n .map(([platform, url]) => ({ platform, url, Icon: socialIcons[platform] }));\n }, []);\n\n const currentYear = new Date().getFullYear();\n\n return (\n <footer className=\"border-t bg-muted/20\">\n <div className=\"container mx-auto px-4 py-6\">\n <div className=\"flex flex-col md:flex-row items-center justify-between gap-4\">\n {/* Copyright */}\n <p className=\"text-sm text-muted-foreground\">\n © {currentYear} {constants.site.name}\n </p>\n\n {/* Legal Links */}\n <div className=\"flex items-center gap-6\">\n <Link to=\"/privacy\" className=\"text-sm text-muted-foreground hover:text-foreground transition-colors\">\n {t(\"privacy\", \"Privacy\")}\n </Link>\n <Link to=\"/terms\" className=\"text-sm text-muted-foreground hover:text-foreground transition-colors\">\n {t(\"terms\", \"Terms\")}\n </Link>\n </div>\n\n {/* Social Links */}\n {socialLinks.length > 0 && (\n <div className=\"flex gap-1\">\n {socialLinks.map(({ platform, url, Icon }) => (\n <a\n key={platform}\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"h-8 w-8 flex items-center justify-center rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted transition-colors\"\n >\n <Icon className=\"h-4 w-4\" />\n </a>\n ))}\n </div>\n )}\n </div>\n </div>\n </footer>\n );\n}\n"
19
+ "content": "import { useMemo } from \"react\";\nimport { Link } from \"react-router\";\nimport { Facebook, Twitter, Instagram, Linkedin } from \"lucide-react\";\nimport constants from \"@/constants/constants.json\";\nimport { useTranslation } from \"react-i18next\";\n\nconst socialIcons: Record<string, React.ElementType> = {\n facebook: Facebook,\n twitter: Twitter,\n instagram: Instagram,\n linkedin: Linkedin,\n};\n\nexport function FooterMinimal() {\n const { t } = useTranslation(\"footer-minimal\");\n\n const socialLinks = useMemo(() => {\n const socialMedia = constants.socialMedia as Record<string, string> | undefined;\n if (!socialMedia) return [];\n return Object.entries(socialMedia)\n .filter(([platform, url]) => url && socialIcons[platform])\n .map(([platform, url]) => ({ platform, url, Icon: socialIcons[platform] }));\n }, []);\n\n const currentYear = new Date().getFullYear();\n\n return (\n <footer className=\"border-t bg-muted/20\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-6\">\n <div className=\"flex flex-col md:flex-row items-center justify-between gap-4\">\n {/* Copyright */}\n <p className=\"text-sm text-muted-foreground\">\n © {currentYear} {constants.site.name}. {t(\"allRightsReserved\", \"All rights reserved.\")}\n </p>\n\n {/* Legal Links */}\n <div className=\"flex items-center gap-6\">\n <Link to=\"/privacy\" className=\"text-sm text-muted-foreground hover:text-foreground transition-colors\">\n {t(\"privacy\", \"Privacy\")}\n </Link>\n <Link to=\"/terms\" className=\"text-sm text-muted-foreground hover:text-foreground transition-colors\">\n {t(\"terms\", \"Terms\")}\n </Link>\n </div>\n\n {/* Social Links */}\n {socialLinks.length > 0 && (\n <div className=\"flex gap-1\">\n {socialLinks.map(({ platform, url, Icon }) => (\n <a\n key={platform}\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"h-8 w-8 flex items-center justify-center rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted transition-colors\"\n >\n <Icon className=\"h-4 w-4\" />\n </a>\n ))}\n </div>\n )}\n </div>\n </div>\n </footer>\n );\n}\n"
20
20
  },
21
21
  {
22
22
  "path": "footer-minimal/lang/en.json",
23
23
  "type": "registry:lang",
24
24
  "target": "$modules$/footer-minimal/lang/en.json",
25
- "content": "{\r\n \"privacy\": \"Privacy\",\r\n \"terms\": \"Terms\"\r\n}\r\n"
25
+ "content": "{\r\n \"privacy\": \"Privacy\",\r\n \"terms\": \"Terms\",\r\n \"allRightsReserved\": \"All rights reserved.\"\r\n}\r\n"
26
26
  },
27
27
  {
28
28
  "path": "footer-minimal/lang/tr.json",
29
29
  "type": "registry:lang",
30
30
  "target": "$modules$/footer-minimal/lang/tr.json",
31
- "content": "{\r\n \"privacy\": \"Gizlilik\",\r\n \"terms\": \"Şartlar\"\r\n}\r\n"
31
+ "content": "{\r\n \"privacy\": \"Gizlilik\",\r\n \"terms\": \"Şartlar\",\r\n \"allRightsReserved\": \"Tüm hakları saklıdır.\"\r\n}\r\n"
32
32
  }
33
33
  ],
34
34
  "exports": {
@@ -16,13 +16,13 @@
16
16
  "path": "footer/footer.tsx",
17
17
  "type": "registry:component",
18
18
  "target": "$modules$/footer/footer.tsx",
19
- "content": "import { useMemo } from \"react\";\nimport { Link } from \"react-router\";\nimport { Mail, Phone, MapPin, Facebook, Twitter, Instagram, Linkedin } from \"lucide-react\";\nimport { Logo } from \"@/components/Logo\";\nimport constants from \"@/constants/constants.json\";\nimport { useTranslation } from \"react-i18next\";\n\nconst socialIcons: Record<string, React.ElementType> = {\n facebook: Facebook,\n twitter: Twitter,\n instagram: Instagram,\n linkedin: Linkedin,\n};\n\nexport function Footer() {\n const { t } = useTranslation(\"footer\");\n\n const socialLinks = useMemo(() => {\n const socialMedia = constants.socialMedia as Record<string, string> | undefined;\n if (!socialMedia) return [];\n return Object.entries(socialMedia)\n .filter(([platform, url]) => url && socialIcons[platform])\n .map(([platform, url]) => ({ platform, url, Icon: socialIcons[platform] }));\n }, []);\n\n const currentYear = new Date().getFullYear();\n\n return (\n <footer className=\"border-t bg-muted/20\">\n <div className=\"container mx-auto px-4 py-12 lg:py-16\">\n <div className=\"grid grid-cols-1 gap-10 md:grid-cols-2 lg:grid-cols-4\">\n {/* Brand */}\n <div className=\"space-y-4\">\n <Logo size=\"md\" />\n <p className=\"text-sm text-muted-foreground leading-relaxed\">\n {t(\"description\", \"Quality products and exceptional service for our valued customers.\")}\n </p>\n {socialLinks.length > 0 && (\n <div className=\"flex gap-1\">\n {socialLinks.map(({ platform, url, Icon }) => (\n <a\n key={platform}\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"h-9 w-9 flex items-center justify-center rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted transition-colors\"\n >\n <Icon className=\"h-4 w-4\" />\n </a>\n ))}\n </div>\n )}\n </div>\n\n {/* Quick Links */}\n <div className=\"space-y-4\">\n <h4 className=\"font-semibold\">{t(\"quickLinks\", \"Quick Links\")}</h4>\n <ul className=\"space-y-2.5\">\n <li>\n <Link to=\"/about\" className=\"text-sm text-muted-foreground hover:text-foreground transition-colors\">\n {t(\"about\", \"About\")}\n </Link>\n </li>\n <li>\n <Link to=\"/contact\" className=\"text-sm text-muted-foreground hover:text-foreground transition-colors\">\n {t(\"contact\", \"Contact\")}\n </Link>\n </li>\n </ul>\n </div>\n\n {/* Legal */}\n <div className=\"space-y-4\">\n <h4 className=\"font-semibold\">{t(\"legal\", \"Legal\")}</h4>\n <ul className=\"space-y-2.5\">\n <li>\n <Link to=\"/privacy\" className=\"text-sm text-muted-foreground hover:text-foreground transition-colors\">\n {t(\"privacy\", \"Privacy Policy\")}\n </Link>\n </li>\n <li>\n <Link to=\"/terms\" className=\"text-sm text-muted-foreground hover:text-foreground transition-colors\">\n {t(\"terms\", \"Terms of Service\")}\n </Link>\n </li>\n <li>\n <Link to=\"/cookies\" className=\"text-sm text-muted-foreground hover:text-foreground transition-colors\">\n {t(\"cookies\", \"Cookie Policy\")}\n </Link>\n </li>\n </ul>\n </div>\n\n {/* Contact */}\n <div className=\"space-y-4\">\n <h4 className=\"font-semibold\">{t(\"contactTitle\", \"Contact\")}</h4>\n <ul className=\"space-y-3\">\n <li className=\"flex items-center gap-3 text-sm text-muted-foreground\">\n <Phone className=\"h-4 w-4 shrink-0\" />\n <span>{constants.phone}</span>\n </li>\n <li className=\"flex items-center gap-3 text-sm text-muted-foreground\">\n <Mail className=\"h-4 w-4 shrink-0\" />\n <span>{constants.email}</span>\n </li>\n <li className=\"flex items-start gap-3 text-sm text-muted-foreground\">\n <MapPin className=\"h-4 w-4 shrink-0 mt-0.5\" />\n <span>\n {constants.address.city}, {constants.address.state}\n </span>\n </li>\n </ul>\n </div>\n </div>\n\n {/* Bottom */}\n <div className=\"mt-12 pt-6 border-t\">\n <p className=\"text-sm text-muted-foreground text-center\">\n © {currentYear} {constants.site.name}. {t(\"allRightsReserved\", \"All rights reserved.\")}\n </p>\n </div>\n </div>\n </footer>\n );\n}\n"
19
+ "content": "import { useMemo } from \"react\";\nimport { Link } from \"react-router\";\nimport { Mail, Phone, MapPin, Facebook, Twitter, Instagram, Linkedin } from \"lucide-react\";\nimport { Logo } from \"@/components/Logo\";\nimport constants from \"@/constants/constants.json\";\nimport { useTranslation } from \"react-i18next\";\n\nconst socialIcons: Record<string, React.ElementType> = {\n facebook: Facebook,\n twitter: Twitter,\n instagram: Instagram,\n linkedin: Linkedin,\n};\n\nexport function Footer() {\n const { t } = useTranslation(\"footer\");\n\n const socialLinks = useMemo(() => {\n const socialMedia = constants.socialMedia as Record<string, string> | undefined;\n if (!socialMedia) return [];\n return Object.entries(socialMedia)\n .filter(([platform, url]) => url && socialIcons[platform])\n .map(([platform, url]) => ({ platform, url, Icon: socialIcons[platform] }));\n }, []);\n\n const currentYear = new Date().getFullYear();\n\n return (\n <footer className=\"border-t bg-muted/20\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-12 lg:py-16\">\n <div className=\"grid grid-cols-1 gap-10 md:grid-cols-2 lg:grid-cols-4\">\n {/* Brand */}\n <div className=\"space-y-4\">\n <Logo size=\"md\" />\n <p className=\"text-sm text-muted-foreground leading-relaxed\">\n {t(\"description\", \"Quality products and exceptional service for our valued customers.\")}\n </p>\n {socialLinks.length > 0 && (\n <div className=\"flex gap-1\">\n {socialLinks.map(({ platform, url, Icon }) => (\n <a\n key={platform}\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"h-9 w-9 flex items-center justify-center rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted transition-colors\"\n >\n <Icon className=\"h-4 w-4\" />\n </a>\n ))}\n </div>\n )}\n </div>\n\n {/* Quick Links */}\n <div className=\"space-y-4\">\n <h4 className=\"font-semibold\">{t(\"quickLinks\", \"Quick Links\")}</h4>\n <ul className=\"space-y-2.5\">\n <li>\n <Link to=\"/about\" className=\"text-sm text-muted-foreground hover:text-foreground transition-colors\">\n {t(\"about\", \"About\")}\n </Link>\n </li>\n <li>\n <Link to=\"/contact\" className=\"text-sm text-muted-foreground hover:text-foreground transition-colors\">\n {t(\"contact\", \"Contact\")}\n </Link>\n </li>\n </ul>\n </div>\n\n {/* Legal */}\n <div className=\"space-y-4\">\n <h4 className=\"font-semibold\">{t(\"legal\", \"Legal\")}</h4>\n <ul className=\"space-y-2.5\">\n <li>\n <Link to=\"/privacy\" className=\"text-sm text-muted-foreground hover:text-foreground transition-colors\">\n {t(\"privacy\", \"Privacy Policy\")}\n </Link>\n </li>\n <li>\n <Link to=\"/terms\" className=\"text-sm text-muted-foreground hover:text-foreground transition-colors\">\n {t(\"terms\", \"Terms of Service\")}\n </Link>\n </li>\n <li>\n <Link to=\"/cookies\" className=\"text-sm text-muted-foreground hover:text-foreground transition-colors\">\n {t(\"cookies\", \"Cookie Policy\")}\n </Link>\n </li>\n </ul>\n </div>\n\n {/* Contact */}\n <div className=\"space-y-4\">\n <h4 className=\"font-semibold\">{t(\"contactTitle\", \"Contact\")}</h4>\n <ul className=\"space-y-3\">\n <li className=\"flex items-center gap-3 text-sm text-muted-foreground\">\n <Phone className=\"h-4 w-4 shrink-0\" />\n <span>{constants.phone}</span>\n </li>\n <li className=\"flex items-center gap-3 text-sm text-muted-foreground\">\n <Mail className=\"h-4 w-4 shrink-0\" />\n <span>{constants.email}</span>\n </li>\n <li className=\"flex items-start gap-3 text-sm text-muted-foreground\">\n <MapPin className=\"h-4 w-4 shrink-0 mt-0.5\" />\n <span>\n {constants.address.city}, {constants.address.state}\n </span>\n </li>\n </ul>\n </div>\n </div>\n\n {/* Bottom */}\n <div className=\"mt-12 pt-6 border-t\">\n <p className=\"text-sm text-muted-foreground text-center\">\n © {currentYear} {constants.site.name}. {t(\"allRightsReserved\", \"All rights reserved.\")}\n </p>\n </div>\n </div>\n </footer>\n );\n}\n"
20
20
  },
21
21
  {
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\": \"AI will 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\": \"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"
26
26
  },
27
27
  {
28
28
  "path": "footer/lang/tr.json",
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "forgot-password-page-split",
3
+ "type": "registry:page",
4
+ "title": "Forgot Password Page Split",
5
+ "description": "Split-screen password recovery page with form on the left and full-height image on the right. Features email form with success state after submission. Uses customerClient for API calls.",
6
+ "registryDependencies": [
7
+ "button",
8
+ "input",
9
+ "auth-core",
10
+ "api"
11
+ ],
12
+ "usage": "import ForgotPasswordPageSplit from '@/modules/forgot-password-page-split';\n\n<ForgotPasswordPageSplit\n image=\"/images/forgot-bg.jpg\"\n/>\n\n• Installed at: src/modules/forgot-password-page-split/\n• Customize text: src/modules/forgot-password-page-split/lang/*.json\n• API Integration:\n - customerClient.auth.forgotPassword() for sending reset email\n - Shows success state after email sent\n• Add to your router as a page component",
13
+ "route": {
14
+ "path": "/forgot-password",
15
+ "componentName": "ForgotPasswordPageSplit"
16
+ },
17
+ "files": [
18
+ {
19
+ "path": "forgot-password-page-split/index.ts",
20
+ "type": "registry:index",
21
+ "target": "$modules$/forgot-password-page-split/index.ts",
22
+ "content": "export * from './forgot-password-page-split';\r\nexport { default } from './forgot-password-page-split';\r\n"
23
+ },
24
+ {
25
+ "path": "forgot-password-page-split/forgot-password-page-split.tsx",
26
+ "type": "registry:page",
27
+ "target": "$modules$/forgot-password-page-split/forgot-password-page-split.tsx",
28
+ "content": "import { useState } from \"react\";\r\nimport { Link } from \"react-router\";\r\nimport { toast } from \"sonner\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { Logo } from \"@/components/Logo\";\r\nimport { Mail, ArrowLeft, CheckCircle } from \"lucide-react\";\r\nimport { customerClient } from \"@/modules/api/customer-client\";\r\nimport { getErrorMessage } from \"@/modules/api/get-error-message\";\r\n\r\ninterface ForgotPasswordPageSplitProps {\r\n image?: string;\r\n}\r\n\r\nexport function ForgotPasswordPageSplit({\r\n image = \"/images/placeholder.png\",\r\n}: ForgotPasswordPageSplitProps) {\r\n const { t } = useTranslation(\"forgot-password-page-split\");\r\n usePageTitle({ title: t(\"title\", \"Forgot Password\") });\r\n\r\n const [email, setEmail] = useState(\"\");\r\n const [isLoading, setIsLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const [isEmailSent, setIsEmailSent] = useState(false);\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 await customerClient.auth.forgotPassword({ username: email });\r\n\r\n setIsEmailSent(true);\r\n toast.success(t(\"emailSent\", \"Reset link sent!\"), {\r\n description: t(\"checkInbox\", \"Please check your email inbox.\"),\r\n });\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(\r\n err,\r\n t(\"forgotPasswordError\", \"Failed to send reset link. Please try again.\")\r\n );\r\n setError(errorMessage);\r\n } finally {\r\n setIsLoading(false);\r\n }\r\n };\r\n\r\n // Success state - email sent\r\n if (isEmailSent) {\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 text-center\">\r\n <Logo />\r\n <hr />\r\n\r\n <div className=\"flex justify-center\">\r\n <div className=\"w-16 h-16 bg-green-100 dark:bg-green-900/30 rounded-full flex items-center justify-center\">\r\n <CheckCircle className=\"w-8 h-8 text-green-600 dark:text-green-400\" />\r\n </div>\r\n </div>\r\n\r\n <div>\r\n <h1 className=\"text-xl font-bold tracking-tight\">\r\n {t(\"checkYourEmail\", \"Check your email\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground mt-2\">\r\n {t(\"emailSentTo\", \"We've sent a password reset link to\")}\r\n </p>\r\n <p className=\"text-sm font-medium mt-1\">{email}</p>\r\n </div>\r\n\r\n <div className=\"text-sm text-muted-foreground\">\r\n <p>{t(\"didntReceive\", \"Didn't receive the email?\")}</p>\r\n <Button\r\n variant=\"link\"\r\n className=\"p-0 h-auto\"\r\n onClick={() => {\r\n setIsEmailSent(false);\r\n setError(null);\r\n }}\r\n >\r\n {t(\"tryAgain\", \"Click here to try again\")}\r\n </Button>\r\n </div>\r\n\r\n <Link\r\n to=\"/login\"\r\n className=\"inline-flex items-center justify-center gap-2 text-sm text-muted-foreground hover:text-foreground\"\r\n >\r\n <ArrowLeft className=\"w-4 h-4\" />\r\n {t(\"backToLogin\", \"Back to login\")}\r\n </Link>\r\n\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\", \"Forgot password 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\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\r\n <div className=\"flex justify-center\">\r\n <div className=\"w-12 h-12 bg-primary/10 rounded-full flex items-center justify-center\">\r\n <Mail className=\"w-6 h-6 text-primary\" />\r\n </div>\r\n </div>\r\n\r\n <div className=\"text-center\">\r\n <h1 className=\"text-xl font-bold tracking-tight\">\r\n {t(\"title\", \"Forgot Password\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground mt-1\">\r\n {t(\"subtitle\", \"Enter your email to reset your password\")}\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\">{t(\"email\", \"Email\")}</Label>\r\n <Input\r\n required\r\n id=\"email\"\r\n type=\"email\"\r\n autoComplete=\"email\"\r\n placeholder={t(\"emailPlaceholder\", \"you@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\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(\"sending\", \"Sending...\")}\r\n </>\r\n ) : (\r\n t(\"sendLink\", \"Send Reset Link\")\r\n )}\r\n </Button>\r\n </form>\r\n\r\n <Link\r\n to=\"/login\"\r\n className=\"inline-flex items-center justify-center gap-2 text-sm text-muted-foreground hover:text-foreground\"\r\n >\r\n <ArrowLeft className=\"w-4 h-4\" />\r\n {t(\"backToLogin\", \"Back to login\")}\r\n </Link>\r\n\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\", \"Forgot password 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 ForgotPasswordPageSplit;\r\n"
29
+ },
30
+ {
31
+ "path": "forgot-password-page-split/lang/en.json",
32
+ "type": "registry:lang",
33
+ "target": "$modules$/forgot-password-page-split/lang/en.json",
34
+ "content": "{\r\n \"title\": \"Forgot Password\",\r\n \"subtitle\": \"Enter your email to reset your password\",\r\n \"email\": \"Email\",\r\n \"emailPlaceholder\": \"you@example.com\",\r\n \"sendLink\": \"Send Reset Link\",\r\n \"sending\": \"Sending...\",\r\n \"emailSent\": \"Reset link sent!\",\r\n \"checkInbox\": \"Please check your email inbox.\",\r\n \"forgotPasswordError\": \"Failed to send reset link. Please try again.\",\r\n \"checkYourEmail\": \"Check your email\",\r\n \"emailSentTo\": \"We've sent a password reset link to\",\r\n \"didntReceive\": \"Didn't receive the email?\",\r\n \"tryAgain\": \"Click here to try again\",\r\n \"backToLogin\": \"Back to login\",\r\n \"copyright\": \"All rights reserved.\",\r\n \"imageAlt\": \"Forgot password background\"\r\n}\r\n"
35
+ },
36
+ {
37
+ "path": "forgot-password-page-split/lang/tr.json",
38
+ "type": "registry:lang",
39
+ "target": "$modules$/forgot-password-page-split/lang/tr.json",
40
+ "content": "{\r\n \"title\": \"Şifremi Unuttum\",\r\n \"subtitle\": \"Şifrenizi sıfırlamak için e-postanızı girin\",\r\n \"email\": \"E-posta\",\r\n \"emailPlaceholder\": \"ornek@email.com\",\r\n \"sendLink\": \"Sıfırlama Linki Gönder\",\r\n \"sending\": \"Gönderiliyor...\",\r\n \"emailSent\": \"Sıfırlama linki gönderildi!\",\r\n \"checkInbox\": \"Lütfen e-posta gelen kutunuzu kontrol edin.\",\r\n \"forgotPasswordError\": \"Sıfırlama linki gönderilemedi. Lütfen tekrar deneyin.\",\r\n \"checkYourEmail\": \"E-postanızı kontrol edin\",\r\n \"emailSentTo\": \"Şifre sıfırlama linki gönderildi:\",\r\n \"didntReceive\": \"E-posta almadınız mı?\",\r\n \"tryAgain\": \"Tekrar denemek için tıklayın\",\r\n \"backToLogin\": \"Girişe dön\",\r\n \"copyright\": \"Tüm hakları saklıdır.\",\r\n \"imageAlt\": \"Şifre sıfırlama arka planı\"\r\n}\r\n"
41
+ }
42
+ ],
43
+ "exports": {
44
+ "types": [],
45
+ "variables": [
46
+ "ForgotPasswordPageSplit",
47
+ "default"
48
+ ]
49
+ }
50
+ }
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "forgot-password-page",
3
+ "type": "registry:page",
4
+ "title": "Forgot Password Page",
5
+ "description": "Centered card password recovery page with Layout wrapper. Two-step flow: request reset code then enter code with new password. Uses useAuth hook from auth-core for API calls.",
6
+ "registryDependencies": [
7
+ "button",
8
+ "input",
9
+ "card",
10
+ "auth-core",
11
+ "api"
12
+ ],
13
+ "usage": "import ForgotPasswordPage from '@/modules/forgot-password-page';\n\n<ForgotPasswordPage />\n\n• Installed at: src/modules/forgot-password-page/\n• Customize text: src/modules/forgot-password-page/lang/*.json\n• Uses useAuth() hook from auth-core:\n const { forgotPassword, resetPassword } = useAuth();\n await forgotPassword(username);\n await resetPassword(username, code, newPassword);\n• Two-step flow: request code -> enter code with new password\n• Add to your router as a page component",
14
+ "route": {
15
+ "path": "/forgot-password",
16
+ "componentName": "ForgotPasswordPage"
17
+ },
18
+ "files": [
19
+ {
20
+ "path": "forgot-password-page/index.ts",
21
+ "type": "registry:index",
22
+ "target": "$modules$/forgot-password-page/index.ts",
23
+ "content": "export * from './forgot-password-page';\r\nexport { default } from './forgot-password-page';\r\n"
24
+ },
25
+ {
26
+ "path": "forgot-password-page/forgot-password-page.tsx",
27
+ "type": "registry:page",
28
+ "target": "$modules$/forgot-password-page/forgot-password-page.tsx",
29
+ "content": "import { useState } from \"react\";\r\nimport { Link } from \"react-router\";\r\nimport { toast } from \"sonner\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { useAuth } from \"@/modules/auth-core/use-auth\";\r\nimport { getErrorMessage } from \"@/modules/api/get-error-message\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport {\r\n Card,\r\n CardContent,\r\n CardHeader,\r\n CardTitle,\r\n CardDescription,\r\n} from \"@/components/ui/card\";\r\nimport { KeyRound, ArrowLeft, Eye, EyeOff, CheckCircle2 } from \"lucide-react\";\r\n\r\ntype Step = \"request\" | \"reset\" | \"success\";\r\n\r\nexport function ForgotPasswordPage() {\r\n const { t } = useTranslation(\"forgot-password-page\");\r\n usePageTitle({ title: t(\"title\", \"Forgot Password\") });\r\n\r\n const { forgotPassword, resetPassword } = useAuth();\r\n\r\n const [step, setStep] = useState<Step>(\"request\");\r\n const [username, setUsername] = useState(\"\");\r\n const [code, setCode] = useState(\"\");\r\n const [newPassword, setNewPassword] = useState(\"\");\r\n const [confirmPassword, setConfirmPassword] = useState(\"\");\r\n const [showPassword, setShowPassword] = useState(false);\r\n const [isSubmitting, setIsSubmitting] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const handleRequestCode = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setIsSubmitting(true);\r\n setError(null);\r\n\r\n try {\r\n await forgotPassword(username);\r\n toast.success(t(\"codeSentTitle\", \"Code Sent!\"), {\r\n description: t(\r\n \"codeSentDesc\",\r\n \"A password reset code has been sent to your email.\",\r\n ),\r\n });\r\n setStep(\"reset\");\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(\r\n err,\r\n t(\"errorGeneric\", \"Failed to send reset code. Please try again.\"),\r\n );\r\n setError(errorMessage);\r\n toast.error(t(\"errorTitle\", \"Error\"), {\r\n description: errorMessage,\r\n });\r\n } finally {\r\n setIsSubmitting(false);\r\n }\r\n };\r\n\r\n const handleResetPassword = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setError(null);\r\n\r\n // Validate passwords match\r\n if (newPassword !== confirmPassword) {\r\n setError(t(\"passwordMismatch\", \"Passwords do not match\"));\r\n return;\r\n }\r\n\r\n setIsSubmitting(true);\r\n\r\n try {\r\n await resetPassword(username, code, newPassword);\r\n toast.success(t(\"resetSuccessTitle\", \"Password Reset!\"), {\r\n description: t(\r\n \"resetSuccessDesc\",\r\n \"Your password has been successfully reset.\",\r\n ),\r\n });\r\n setStep(\"success\");\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(\r\n err,\r\n t(\"errorResetGeneric\", \"Failed to reset password. Please try again.\"),\r\n );\r\n setError(errorMessage);\r\n toast.error(t(\"errorTitle\", \"Error\"), {\r\n description: errorMessage,\r\n });\r\n } finally {\r\n setIsSubmitting(false);\r\n }\r\n };\r\n\r\n // Success step\r\n if (step === \"success\") {\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=\"max-w-md mx-auto\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8 text-center\">\r\n <CheckCircle2 className=\"w-16 h-16 text-green-500 mx-auto mb-4\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"successTitle\", \"Password Reset Successfully!\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"successDescription\",\r\n \"Your password has been changed. You can now login with your new password.\",\r\n )}\r\n </p>\r\n <Button asChild className=\"w-full\">\r\n <Link to=\"/login\">{t(\"goToLogin\", \"Go to Login\")}</Link>\r\n </Button>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\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 {/* Hero Section */}\r\n <div className=\"text-center mb-12\">\r\n <h1 className=\"text-4xl font-bold text-foreground mb-4\">\r\n {t(\"title\", \"Forgot Password\")}\r\n </h1>\r\n <div className=\"w-16 h-1 bg-primary mx-auto mb-6\"></div>\r\n <p className=\"text-lg text-muted-foreground max-w-xl mx-auto\">\r\n {step === \"request\"\r\n ? t(\r\n \"descriptionRequest\",\r\n \"Enter your username and we'll send you a code to reset your password.\",\r\n )\r\n : t(\r\n \"descriptionReset\",\r\n \"Enter the code sent to your email and your new password.\",\r\n )}\r\n </p>\r\n </div>\r\n\r\n <div className=\"max-w-md mx-auto\">\r\n <Card>\r\n <CardHeader>\r\n <CardTitle className=\"flex items-center gap-2\">\r\n <KeyRound className=\"w-5 h-5 text-primary\" />\r\n {step === \"request\"\r\n ? t(\"cardTitleRequest\", \"Request Reset Code\")\r\n : t(\"cardTitleReset\", \"Reset Password\")}\r\n </CardTitle>\r\n <CardDescription>\r\n {step === \"request\"\r\n ? t(\"cardDescRequest\", \"Step 1 of 2: Request a reset code\")\r\n : t(\r\n \"cardDescReset\",\r\n \"Step 2 of 2: Enter code and new password\",\r\n )}\r\n </CardDescription>\r\n </CardHeader>\r\n <CardContent>\r\n {step === \"request\" ? (\r\n // Step 1: Request Code\r\n <form onSubmit={handleRequestCode} className=\"space-y-6\">\r\n <div>\r\n <Label htmlFor=\"username\">\r\n {t(\"username\", \"Username\")} *\r\n </Label>\r\n <Input\r\n id=\"username\"\r\n type=\"text\"\r\n value={username}\r\n onChange={(e) => setUsername(e.target.value)}\r\n placeholder={t(\r\n \"usernamePlaceholder\",\r\n \"Enter your username\",\r\n )}\r\n required\r\n className=\"mt-1\"\r\n autoComplete=\"username\"\r\n />\r\n </div>\r\n\r\n {error && (\r\n <div className=\"p-4 bg-red-50 border border-red-200 rounded-lg\">\r\n <p className=\"text-red-800 text-sm font-medium\">\r\n {error}\r\n </p>\r\n </div>\r\n )}\r\n\r\n <Button\r\n type=\"submit\"\r\n size=\"lg\"\r\n className=\"w-full\"\r\n disabled={isSubmitting}\r\n >\r\n {isSubmitting ? (\r\n <>\r\n <div className=\"w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2\" />\r\n {t(\"sending\", \"Sending...\")}\r\n </>\r\n ) : (\r\n t(\"sendCode\", \"Send Reset Code\")\r\n )}\r\n </Button>\r\n\r\n <div className=\"text-center\">\r\n <Link\r\n to=\"/login\"\r\n className=\"text-sm text-muted-foreground hover:text-primary inline-flex items-center gap-1\"\r\n >\r\n <ArrowLeft className=\"w-4 h-4\" />\r\n {t(\"backToLogin\", \"Back to Login\")}\r\n </Link>\r\n </div>\r\n </form>\r\n ) : (\r\n // Step 2: Reset Password\r\n <form onSubmit={handleResetPassword} className=\"space-y-6\">\r\n <div className=\"p-3 bg-muted rounded-lg text-sm\">\r\n <span className=\"text-muted-foreground\">\r\n {t(\"codeFor\", \"Reset code for:\")}{\" \"}\r\n </span>\r\n <span className=\"font-medium\">{username}</span>\r\n </div>\r\n\r\n <div>\r\n <Label htmlFor=\"code\">{t(\"code\", \"Reset Code\")} *</Label>\r\n <Input\r\n id=\"code\"\r\n type=\"text\"\r\n value={code}\r\n onChange={(e) => setCode(e.target.value)}\r\n placeholder={t(\"codePlaceholder\", \"Enter 6-digit code\")}\r\n required\r\n className=\"mt-1\"\r\n maxLength={6}\r\n />\r\n </div>\r\n\r\n <div>\r\n <Label htmlFor=\"newPassword\">\r\n {t(\"newPassword\", \"New Password\")} *\r\n </Label>\r\n <div className=\"relative\">\r\n <Input\r\n id=\"newPassword\"\r\n type={showPassword ? \"text\" : \"password\"}\r\n value={newPassword}\r\n onChange={(e) => setNewPassword(e.target.value)}\r\n placeholder={t(\r\n \"newPasswordPlaceholder\",\r\n \"Enter new password\",\r\n )}\r\n required\r\n className=\"mt-1 pr-10\"\r\n autoComplete=\"new-password\"\r\n />\r\n <button\r\n type=\"button\"\r\n onClick={() => setShowPassword(!showPassword)}\r\n className=\"absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground\"\r\n >\r\n {showPassword ? (\r\n <EyeOff className=\"w-4 h-4\" />\r\n ) : (\r\n <Eye className=\"w-4 h-4\" />\r\n )}\r\n </button>\r\n </div>\r\n <p className=\"text-xs text-muted-foreground mt-1\">\r\n {t(\r\n \"passwordRequirements\",\r\n \"At least 8 characters, 1 letter and 1 number\",\r\n )}\r\n </p>\r\n </div>\r\n\r\n <div>\r\n <Label htmlFor=\"confirmPassword\">\r\n {t(\"confirmPassword\", \"Confirm Password\")} *\r\n </Label>\r\n <Input\r\n id=\"confirmPassword\"\r\n type={showPassword ? \"text\" : \"password\"}\r\n value={confirmPassword}\r\n onChange={(e) => setConfirmPassword(e.target.value)}\r\n placeholder={t(\r\n \"confirmPasswordPlaceholder\",\r\n \"Confirm new password\",\r\n )}\r\n required\r\n className=\"mt-1\"\r\n autoComplete=\"new-password\"\r\n />\r\n </div>\r\n\r\n {error && (\r\n <div className=\"p-4 bg-red-50 border border-red-200 rounded-lg\">\r\n <p className=\"text-red-800 text-sm font-medium\">\r\n {error}\r\n </p>\r\n </div>\r\n )}\r\n\r\n <Button\r\n type=\"submit\"\r\n size=\"lg\"\r\n className=\"w-full\"\r\n disabled={isSubmitting}\r\n >\r\n {isSubmitting ? (\r\n <>\r\n <div className=\"w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2\" />\r\n {t(\"resetting\", \"Resetting...\")}\r\n </>\r\n ) : (\r\n t(\"resetPassword\", \"Reset Password\")\r\n )}\r\n </Button>\r\n\r\n <div className=\"flex justify-between\">\r\n <button\r\n type=\"button\"\r\n onClick={() => {\r\n setStep(\"request\");\r\n setCode(\"\");\r\n setNewPassword(\"\");\r\n setConfirmPassword(\"\");\r\n setError(null);\r\n }}\r\n className=\"text-sm text-muted-foreground hover:text-primary\"\r\n >\r\n {t(\"changeUsername\", \"Change username\")}\r\n </button>\r\n <button\r\n type=\"button\"\r\n onClick={() =>\r\n handleRequestCode({\r\n preventDefault: () => {},\r\n } as React.FormEvent)\r\n }\r\n className=\"text-sm text-primary hover:underline\"\r\n disabled={isSubmitting}\r\n >\r\n {t(\"resendCode\", \"Resend code\")}\r\n </button>\r\n </div>\r\n </form>\r\n )}\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default ForgotPasswordPage;\r\n"
30
+ },
31
+ {
32
+ "path": "forgot-password-page/lang/en.json",
33
+ "type": "registry:lang",
34
+ "target": "$modules$/forgot-password-page/lang/en.json",
35
+ "content": "{\r\n \"title\": \"Forgot Password\",\r\n \"descriptionRequest\": \"Enter your username and we'll send you a code to reset your password.\",\r\n \"descriptionReset\": \"Enter the code sent to your email and your new password.\",\r\n \"cardTitleRequest\": \"Request Reset Code\",\r\n \"cardTitleReset\": \"Reset Password\",\r\n \"cardDescRequest\": \"Step 1 of 2: Request a reset code\",\r\n \"cardDescReset\": \"Step 2 of 2: Enter code and new password\",\r\n \"username\": \"Username\",\r\n \"usernamePlaceholder\": \"Enter your username\",\r\n \"code\": \"Reset Code\",\r\n \"codePlaceholder\": \"Enter 6-digit code\",\r\n \"codeFor\": \"Reset code for:\",\r\n \"newPassword\": \"New Password\",\r\n \"newPasswordPlaceholder\": \"Enter new password\",\r\n \"confirmPassword\": \"Confirm Password\",\r\n \"confirmPasswordPlaceholder\": \"Confirm new password\",\r\n \"passwordRequirements\": \"At least 8 characters, 1 letter and 1 number\",\r\n \"passwordMismatch\": \"Passwords do not match\",\r\n \"sendCode\": \"Send Reset Code\",\r\n \"sending\": \"Sending...\",\r\n \"resetPassword\": \"Reset Password\",\r\n \"resetting\": \"Resetting...\",\r\n \"backToLogin\": \"Back to Login\",\r\n \"changeUsername\": \"Change username\",\r\n \"resendCode\": \"Resend code\",\r\n \"codeSentTitle\": \"Code Sent!\",\r\n \"codeSentDesc\": \"A password reset code has been sent to your email.\",\r\n \"errorTitle\": \"Error\",\r\n \"errorGeneric\": \"Failed to send reset code. Please try again.\",\r\n \"errorResetGeneric\": \"Failed to reset password. Please try again.\",\r\n \"resetSuccessTitle\": \"Password Reset!\",\r\n \"resetSuccessDesc\": \"Your password has been successfully reset.\",\r\n \"successTitle\": \"Password Reset Successfully!\",\r\n \"successDescription\": \"Your password has been changed. You can now login with your new password.\",\r\n \"goToLogin\": \"Go to Login\"\r\n}\r\n"
36
+ },
37
+ {
38
+ "path": "forgot-password-page/lang/tr.json",
39
+ "type": "registry:lang",
40
+ "target": "$modules$/forgot-password-page/lang/tr.json",
41
+ "content": "{\r\n \"title\": \"Şifremi Unuttum\",\r\n \"descriptionRequest\": \"Kullanıcı adınızı girin, size şifre sıfırlama kodu göndereceğiz.\",\r\n \"descriptionReset\": \"E-postanıza gönderilen kodu ve yeni şifrenizi girin.\",\r\n \"cardTitleRequest\": \"Sıfırlama Kodu İste\",\r\n \"cardTitleReset\": \"Şifre Sıfırla\",\r\n \"cardDescRequest\": \"Adım 1/2: Sıfırlama kodu isteyin\",\r\n \"cardDescReset\": \"Adım 2/2: Kodu ve yeni şifreyi girin\",\r\n \"username\": \"Kullanıcı Adı\",\r\n \"usernamePlaceholder\": \"Kullanıcı adınızı girin\",\r\n \"code\": \"Sıfırlama Kodu\",\r\n \"codePlaceholder\": \"6 haneli kodu girin\",\r\n \"codeFor\": \"Sıfırlama kodu:\",\r\n \"newPassword\": \"Yeni Şifre\",\r\n \"newPasswordPlaceholder\": \"Yeni şifre girin\",\r\n \"confirmPassword\": \"Şifre Onayı\",\r\n \"confirmPasswordPlaceholder\": \"Yeni şifreyi onaylayın\",\r\n \"passwordRequirements\": \"En az 8 karakter, 1 harf ve 1 rakam\",\r\n \"passwordMismatch\": \"Şifreler eşleşmiyor\",\r\n \"sendCode\": \"Sıfırlama Kodu Gönder\",\r\n \"sending\": \"Gönderiliyor...\",\r\n \"resetPassword\": \"Şifreyi Sıfırla\",\r\n \"resetting\": \"Sıfırlanıyor...\",\r\n \"backToLogin\": \"Girişe Dön\",\r\n \"changeUsername\": \"Kullanıcı adını değiştir\",\r\n \"resendCode\": \"Kodu tekrar gönder\",\r\n \"codeSentTitle\": \"Kod Gönderildi!\",\r\n \"codeSentDesc\": \"E-postanıza şifre sıfırlama kodu gönderildi.\",\r\n \"errorTitle\": \"Hata\",\r\n \"errorGeneric\": \"Sıfırlama kodu gönderilemedi. Lütfen tekrar deneyin.\",\r\n \"errorResetGeneric\": \"Şifre sıfırlanamadı. Lütfen tekrar deneyin.\",\r\n \"resetSuccessTitle\": \"Şifre Sıfırlandı!\",\r\n \"resetSuccessDesc\": \"Şifreniz başarıyla sıfırlandı.\",\r\n \"successTitle\": \"Şifre Başarıyla Sıfırlandı!\",\r\n \"successDescription\": \"Şifreniz değiştirildi. Artık yeni şifrenizle giriş yapabilirsiniz.\",\r\n \"goToLogin\": \"Girişe Git\"\r\n}\r\n"
42
+ }
43
+ ],
44
+ "exports": {
45
+ "types": [],
46
+ "variables": [
47
+ "ForgotPasswordPage",
48
+ "default"
49
+ ]
50
+ }
51
+ }
@@ -4,7 +4,9 @@
4
4
  "title": "E-commerce Header",
5
5
  "description": "Full-featured e-commerce header with logo, main navigation, search bar with autocomplete dropdown, favorites icon with count badge, shopping cart icon with item count, user account dropdown menu, and mobile hamburger menu. Includes sticky positioning, language switcher, and responsive breakpoints. Dark mode support included.",
6
6
  "registryDependencies": [
7
- "ecommerce-core"
7
+ "ecommerce-core",
8
+ "cart-drawer",
9
+ "auth-core"
8
10
  ],
9
11
  "usage": "import { Header } from '@/modules/header-ecommerce';\n\n<Header />\n\n• Component is installed at: src/modules/header-ecommerce/\n• Customize navigation links in: src/modules/header-ecommerce/lang/*.json\n• Uses useCart and useFavorites from ecommerce-core for badges\n• Includes search, cart, favorites, and mobile menu",
10
12
  "files": [
@@ -18,7 +20,7 @@
18
20
  "path": "header-ecommerce/header-ecommerce.tsx",
19
21
  "type": "registry:component",
20
22
  "target": "$modules$/header-ecommerce/header-ecommerce.tsx",
21
- "content": "import { useState } from \"react\";\nimport { Link, useNavigate } from \"react-router\";\nimport { ShoppingCart, Menu, Search, Heart, Package } 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 { DropdownMenuItem } from \"@/components/ui/dropdown-menu\";\nimport { Logo } from \"@/components/Logo\";\nimport { AuthHeaderMenu } from \"@/modules/auth/auth-header-menu\";\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 navigate = useNavigate();\n const { t } = useTranslation(\"header-ecommerce\");\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=\"container 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 <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 <AuthHeaderMenu variant=\"desktop\">\n <DropdownMenuItem asChild className=\"cursor-pointer\">\n <Link to=\"/orders\" className=\"flex items-center\">\n <Package className=\"mr-2 h-4 w-4\" />\n {t(\"myOrders\", \"My Orders\")}\n </Link>\n </DropdownMenuItem>\n </AuthHeaderMenu>\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 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 <AuthHeaderMenu\n variant=\"mobile\"\n onMenuClose={() => setMobileMenuOpen(false)}\n >\n <Link\n to=\"/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 </AuthHeaderMenu>\n </div>\n </div>\n </SheetContent>\n </Sheet>\n </div>\n </div>\n </div>\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, 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"
22
24
  },
23
25
  {
24
26
  "path": "header-ecommerce/lang/en.json",