@promakeai/cli 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/index.js +412 -201
  2. package/dist/registry/about-page.json +2 -2
  3. package/dist/registry/about-section.json +2 -2
  4. package/dist/registry/announcement-bar.json +2 -2
  5. package/dist/registry/blog-list-page.json +1 -1
  6. package/dist/registry/blog-section.json +6 -4
  7. package/dist/registry/cards-carousel-section.json +1 -1
  8. package/dist/registry/cart-drawer.json +5 -4
  9. package/dist/registry/case-study-page.json +2 -2
  10. package/dist/registry/coming-soon-page-minimal.json +1 -1
  11. package/dist/registry/coming-soon-page.json +1 -1
  12. package/dist/registry/contact-info-grid.json +2 -2
  13. package/dist/registry/contact-page-centered.json +2 -2
  14. package/dist/registry/contact-page-split.json +2 -2
  15. package/dist/registry/contact-page.json +2 -2
  16. package/dist/registry/cta-section.json +2 -2
  17. package/dist/registry/docs/blog-section.md +3 -1
  18. package/dist/registry/docs/cart-drawer.md +9 -9
  19. package/dist/registry/docs/favorites-blog-block.md +10 -3
  20. package/dist/registry/docs/favorites-blog-page.md +38 -0
  21. package/dist/registry/docs/favorites-ecommerce-block.md +10 -3
  22. package/dist/registry/docs/favorites-ecommerce-page.md +38 -0
  23. package/dist/registry/docs/login-page.md +6 -16
  24. package/dist/registry/docs/payment-success-block.md +8 -1
  25. package/dist/registry/docs/post-detail-page.md +39 -0
  26. package/dist/registry/docs/product-card-detailed.md +7 -11
  27. package/dist/registry/docs/product-detail-page.md +39 -0
  28. package/dist/registry/docs/product-detail-section.md +7 -13
  29. package/dist/registry/docs/product-quick-view.md +4 -2
  30. package/dist/registry/ecommerce-core.json +2 -2
  31. package/dist/registry/faq-categorized.json +2 -2
  32. package/dist/registry/faq-simple.json +2 -2
  33. package/dist/registry/favorites-blog-block.json +1 -1
  34. package/dist/registry/favorites-blog-page.json +48 -0
  35. package/dist/registry/favorites-ecommerce-block.json +1 -1
  36. package/dist/registry/favorites-ecommerce-page.json +48 -0
  37. package/dist/registry/feature-section.json +2 -2
  38. package/dist/registry/footer.json +2 -2
  39. package/dist/registry/header-ecommerce.json +1 -1
  40. package/dist/registry/hero-carousel.json +3 -3
  41. package/dist/registry/hero-cta.json +2 -2
  42. package/dist/registry/hero-gradient.json +2 -2
  43. package/dist/registry/hero.json +2 -2
  44. package/dist/registry/index.json +9 -0
  45. package/dist/registry/landing-page-app.json +1 -1
  46. package/dist/registry/landing-page-saas.json +1 -1
  47. package/dist/registry/login-page-split.json +1 -1
  48. package/dist/registry/login-page.json +8 -6
  49. package/dist/registry/logo-cloud.json +1 -1
  50. package/dist/registry/payment-success-block.json +7 -3
  51. package/dist/registry/portfolio-page.json +2 -2
  52. package/dist/registry/post-card.json +1 -1
  53. package/dist/registry/post-detail-page.json +48 -0
  54. package/dist/registry/pricing-page.json +1 -1
  55. package/dist/registry/pricing-section.json +2 -2
  56. package/dist/registry/product-card-detailed.json +5 -4
  57. package/dist/registry/product-detail-page.json +48 -0
  58. package/dist/registry/product-detail-section.json +5 -4
  59. package/dist/registry/product-quick-view.json +5 -4
  60. package/dist/registry/products-page.json +1 -1
  61. package/dist/registry/reading-progress.json +1 -1
  62. package/dist/registry/team-page.json +1 -1
  63. package/dist/registry/testimonials-carousel.json +2 -2
  64. package/dist/registry/testimonials-grid.json +2 -2
  65. package/dist/registry/timeline-section.json +2 -2
  66. package/dist/registry/video-hero.json +1 -1
  67. package/package.json +53 -51
  68. package/template/eslint.config.js +5 -4
  69. package/template/src/components/ui/sidebar.tsx +2 -4
@@ -28,13 +28,13 @@
28
28
  "path": "about-page/lang/en.json",
29
29
  "type": "registry:lang",
30
30
  "target": "$modules$/about-page/lang/en.json",
31
- "content": "{\r\n \"title\": \"About Us\",\r\n \"subtitle\": \"Ask Promake to customize this about page subtitle based on your site. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"storyTitle\": \"Our Story\",\r\n \"storyP1\": \"This is placeholder text for your story. Ask Promake to replace this with your actual background and journey. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"storyP2\": \"Placeholder text for the second paragraph of your story. Ask Promake to customize this based on your journey and values. Sed do eiusmod tempor incididunt ut labore.\",\r\n \"missionTitle\": \"Our Mission\",\r\n \"missionDesc\": \"Ask Promake to customize this mission description based on your goals. Ut enim ad minim veniam, quis nostrud exercitation.\",\r\n \"valuesTitle\": \"Our Values\",\r\n \"valuesDesc\": \"This values description will be replaced by Promake with your actual values. Duis aute irure dolor in reprehenderit.\",\r\n \"teamTitle\": \"Our Team\",\r\n \"teamDesc\": \"Placeholder team description. Ask Promake to customize this based on your team structure and culture.\",\r\n \"qualityTitle\": \"Quality First\",\r\n \"qualityDesc\": \"Ask Promake to replace this quality description with relevant information about your quality standards.\",\r\n \"customersValue\": \"500+\",\r\n \"customersLabel\": \"Happy Customers\",\r\n \"projectsValue\": \"1000+\",\r\n \"projectsLabel\": \"Projects Completed\",\r\n \"experienceValue\": \"10+\",\r\n \"experienceLabel\": \"Years Experience\",\r\n \"satisfactionValue\": \"99%\",\r\n \"satisfactionLabel\": \"Client Satisfaction\",\r\n \"ctaTitle\": \"Ready to Work Together?\",\r\n \"ctaDesc\": \"Ask Promake to customize this CTA description based on your site goals. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\"\r\n}\r\n"
31
+ "content": "{\r\n \"title\": \"About Us\",\r\n \"subtitle\": \"Customize this about page subtitle with Promake. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"storyTitle\": \"Our Story\",\r\n \"storyP1\": \"This is placeholder text for your story. Let Promake tailor this with your actual background and journey. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"storyP2\": \"Placeholder text for the second paragraph of your story. Work with Promake to personalize this based on your journey. Sed do eiusmod tempor incididunt ut labore.\",\r\n \"missionTitle\": \"Our Mission\",\r\n \"missionDesc\": \"Use Promake to update this mission description for your goals. Ut enim ad minim veniam, quis nostrud exercitation.\",\r\n \"valuesTitle\": \"Our Values\",\r\n \"valuesDesc\": \"Have Promake replace this description with your actual values. Duis aute irure dolor in reprehenderit.\",\r\n \"teamTitle\": \"Our Team\",\r\n \"teamDesc\": \"Placeholder team description. Promake can help tailor this to your team culture.\",\r\n \"qualityTitle\": \"Quality First\",\r\n \"qualityDesc\": \"Edit this quality description via Promake with details about your standards.\",\r\n \"customersValue\": \"500+\",\r\n \"customersLabel\": \"Happy Customers\",\r\n \"projectsValue\": \"1000+\",\r\n \"projectsLabel\": \"Projects Completed\",\r\n \"experienceValue\": \"10+\",\r\n \"experienceLabel\": \"Years Experience\",\r\n \"satisfactionValue\": \"99%\",\r\n \"satisfactionLabel\": \"Client Satisfaction\",\r\n \"ctaTitle\": \"Ready to Work Together?\",\r\n \"ctaDesc\": \"Update this CTA description using Promake based on your goals. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\"\r\n}\r\n"
32
32
  },
33
33
  {
34
34
  "path": "about-page/lang/tr.json",
35
35
  "type": "registry:lang",
36
36
  "target": "$modules$/about-page/lang/tr.json",
37
- "content": "{\r\n \"title\": \"Hakkımızda\",\r\n \"subtitle\": \"AI bu hakkımızda sayfası alt başlığını sitenize göre özelleştirecektir. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"storyTitle\": \"Hikayemiz\",\r\n \"storyP1\": \"Bu hikayeniz için placeholder metindir. AI bunu gerçek geçmişiniz ve yolculuğunuzla değiştirecektir. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"storyP2\": \"Hikayenin ikinci paragrafı için placeholder metin. AI bunu yolculuğunuz ve değerlerinize göre özelleştirecektir. Sed do eiusmod tempor incididunt ut labore.\",\r\n \"missionTitle\": \"Misyonumuz\",\r\n \"missionDesc\": \"AI bu misyon açıklamasını hedeflerinize göre özelleştirecektir. Ut enim ad minim veniam, quis nostrud exercitation.\",\r\n \"valuesTitle\": \"Değerlerimiz\",\r\n \"valuesDesc\": \"Bu değerler açıklaması AI tarafından gerçek değerlerinizle değiştirilecektir. Duis aute irure dolor in reprehenderit.\",\r\n \"teamTitle\": \"Ekibimiz\",\r\n \"teamDesc\": \"Placeholder ekip açıklaması. AI bunu ekip yapınıza ve kültürünüze göre özelleştirecektir.\",\r\n \"qualityTitle\": \"Önce Kalite\",\r\n \"qualityDesc\": \"AI bu kalite açıklamasını kalite standartlarınızla ilgili bilgilerle değiştirecektir.\",\r\n \"customersValue\": \"500+\",\r\n \"customersLabel\": \"Mutlu Müşteri\",\r\n \"projectsValue\": \"1000+\",\r\n \"projectsLabel\": \"Tamamlanan Proje\",\r\n \"experienceValue\": \"10+\",\r\n \"experienceLabel\": \"Yıllık Deneyim\",\r\n \"satisfactionValue\": \"%99\",\r\n \"satisfactionLabel\": \"Müşteri Memnuniyeti\",\r\n \"ctaTitle\": \"Birlikte Çalışmaya Hazır mısınız?\",\r\n \"ctaDesc\": \"AI bu CTA açıklamasını site hedeflerinize göre özelleştirecektir. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\"\r\n}\r\n"
37
+ "content": "{\r\n \"title\": \"Hakkımızda\",\r\n \"subtitle\": \"Bu hakkımızda sayfası alt başlığını Promake ile özelleştirin. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"storyTitle\": \"Hikayemiz\",\r\n \"storyP1\": \"Bu hikayeniz için placeholder metindir. Promake ile gerçek geçmişiniz ve yolculuğunuzla uyarlayın. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"storyP2\": \"Hikayenin ikinci paragrafı için placeholder metin. Promake ile yolculuğunuza göre kişiselleştirin. Sed do eiusmod tempor incididunt ut labore.\",\r\n \"missionTitle\": \"Misyonumuz\",\r\n \"missionDesc\": \"Bu misyon açıklamasını hedeflerinize göre güncellemek için Promake kullanın. Ut enim ad minim veniam, quis nostrud exercitation.\",\r\n \"valuesTitle\": \"Değerlerimiz\",\r\n \"valuesDesc\": \"Promake'ten bu açıklamayı gerçek değerlerinizle değiştirmesini isteyin. Duis aute irure dolor in reprehenderit.\",\r\n \"teamTitle\": \"Ekibimiz\",\r\n \"teamDesc\": \"Placeholder ekip açıklaması. Promake bunu ekip kültürünüze göre uyarlamanıza yardımcı olabilir.\",\r\n \"qualityTitle\": \"Önce Kalite\",\r\n \"qualityDesc\": \"Bu kalite açıklamasını standartlarınızla ilgili detaylarla Promake üzerinden düzenleyin.\",\r\n \"customersValue\": \"500+\",\r\n \"customersLabel\": \"Mutlu Müşteri\",\r\n \"projectsValue\": \"1000+\",\r\n \"projectsLabel\": \"Tamamlanan Proje\",\r\n \"experienceValue\": \"10+\",\r\n \"experienceLabel\": \"Yıllık Deneyim\",\r\n \"satisfactionValue\": \"%99\",\r\n \"satisfactionLabel\": \"Müşteri Memnuniyeti\",\r\n \"ctaTitle\": \"Birlikte Çalışmaya Hazır mısınız?\",\r\n \"ctaDesc\": \"Bu CTA açıklamasını hedeflerinize göre Promake kullanarak güncelleyin. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\"\r\n}\r\n"
38
38
  }
39
39
  ],
40
40
  "exports": {
@@ -22,13 +22,13 @@
22
22
  "path": "about-section/lang/en.json",
23
23
  "type": "registry:lang",
24
24
  "target": "$modules$/about-section/lang/en.json",
25
- "content": "{\r\n \"title\": \"About Us\",\r\n \"description\": \"Ask Promake to customize this description based on your site and audience. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.\",\r\n \"mainImageAlt\": \"Our team\",\r\n \"secondaryImageAlt\": \"Our office\",\r\n \"cardTitle\": \"Our Mission\",\r\n \"cardDescription\": \"This mission description will be customized by Promake based on your goals and values. Ut enim ad minim veniam, quis nostrud exercitation.\",\r\n \"cardButton\": \"Learn More\",\r\n \"companiesTitle\": \"Ask Promake to customize this companies title\",\r\n \"statsTitle\": \"Our Achievements\",\r\n \"statsDescription\": \"This stats description will be replaced by Promake with relevant information about your milestones.\",\r\n \"stat1Value\": \"500+\",\r\n \"stat1Label\": \"Happy Clients\",\r\n \"stat2Value\": \"1000+\",\r\n \"stat2Label\": \"Projects Completed\",\r\n \"stat3Value\": \"99%\",\r\n \"stat3Label\": \"Satisfaction Rate\",\r\n \"stat4Value\": \"15+\",\r\n \"stat4Label\": \"Years Experience\"\r\n}\r\n"
25
+ "content": "{\r\n \"title\": \"About Us\",\r\n \"description\": \"Let Promake personalize this description for your brand and audience. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.\",\r\n \"mainImageAlt\": \"Our team\",\r\n \"secondaryImageAlt\": \"Our office\",\r\n \"cardTitle\": \"Our Mission\",\r\n \"cardDescription\": \"Work with Promake to tailor this mission statement to your goals and values. Ut enim ad minim veniam, quis nostrud exercitation.\",\r\n \"cardButton\": \"Learn More\",\r\n \"companiesTitle\": \"Customize this section title with Promake\",\r\n \"statsTitle\": \"Our Achievements\",\r\n \"statsDescription\": \"Have Promake update this with key information about your milestones.\",\r\n \"stat1Value\": \"500+\",\r\n \"stat1Label\": \"Happy Clients\",\r\n \"stat2Value\": \"1000+\",\r\n \"stat2Label\": \"Projects Completed\",\r\n \"stat3Value\": \"99%\",\r\n \"stat3Label\": \"Satisfaction Rate\",\r\n \"stat4Value\": \"15+\",\r\n \"stat4Label\": \"Years Experience\"\r\n}\r\n"
26
26
  },
27
27
  {
28
28
  "path": "about-section/lang/tr.json",
29
29
  "type": "registry:lang",
30
30
  "target": "$modules$/about-section/lang/tr.json",
31
- "content": "{\r\n \"title\": \"Hakkımızda\",\r\n \"description\": \"AI bu açıklamayı sitenize ve hedef kitlenize göre özelleştirecektir. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.\",\r\n \"mainImageAlt\": \"Ekibimiz\",\r\n \"secondaryImageAlt\": \"Ofisimiz\",\r\n \"cardTitle\": \"Misyonumuz\",\r\n \"cardDescription\": \"Bu misyon açıklaması AI tarafından hedeflerinize ve değerlerinize göre özelleştirilecektir. Ut enim ad minim veniam, quis nostrud exercitation.\",\r\n \"cardButton\": \"Daha Fazla\",\r\n \"companiesTitle\": \"AI bu referanslar başlığını özelleştirecektir\",\r\n \"statsTitle\": \"Başarılarımız\",\r\n \"statsDescription\": \"Bu istatistik açıklaması AI tarafından önemli kilometre taşlarınızla ilgili bilgilerle değiştirilecektir.\",\r\n \"stat1Value\": \"500+\",\r\n \"stat1Label\": \"Mutlu Müşteri\",\r\n \"stat2Value\": \"1000+\",\r\n \"stat2Label\": \"Tamamlanan Proje\",\r\n \"stat3Value\": \"%99\",\r\n \"stat3Label\": \"Memnuniyet Oranı\",\r\n \"stat4Value\": \"15+\",\r\n \"stat4Label\": \"Yıllık Deneyim\"\r\n}\r\n"
31
+ "content": "{\r\n \"title\": \"Hakkımızda\",\r\n \"description\": \"Promake ile bu açıklamayı markanıza ve hedef kitlenize göre kişiselleştirin. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.\",\r\n \"mainImageAlt\": \"Ekibimiz\",\r\n \"secondaryImageAlt\": \"Ofisimiz\",\r\n \"cardTitle\": \"Misyonumuz\",\r\n \"cardDescription\": \"Promake ile bu misyon metnini hedeflerinize ve değerlerinize göre uyarlayın. Ut enim ad minim veniam, quis nostrud exercitation.\",\r\n \"cardButton\": \"Daha Fazla\",\r\n \"companiesTitle\": \"Bu bölüm başlığını Promake kullanarak özelleştirin\",\r\n \"statsTitle\": \"Başarılarımız\",\r\n \"statsDescription\": \"Promake'ten önemli kilometre taşlarınızla ilgili bilgilerle güncellemesini isteyin.\",\r\n \"stat1Value\": \"500+\",\r\n \"stat1Label\": \"Mutlu Müşteri\",\r\n \"stat2Value\": \"1000+\",\r\n \"stat2Label\": \"Tamamlanan Proje\",\r\n \"stat3Value\": \"%99\",\r\n \"stat3Label\": \"Memnuniyet Oranı\",\r\n \"stat4Value\": \"15+\",\r\n \"stat4Label\": \"Yıllık Deneyim\"\r\n}\r\n"
32
32
  }
33
33
  ],
34
34
  "exports": {
@@ -19,13 +19,13 @@
19
19
  "path": "announcement-bar/announcement-bar.tsx",
20
20
  "type": "registry:component",
21
21
  "target": "$modules$/announcement-bar/announcement-bar.tsx",
22
- "content": "import { useState, useEffect } from \"react\";\r\nimport { Link } from \"react-router\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { X, ArrowRight, Sparkles, Megaphone, Gift, Zap } from \"lucide-react\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { motion, AnimatePresence } from \"motion/react\";\r\n\r\ntype BarVariant = \"default\" | \"primary\" | \"warning\" | \"success\" | \"gradient\";\r\n\r\ninterface AnnouncementBarProps {\r\n message?: string;\r\n linkText?: string;\r\n linkUrl?: string;\r\n variant?: BarVariant;\r\n icon?: \"sparkles\" | \"megaphone\" | \"gift\" | \"zap\" | \"none\";\r\n dismissible?: boolean;\r\n storageKey?: string;\r\n className?: string;\r\n}\r\n\r\nconst icons = {\r\n sparkles: Sparkles,\r\n megaphone: Megaphone,\r\n gift: Gift,\r\n zap: Zap,\r\n none: null,\r\n};\r\n\r\nconst variantStyles: Record<BarVariant, string> = {\r\n default: \"bg-muted text-muted-foreground\",\r\n primary: \"bg-primary text-primary-foreground\",\r\n warning: \"bg-yellow-500 text-yellow-950\",\r\n success: \"bg-green-500 text-white\",\r\n gradient: \"bg-gradient-to-r from-primary via-purple-500 to-pink-500 text-white\",\r\n};\r\n\r\nexport function AnnouncementBar({\r\n message,\r\n linkText,\r\n linkUrl = \"#\",\r\n variant = \"primary\",\r\n icon = \"sparkles\",\r\n dismissible = true,\r\n storageKey = \"announcement-bar-dismissed\",\r\n className,\r\n}: AnnouncementBarProps) {\r\n const { t } = useTranslation(\"announcement-bar\");\r\n const [isVisible, setIsVisible] = useState(false);\r\n\r\n const displayMessage = message || t(\"message\", \"Exciting news! Check out our latest features.\");\r\n const displayLinkText = linkText || t(\"linkText\", \"Learn more\");\r\n\r\n useEffect(() => {\r\n if (dismissible) {\r\n const dismissed = localStorage.getItem(storageKey);\r\n if (!dismissed) {\r\n setIsVisible(true);\r\n }\r\n } else {\r\n setIsVisible(true);\r\n }\r\n }, [dismissible, storageKey]);\r\n\r\n const handleDismiss = () => {\r\n if (dismissible) {\r\n localStorage.setItem(storageKey, \"true\");\r\n }\r\n setIsVisible(false);\r\n };\r\n\r\n const IconComponent = icons[icon];\r\n\r\n return (\r\n <AnimatePresence>\r\n {isVisible && (\r\n <motion.div\r\n initial={{ height: 0, opacity: 0 }}\r\n animate={{ height: \"auto\", opacity: 1 }}\r\n exit={{ height: 0, opacity: 0 }}\r\n transition={{ duration: 0.3 }}\r\n className={cn(\r\n \"relative overflow-hidden\",\r\n variantStyles[variant],\r\n className\r\n )}\r\n >\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n <div className=\"flex items-center justify-center gap-2 py-2.5 text-sm font-medium\">\r\n {IconComponent && (\r\n <IconComponent className=\"h-4 w-4 flex-shrink-0\" />\r\n )}\r\n <span className=\"text-center\">{displayMessage}</span>\r\n {linkUrl && (\r\n <Link\r\n to={linkUrl}\r\n className=\"inline-flex items-center gap-1 font-semibold hover:underline underline-offset-2\"\r\n >\r\n {displayLinkText}\r\n <ArrowRight className=\"h-3 w-3\" />\r\n </Link>\r\n )}\r\n {dismissible && (\r\n <button\r\n onClick={handleDismiss}\r\n className=\"absolute right-4 p-1 rounded hover:bg-black/10 transition-colors\"\r\n aria-label={t(\"dismiss\", \"Dismiss\")}\r\n >\r\n <X className=\"h-4 w-4\" />\r\n </button>\r\n )}\r\n </div>\r\n </div>\r\n </motion.div>\r\n )}\r\n </AnimatePresence>\r\n );\r\n}\r\n"
22
+ "content": "import { useState } from \"react\";\r\nimport { Link } from \"react-router\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { X, ArrowRight, Sparkles, Megaphone, Gift, Zap } from \"lucide-react\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { motion, AnimatePresence } from \"motion/react\";\r\n\r\ntype BarVariant = \"default\" | \"primary\" | \"warning\" | \"success\" | \"gradient\";\r\n\r\ninterface AnnouncementBarProps {\r\n message?: string;\r\n linkText?: string;\r\n linkUrl?: string;\r\n variant?: BarVariant;\r\n icon?: \"sparkles\" | \"megaphone\" | \"gift\" | \"zap\" | \"none\";\r\n dismissible?: boolean;\r\n storageKey?: string;\r\n className?: string;\r\n}\r\n\r\nconst icons = {\r\n sparkles: Sparkles,\r\n megaphone: Megaphone,\r\n gift: Gift,\r\n zap: Zap,\r\n none: null,\r\n};\r\n\r\nconst variantStyles: Record<BarVariant, string> = {\r\n default: \"bg-muted text-muted-foreground\",\r\n primary: \"bg-primary text-primary-foreground\",\r\n warning: \"bg-yellow-500 text-yellow-950\",\r\n success: \"bg-green-500 text-white\",\r\n gradient: \"bg-gradient-to-r from-primary via-purple-500 to-pink-500 text-white\",\r\n};\r\n\r\nexport function AnnouncementBar({\r\n message,\r\n linkText,\r\n linkUrl = \"#\",\r\n variant = \"primary\",\r\n icon = \"sparkles\",\r\n dismissible = true,\r\n storageKey = \"announcement-bar-dismissed\",\r\n className,\r\n}: AnnouncementBarProps) {\r\n const { t } = useTranslation(\"announcement-bar\");\r\n const [isVisible, setIsVisible] = useState(() => {\r\n if (typeof window === \"undefined\") return false;\r\n if (dismissible) {\r\n return !localStorage.getItem(storageKey);\r\n }\r\n return true;\r\n });\r\n\r\n const displayMessage = message || t(\"message\", \"Exciting news! Check out our latest features.\");\r\n const displayLinkText = linkText || t(\"linkText\", \"Learn more\");\r\n\r\n const handleDismiss = () => {\r\n if (dismissible) {\r\n localStorage.setItem(storageKey, \"true\");\r\n }\r\n setIsVisible(false);\r\n };\r\n\r\n const IconComponent = icons[icon];\r\n\r\n return (\r\n <AnimatePresence>\r\n {isVisible && (\r\n <motion.div\r\n initial={{ height: 0, opacity: 0 }}\r\n animate={{ height: \"auto\", opacity: 1 }}\r\n exit={{ height: 0, opacity: 0 }}\r\n transition={{ duration: 0.3 }}\r\n className={cn(\r\n \"relative overflow-hidden\",\r\n variantStyles[variant],\r\n className\r\n )}\r\n >\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n <div className=\"flex items-center justify-center gap-2 py-2.5 text-sm font-medium\">\r\n {IconComponent && (\r\n <IconComponent className=\"h-4 w-4 flex-shrink-0\" />\r\n )}\r\n <span className=\"text-center\">{displayMessage}</span>\r\n {linkUrl && (\r\n <Link\r\n to={linkUrl}\r\n className=\"inline-flex items-center gap-1 font-semibold hover:underline underline-offset-2\"\r\n >\r\n {displayLinkText}\r\n <ArrowRight className=\"h-3 w-3\" />\r\n </Link>\r\n )}\r\n {dismissible && (\r\n <button\r\n onClick={handleDismiss}\r\n className=\"absolute right-4 p-1 rounded hover:bg-black/10 transition-colors\"\r\n aria-label={t(\"dismiss\", \"Dismiss\")}\r\n >\r\n <X className=\"h-4 w-4\" />\r\n </button>\r\n )}\r\n </div>\r\n </div>\r\n </motion.div>\r\n )}\r\n </AnimatePresence>\r\n );\r\n}\r\n"
23
23
  },
24
24
  {
25
25
  "path": "announcement-bar/lang/en.json",
26
26
  "type": "registry:lang",
27
27
  "target": "$modules$/announcement-bar/lang/en.json",
28
- "content": "{\r\n \"message\": \"Ask Promake to customize this announcement message based on your news or promotion.\",\r\n \"linkText\": \"Learn more\",\r\n \"dismiss\": \"Dismiss\"\r\n}\r\n"
28
+ "content": "{\r\n \"message\": \"Customize this announcement with Promake for your news or promotion.\",\r\n \"linkText\": \"Learn more\",\r\n \"dismiss\": \"Dismiss\"\r\n}\r\n"
29
29
  },
30
30
  {
31
31
  "path": "announcement-bar/lang/tr.json",
@@ -24,7 +24,7 @@
24
24
  "path": "blog-list-page/blog-list-page.tsx",
25
25
  "type": "registry:page",
26
26
  "target": "$modules$/blog-list-page/blog-list-page.tsx",
27
- "content": "import { useState, useEffect } from \"react\";\nimport { useSearchParams } from \"react-router\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { useTranslation } from \"react-i18next\";\nimport { Layout } from \"@/components/Layout\";\nimport { Search, Filter } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { FadeIn } from \"@/modules/animations\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n SheetTrigger,\n} from \"@/components/ui/sheet\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { PostCard } from \"@/modules/post-card/post-card\";\nimport { usePosts, useBlogCategories } from \"@/modules/blog-core\";\n\nexport function BlogListPage() {\n const { t } = useTranslation(\"blog-list-page\");\n usePageTitle({ title: t(\"title\") });\n\n const [searchParams, setSearchParams] = useSearchParams();\n const [searchTerm, setSearchTerm] = useState(\n searchParams.get(\"search\") || \"\"\n );\n const [selectedCategories, setSelectedCategories] = useState<string[]>(\n searchParams.get(\"categories\")?.split(\",\").filter(Boolean) || []\n );\n const [selectedTags, setSelectedTags] = useState<string[]>(\n searchParams.get(\"tags\")?.split(\",\").filter(Boolean) || []\n );\n const [sortBy, setSortBy] = useState(searchParams.get(\"sort\") || \"newest\");\n const [viewMode, _setViewMode] = useState<\"grid\" | \"list\">(\"grid\");\n\n const { posts, loading, error } = usePosts();\n const { categories } = useBlogCategories();\n\n const filteredPosts = posts.filter((post) => {\n if (searchTerm) {\n const searchLower = searchTerm.toLowerCase();\n if (\n !post.title.toLowerCase().includes(searchLower) &&\n !post.excerpt.toLowerCase().includes(searchLower) &&\n !post.content.toLowerCase().includes(searchLower)\n ) {\n return false;\n }\n }\n\n if (selectedCategories.length > 0) {\n const hasMatchingCategory = selectedCategories.some(\n (categorySlug) =>\n post.category === categorySlug ||\n post.categories?.some((cat) => cat.slug === categorySlug)\n );\n if (!hasMatchingCategory) return false;\n }\n\n if (selectedTags.length > 0) {\n const hasMatchingTag = selectedTags.some((tag) =>\n post.tags.includes(tag)\n );\n if (!hasMatchingTag) return false;\n }\n\n return true;\n });\n\n const sortedPosts = [...filteredPosts].sort((a, b) => {\n switch (sortBy) {\n case \"oldest\":\n return (\n new Date(a.published_at).getTime() -\n new Date(b.published_at).getTime()\n );\n case \"popular\":\n return (b.view_count || 0) - (a.view_count || 0);\n case \"reading-time\":\n return (a.read_time || 0) - (b.read_time || 0);\n case \"newest\":\n default:\n return (\n new Date(b.published_at).getTime() -\n new Date(a.published_at).getTime()\n );\n }\n });\n\n useEffect(() => {\n const params = new URLSearchParams();\n if (searchTerm) params.set(\"search\", searchTerm);\n if (selectedCategories.length)\n params.set(\"categories\", selectedCategories.join(\",\"));\n if (selectedTags.length) params.set(\"tags\", selectedTags.join(\",\"));\n if (sortBy !== \"newest\") params.set(\"sort\", sortBy);\n\n setSearchParams(params);\n }, [searchTerm, selectedCategories, selectedTags, sortBy, setSearchParams]);\n\n const handleCategoryChange = (categorySlug: string, checked: boolean) => {\n if (checked) {\n setSelectedCategories([...selectedCategories, categorySlug]);\n } else {\n setSelectedCategories(\n selectedCategories.filter((c) => c !== categorySlug)\n );\n }\n };\n\n const handleTagChange = (tag: string, checked: boolean) => {\n if (checked) {\n setSelectedTags([...selectedTags, tag]);\n } else {\n setSelectedTags(selectedTags.filter((t) => t !== tag));\n }\n };\n\n const allTags = Array.from(new Set(posts.flatMap((post) => post.tags)));\n\n const clearFilters = () => {\n setSearchTerm(\"\");\n setSelectedCategories([]);\n setSelectedTags([]);\n setSortBy(\"newest\");\n };\n\n const FilterSection = () => (\n <div className=\"space-y-6\">\n <div>\n <h3 className=\"font-semibold mb-3 flex items-center gap-2\">\n <Search className=\"h-4 w-4\" />\n {t(\"search\")}\n </h3>\n <Input\n placeholder={t(\"searchPlaceholder\")}\n value={searchTerm}\n onChange={(e) => setSearchTerm(e.target.value)}\n />\n </div>\n\n <div>\n <h3 className=\"font-semibold mb-3\">{t(\"categories\")}</h3>\n <div className=\"space-y-2\">\n {categories.map((category) => (\n <div key={category.slug} className=\"flex items-center space-x-2\">\n <Checkbox\n id={`category-${category.slug}`}\n checked={selectedCategories.includes(category.slug)}\n onCheckedChange={(checked) =>\n handleCategoryChange(category.slug, checked as boolean)\n }\n />\n <label\n htmlFor={`category-${category.slug}`}\n className=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer\"\n >\n {category.name}\n </label>\n </div>\n ))}\n </div>\n </div>\n\n {allTags.length > 0 && (\n <div>\n <h3 className=\"font-semibold mb-3\">{t(\"tags\")}</h3>\n <div className=\"space-y-2 max-h-48 overflow-y-auto\">\n {allTags.slice(0, 20).map((tag) => (\n <div key={tag} className=\"flex items-center space-x-2\">\n <Checkbox\n id={`tag-${tag}`}\n checked={selectedTags.includes(tag)}\n onCheckedChange={(checked) =>\n handleTagChange(tag, checked as boolean)\n }\n />\n <label\n htmlFor={`tag-${tag}`}\n className=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer\"\n >\n {tag}\n </label>\n </div>\n ))}\n </div>\n </div>\n )}\n\n {(searchTerm ||\n selectedCategories.length > 0 ||\n selectedTags.length > 0) && (\n <Button variant=\"outline\" onClick={clearFilters} className=\"w-full\">\n {t(\"clearFilters\")}\n </Button>\n )}\n </div>\n );\n\n if (loading) {\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\n <div className=\"animate-pulse space-y-4\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div key={i} className=\"h-48 bg-muted rounded-lg\"></div>\n ))}\n </div>\n </div>\n </Layout>\n );\n }\n\n if (error) {\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8 text-center\">\n <p className=\"text-destructive\">{t(\"error\")}</p>\n </div>\n </Layout>\n );\n }\n\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\n <FadeIn className=\"flex flex-col lg:flex-row justify-between items-start lg:items-center gap-4 mb-8\">\n <div>\n <h1 className=\"text-3xl font-bold mb-2\">{t(\"title\")}</h1>\n <p className=\"text-muted-foreground\">{t(\"subtitle\")}</p>\n </div>\n\n <div className=\"flex items-center gap-4\">\n <Select value={sortBy} onValueChange={setSortBy}>\n <SelectTrigger className=\"w-[180px]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"newest\">{t(\"sortNewest\")}</SelectItem>\n <SelectItem value=\"oldest\">{t(\"sortOldest\")}</SelectItem>\n <SelectItem value=\"popular\">{t(\"sortPopular\")}</SelectItem>\n <SelectItem value=\"reading-time\">\n {t(\"sortReadingTime\")}\n </SelectItem>\n </SelectContent>\n </Select>\n\n <Sheet>\n <SheetTrigger asChild>\n <Button variant=\"outline\" size=\"sm\" className=\"lg:hidden\">\n <Filter className=\"h-4 w-4 mr-2\" />\n {t(\"filters\")}\n </Button>\n </SheetTrigger>\n <SheetContent>\n <SheetHeader>\n <SheetTitle>{t(\"filters\")}</SheetTitle>\n <SheetDescription>{t(\"filterDescription\")}</SheetDescription>\n </SheetHeader>\n <div className=\"mt-6\">\n <FilterSection />\n </div>\n </SheetContent>\n </Sheet>\n </div>\n </FadeIn>\n\n <div className=\"flex flex-col lg:flex-row gap-8\">\n <div className=\"hidden lg:block w-64 flex-shrink-0\">\n <div className=\"sticky top-4\">\n <FilterSection />\n </div>\n </div>\n\n <div className=\"flex-1\">\n <div className=\"flex items-center justify-between mb-6\">\n <p className=\"text-sm text-muted-foreground\">\n {t(\"showing\")} {sortedPosts.length} {t(\"of\")} {posts.length}{\" \"}\n {t(\"posts\")}\n {searchTerm && (\n <span className=\"ml-1\">\n {t(\"for\")} \"<strong>{searchTerm}</strong>\"\n </span>\n )}\n </p>\n </div>\n\n {sortedPosts.length > 0 ? (\n <div\n className={`grid gap-6 ${\n viewMode === \"grid\"\n ? \"grid-cols-1 md:grid-cols-2 xl:grid-cols-3\"\n : \"grid-cols-1\"\n }`}\n >\n {sortedPosts.map((post) => (\n <PostCard key={post.id} post={post} layout={viewMode} />\n ))}\n </div>\n ) : (\n <div className=\"text-center py-12\">\n <p className=\"text-muted-foreground mb-4\">\n {t(\"noPostsFound\")}\n </p>\n <Button onClick={clearFilters} variant=\"outline\">\n {t(\"clearFilters\")}\n </Button>\n </div>\n )}\n </div>\n </div>\n </div>\n </Layout>\n );\n}\n\nexport default BlogListPage;\n"
27
+ "content": "import { useState, useEffect } from \"react\";\nimport { useSearchParams } from \"react-router\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { useTranslation } from \"react-i18next\";\nimport { Layout } from \"@/components/Layout\";\nimport { Search, Filter } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { FadeIn } from \"@/modules/animations\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n SheetTrigger,\n} from \"@/components/ui/sheet\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { PostCard } from \"@/modules/post-card/post-card\";\nimport { usePosts, useBlogCategories, type BlogCategory } from \"@/modules/blog-core\";\n\ninterface FilterSectionProps {\n t: (key: string, fallback?: string) => string;\n searchTerm: string;\n setSearchTerm: (term: string) => void;\n categories: BlogCategory[];\n selectedCategories: string[];\n handleCategoryChange: (slug: string, checked: boolean) => void;\n allTags: string[];\n selectedTags: string[];\n handleTagChange: (tag: string, checked: boolean) => void;\n clearFilters: () => void;\n}\n\nfunction FilterSection({\n t,\n searchTerm,\n setSearchTerm,\n categories,\n selectedCategories,\n handleCategoryChange,\n allTags,\n selectedTags,\n handleTagChange,\n clearFilters,\n}: FilterSectionProps) {\n return (\n <div className=\"space-y-6\">\n <div>\n <h3 className=\"font-semibold mb-3 flex items-center gap-2\">\n <Search className=\"h-4 w-4\" />\n {t(\"search\")}\n </h3>\n <Input\n placeholder={t(\"searchPlaceholder\")}\n value={searchTerm}\n onChange={(e) => setSearchTerm(e.target.value)}\n />\n </div>\n\n <div>\n <h3 className=\"font-semibold mb-3\">{t(\"categories\")}</h3>\n <div className=\"space-y-2\">\n {categories.map((category) => (\n <div key={category.slug} className=\"flex items-center space-x-2\">\n <Checkbox\n id={`category-${category.slug}`}\n checked={selectedCategories.includes(category.slug)}\n onCheckedChange={(checked) =>\n handleCategoryChange(category.slug, checked as boolean)\n }\n />\n <label\n htmlFor={`category-${category.slug}`}\n className=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer\"\n >\n {category.name}\n </label>\n </div>\n ))}\n </div>\n </div>\n\n {allTags.length > 0 && (\n <div>\n <h3 className=\"font-semibold mb-3\">{t(\"tags\")}</h3>\n <div className=\"space-y-2 max-h-48 overflow-y-auto\">\n {allTags.slice(0, 20).map((tag) => (\n <div key={tag} className=\"flex items-center space-x-2\">\n <Checkbox\n id={`tag-${tag}`}\n checked={selectedTags.includes(tag)}\n onCheckedChange={(checked) =>\n handleTagChange(tag, checked as boolean)\n }\n />\n <label\n htmlFor={`tag-${tag}`}\n className=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer\"\n >\n {tag}\n </label>\n </div>\n ))}\n </div>\n </div>\n )}\n\n {(searchTerm ||\n selectedCategories.length > 0 ||\n selectedTags.length > 0) && (\n <Button variant=\"outline\" onClick={clearFilters} className=\"w-full\">\n {t(\"clearFilters\")}\n </Button>\n )}\n </div>\n );\n}\n\nexport function BlogListPage() {\n const { t } = useTranslation(\"blog-list-page\");\n usePageTitle({ title: t(\"title\") });\n\n const [searchParams, setSearchParams] = useSearchParams();\n const [searchTerm, setSearchTerm] = useState(\n searchParams.get(\"search\") || \"\"\n );\n const [selectedCategories, setSelectedCategories] = useState<string[]>(\n searchParams.get(\"categories\")?.split(\",\").filter(Boolean) || []\n );\n const [selectedTags, setSelectedTags] = useState<string[]>(\n searchParams.get(\"tags\")?.split(\",\").filter(Boolean) || []\n );\n const [sortBy, setSortBy] = useState(searchParams.get(\"sort\") || \"newest\");\n const [viewMode, _setViewMode] = useState<\"grid\" | \"list\">(\"grid\");\n\n const { posts, loading, error } = usePosts();\n const { categories } = useBlogCategories();\n\n const filteredPosts = posts.filter((post) => {\n if (searchTerm) {\n const searchLower = searchTerm.toLowerCase();\n if (\n !post.title.toLowerCase().includes(searchLower) &&\n !post.excerpt.toLowerCase().includes(searchLower) &&\n !post.content.toLowerCase().includes(searchLower)\n ) {\n return false;\n }\n }\n\n if (selectedCategories.length > 0) {\n const hasMatchingCategory = selectedCategories.some(\n (categorySlug) =>\n post.category === categorySlug ||\n post.categories?.some((cat) => cat.slug === categorySlug)\n );\n if (!hasMatchingCategory) return false;\n }\n\n if (selectedTags.length > 0) {\n const hasMatchingTag = selectedTags.some((tag) =>\n post.tags.includes(tag)\n );\n if (!hasMatchingTag) return false;\n }\n\n return true;\n });\n\n const sortedPosts = [...filteredPosts].sort((a, b) => {\n switch (sortBy) {\n case \"oldest\":\n return (\n new Date(a.published_at).getTime() -\n new Date(b.published_at).getTime()\n );\n case \"popular\":\n return (b.view_count || 0) - (a.view_count || 0);\n case \"reading-time\":\n return (a.read_time || 0) - (b.read_time || 0);\n case \"newest\":\n default:\n return (\n new Date(b.published_at).getTime() -\n new Date(a.published_at).getTime()\n );\n }\n });\n\n useEffect(() => {\n const params = new URLSearchParams();\n if (searchTerm) params.set(\"search\", searchTerm);\n if (selectedCategories.length)\n params.set(\"categories\", selectedCategories.join(\",\"));\n if (selectedTags.length) params.set(\"tags\", selectedTags.join(\",\"));\n if (sortBy !== \"newest\") params.set(\"sort\", sortBy);\n\n setSearchParams(params);\n }, [searchTerm, selectedCategories, selectedTags, sortBy, setSearchParams]);\n\n const handleCategoryChange = (categorySlug: string, checked: boolean) => {\n if (checked) {\n setSelectedCategories([...selectedCategories, categorySlug]);\n } else {\n setSelectedCategories(\n selectedCategories.filter((c) => c !== categorySlug)\n );\n }\n };\n\n const handleTagChange = (tag: string, checked: boolean) => {\n if (checked) {\n setSelectedTags([...selectedTags, tag]);\n } else {\n setSelectedTags(selectedTags.filter((t) => t !== tag));\n }\n };\n\n const allTags = Array.from(new Set(posts.flatMap((post) => post.tags)));\n\n const clearFilters = () => {\n setSearchTerm(\"\");\n setSelectedCategories([]);\n setSelectedTags([]);\n setSortBy(\"newest\");\n };\n\n const filterProps: FilterSectionProps = {\n t,\n searchTerm,\n setSearchTerm,\n categories,\n selectedCategories,\n handleCategoryChange,\n allTags,\n selectedTags,\n handleTagChange,\n clearFilters,\n };\n\n if (loading) {\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\n <div className=\"animate-pulse space-y-4\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div key={i} className=\"h-48 bg-muted rounded-lg\"></div>\n ))}\n </div>\n </div>\n </Layout>\n );\n }\n\n if (error) {\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8 text-center\">\n <p className=\"text-destructive\">{t(\"error\")}</p>\n </div>\n </Layout>\n );\n }\n\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\n <FadeIn className=\"flex flex-col lg:flex-row justify-between items-start lg:items-center gap-4 mb-8\">\n <div>\n <h1 className=\"text-3xl font-bold mb-2\">{t(\"title\")}</h1>\n <p className=\"text-muted-foreground\">{t(\"subtitle\")}</p>\n </div>\n\n <div className=\"flex items-center gap-4\">\n <Select value={sortBy} onValueChange={setSortBy}>\n <SelectTrigger className=\"w-[180px]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"newest\">{t(\"sortNewest\")}</SelectItem>\n <SelectItem value=\"oldest\">{t(\"sortOldest\")}</SelectItem>\n <SelectItem value=\"popular\">{t(\"sortPopular\")}</SelectItem>\n <SelectItem value=\"reading-time\">\n {t(\"sortReadingTime\")}\n </SelectItem>\n </SelectContent>\n </Select>\n\n <Sheet>\n <SheetTrigger asChild>\n <Button variant=\"outline\" size=\"sm\" className=\"lg:hidden\">\n <Filter className=\"h-4 w-4 mr-2\" />\n {t(\"filters\")}\n </Button>\n </SheetTrigger>\n <SheetContent>\n <SheetHeader>\n <SheetTitle>{t(\"filters\")}</SheetTitle>\n <SheetDescription>{t(\"filterDescription\")}</SheetDescription>\n </SheetHeader>\n <div className=\"mt-6\">\n <FilterSection {...filterProps} />\n </div>\n </SheetContent>\n </Sheet>\n </div>\n </FadeIn>\n\n <div className=\"flex flex-col lg:flex-row gap-8\">\n <div className=\"hidden lg:block w-64 flex-shrink-0\">\n <div className=\"sticky top-4\">\n <FilterSection {...filterProps} />\n </div>\n </div>\n\n <div className=\"flex-1\">\n <div className=\"flex items-center justify-between mb-6\">\n <p className=\"text-sm text-muted-foreground\">\n {t(\"showing\")} {sortedPosts.length} {t(\"of\")} {posts.length}{\" \"}\n {t(\"posts\")}\n {searchTerm && (\n <span className=\"ml-1\">\n {t(\"for\")} \"<strong>{searchTerm}</strong>\"\n </span>\n )}\n </p>\n </div>\n\n {sortedPosts.length > 0 ? (\n <div\n className={`grid gap-6 ${\n viewMode === \"grid\"\n ? \"grid-cols-1 md:grid-cols-2 xl:grid-cols-3\"\n : \"grid-cols-1\"\n }`}\n >\n {sortedPosts.map((post) => (\n <PostCard key={post.id} post={post} layout={viewMode} />\n ))}\n </div>\n ) : (\n <div className=\"text-center py-12\">\n <p className=\"text-muted-foreground mb-4\">\n {t(\"noPostsFound\")}\n </p>\n <Button onClick={clearFilters} variant=\"outline\">\n {t(\"clearFilters\")}\n </Button>\n </div>\n )}\n </div>\n </div>\n </div>\n </Layout>\n );\n}\n\nexport default BlogListPage;\n"
28
28
  },
29
29
  {
30
30
  "path": "blog-list-page/lang/en.json",
@@ -2,10 +2,12 @@
2
2
  "name": "blog-section",
3
3
  "type": "registry:component",
4
4
  "title": "Blog Section",
5
- "description": "Blog posts preview section with 3-column card grid. Features tagline badge, heading, subtitle, and 'view all' link. Each card shows image, category badge, date, title, summary, and 'read more' link. Great for homepage blog previews.",
5
+ "description": "Blog posts preview section with 3-column card grid. Features tagline badge, heading, subtitle, and 'view all' link. Each card shows image, category badge, date, title, summary, and 'read more' link. Integrated with blog-core for data fetching.",
6
6
  "registryDependencies": [
7
7
  "card",
8
- "badge"
8
+ "badge",
9
+ "button",
10
+ "blog-core"
9
11
  ],
10
12
  "usage": "import { BlogSection } from '@/modules/blog-section';\n\n<BlogSection />\n\n- 3-column post cards\n- Category badges and dates\n- Hover effects on images\n- Customize posts via lang files",
11
13
  "files": [
@@ -25,13 +27,13 @@
25
27
  "path": "blog-section/lang/en.json",
26
28
  "type": "registry:lang",
27
29
  "target": "$modules$/blog-section/lang/en.json",
28
- "content": "{\r\n \"tagline\": \"Latest Updates\",\r\n \"title\": \"From Our Blog\",\r\n \"subtitle\": \"Ask Promake to customize this blog section subtitle based on your content strategy. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"viewAll\": \"View all articles\",\r\n \"readMore\": \"Read more\",\r\n \"post1Title\": \"Ask Promake to replace this blog post title\",\r\n \"post1Summary\": \"This is placeholder blog post summary text. Ask Promake to customize this based on your actual blog content. Lorem ipsum dolor sit amet.\",\r\n \"post1Category\": \"Tutorial\",\r\n \"post1Author\": \"Sarah Chen\",\r\n \"post1Date\": \"Jan 15, 2024\",\r\n \"post2Title\": \"Replace this with your second blog post title\",\r\n \"post2Summary\": \"Placeholder summary text. Ask Promake to generate appropriate blog post summaries based on your content. Sed do eiusmod tempor.\",\r\n \"post2Category\": \"Best Practices\",\r\n \"post2Author\": \"Michael Park\",\r\n \"post2Date\": \"Jan 10, 2024\",\r\n \"post3Title\": \"This blog post title will be customized by Promake\",\r\n \"post3Summary\": \"Ask Promake to replace this summary with relevant content based on your blog posts. Ut enim ad minim veniam, quis nostrud exercitation.\",\r\n \"post3Category\": \"Design\",\r\n \"post3Author\": \"Emily Davis\",\r\n \"post3Date\": \"Jan 5, 2024\"\r\n}\r\n"
30
+ "content": "{\r\n \"tagline\": \"Latest Updates\",\r\n \"title\": \"From Our Blog\",\r\n \"subtitle\": \"Use Promake to personalize this subtitle for your content strategy. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"viewAll\": \"View all articles\",\r\n \"readMore\": \"Read more\",\r\n \"post1Title\": \"Customize this blog post title with Promake\",\r\n \"post1Summary\": \"This is placeholder blog post summary text. Let Promake tailor this to your actual blog content. Lorem ipsum dolor sit amet.\",\r\n \"post1Category\": \"Tutorial\",\r\n \"post1Author\": \"Sarah Chen\",\r\n \"post1Date\": \"Jan 15, 2024\",\r\n \"post2Title\": \"Update this with your second blog post title\",\r\n \"post2Summary\": \"Placeholder summary text. Have Promake generate relevant summaries for your posts. Sed do eiusmod tempor.\",\r\n \"post2Category\": \"Best Practices\",\r\n \"post2Author\": \"Michael Park\",\r\n \"post2Date\": \"Jan 10, 2024\",\r\n \"post3Title\": \"Work with Promake to customize this post title\",\r\n \"post3Summary\": \"Edit this summary via Promake based on your blog posts. Ut enim ad minim veniam, quis nostrud exercitation.\",\r\n \"post3Category\": \"Design\",\r\n \"post3Author\": \"Emily Davis\",\r\n \"post3Date\": \"Jan 5, 2024\"\r\n}\r\n"
29
31
  },
30
32
  {
31
33
  "path": "blog-section/lang/tr.json",
32
34
  "type": "registry:lang",
33
35
  "target": "$modules$/blog-section/lang/tr.json",
34
- "content": "{\r\n \"tagline\": \"Son Güncellemeler\",\r\n \"title\": \"Blogumuzdan\",\r\n \"subtitle\": \"AI bu blog bölümü alt başlığını içerik stratejinize göre özelleştirecektir. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"viewAll\": \"Tüm makaleleri görüntüle\",\r\n \"readMore\": \"Devamını oku\",\r\n \"post1Title\": \"AI bu blog yazısı başlığını değiştirecektir\",\r\n \"post1Summary\": \"Bu placeholder blog yazısı özet metnidir. AI bunu gerçek blog içeriğinize göre özelleştirecektir. Lorem ipsum dolor sit amet.\",\r\n \"post1Category\": \"Eğitim\",\r\n \"post1Author\": \"Sarah Chen\",\r\n \"post1Date\": \"15 Ocak 2024\",\r\n \"post2Title\": \"Bunu ikinci blog yazınızın başlığıyla değiştirin\",\r\n \"post2Summary\": \"Placeholder özet metni. AI içeriğinize göre uygun blog yazısı özetleri üretecektir. Sed do eiusmod tempor.\",\r\n \"post2Category\": \"En İyi Uygulamalar\",\r\n \"post2Author\": \"Michael Park\",\r\n \"post2Date\": \"10 Ocak 2024\",\r\n \"post3Title\": \"Bu blog yazısı başlığı AI tarafından özelleştirilecektir\",\r\n \"post3Summary\": \"AI bu özeti blog yazılarınıza göre ilgili içerikle değiştirecektir. Ut enim ad minim veniam, quis nostrud exercitation.\",\r\n \"post3Category\": \"Tasarım\",\r\n \"post3Author\": \"Emily Davis\",\r\n \"post3Date\": \"5 Ocak 2024\"\r\n}\r\n"
36
+ "content": "{\r\n \"tagline\": \"Son Güncellemeler\",\r\n \"title\": \"Blogumuzdan\",\r\n \"subtitle\": \"Bu alt başlığı içerik stratejinize göre Promake kullanarak kişiselleştirin. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"viewAll\": \"Tüm makaleleri görüntüle\",\r\n \"readMore\": \"Devamını oku\",\r\n \"post1Title\": \"Bu blog yazısı başlığını Promake ile özelleştirin\",\r\n \"post1Summary\": \"Bu placeholder blog yazısı özet metnidir. Promake ile gerçek blog içeriğinize göre uyarlayın. Lorem ipsum dolor sit amet.\",\r\n \"post1Category\": \"Eğitim\",\r\n \"post1Author\": \"Sarah Chen\",\r\n \"post1Date\": \"15 Ocak 2024\",\r\n \"post2Title\": \"Bunu ikinci blog yazınızın başlığıyla güncelleyin\",\r\n \"post2Summary\": \"Placeholder özet metni. Promake'ten yazılarınız için uygun özetler üretmesini isteyin. Sed do eiusmod tempor.\",\r\n \"post2Category\": \"En İyi Uygulamalar\",\r\n \"post2Author\": \"Michael Park\",\r\n \"post2Date\": \"10 Ocak 2024\",\r\n \"post3Title\": \"Bu yazı başlığını Promake ile kişiselleştirin\",\r\n \"post3Summary\": \"Bu özeti blog yazılarınıza göre Promake üzerinden düzenleyin. Ut enim ad minim veniam, quis nostrud exercitation.\",\r\n \"post3Category\": \"Tasarım\",\r\n \"post3Author\": \"Emily Davis\",\r\n \"post3Date\": \"5 Ocak 2024\"\r\n}\r\n"
35
37
  }
36
38
  ],
37
39
  "exports": {
@@ -20,7 +20,7 @@
20
20
  "path": "cards-carousel-section/cards-carousel-section.tsx",
21
21
  "type": "registry:component",
22
22
  "target": "$modules$/cards-carousel-section/cards-carousel-section.tsx",
23
- "content": "import { useEffect, useRef, useState, createContext, useContext } from \"react\";\r\nimport type { ReactNode } from \"react\";\r\nimport { motion, AnimatePresence } from \"framer-motion\";\r\nimport { X, ChevronLeft, ChevronRight } from \"lucide-react\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\n// Context for outside click detection\r\nconst CarouselContext = createContext<{\r\n onCardClose: (index: number) => void;\r\n currentIndex: number;\r\n}>({\r\n onCardClose: () => {},\r\n currentIndex: 0,\r\n});\r\n\r\n// Card interface\r\ninterface CardData {\r\n category: string;\r\n title: string;\r\n src: string;\r\n content: ReactNode;\r\n}\r\n\r\n// Card Component\r\nexport function Card({\r\n card,\r\n index,\r\n layout = false,\r\n}: {\r\n card: CardData;\r\n index: number;\r\n layout?: boolean;\r\n}) {\r\n const [open, setOpen] = useState(false);\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const { onCardClose } = useContext(CarouselContext);\r\n\r\n useEffect(() => {\r\n function onKeyDown(event: KeyboardEvent) {\r\n if (event.key === \"Escape\") {\r\n handleClose();\r\n }\r\n }\r\n\r\n if (open) {\r\n document.body.style.overflow = \"hidden\";\r\n window.addEventListener(\"keydown\", onKeyDown);\r\n } else {\r\n document.body.style.overflow = \"auto\";\r\n }\r\n\r\n return () => {\r\n window.removeEventListener(\"keydown\", onKeyDown);\r\n document.body.style.overflow = \"auto\";\r\n };\r\n }, [open]);\r\n\r\n const handleOpen = () => setOpen(true);\r\n const handleClose = () => {\r\n setOpen(false);\r\n onCardClose(index);\r\n };\r\n\r\n return (\r\n <>\r\n <AnimatePresence>\r\n {open && (\r\n <div className=\"fixed inset-0 h-screen z-50 overflow-auto\">\r\n <motion.div\r\n initial={{ opacity: 0 }}\r\n animate={{ opacity: 1 }}\r\n exit={{ opacity: 0 }}\r\n className=\"bg-black/80 backdrop-blur-lg h-full w-full fixed inset-0\"\r\n onClick={handleClose}\r\n />\r\n <motion.div\r\n initial={{ opacity: 0 }}\r\n animate={{ opacity: 1 }}\r\n exit={{ opacity: 0 }}\r\n ref={containerRef}\r\n layoutId={layout ? `card-${card.title}` : undefined}\r\n className=\"max-w-5xl mx-auto bg-white dark:bg-neutral-900 h-fit z-[60] my-10 p-4 md:p-10 rounded-3xl relative\"\r\n >\r\n <button\r\n className=\"sticky top-4 h-8 w-8 right-0 ml-auto bg-black dark:bg-white rounded-full flex items-center justify-center\"\r\n onClick={handleClose}\r\n >\r\n <X className=\"h-6 w-6 text-neutral-100 dark:text-neutral-900\" />\r\n </button>\r\n <motion.p\r\n layoutId={layout ? `category-${card.title}` : undefined}\r\n className=\"text-base font-medium text-black dark:text-white\"\r\n >\r\n {card.category}\r\n </motion.p>\r\n <motion.p\r\n layoutId={layout ? `title-${card.title}` : undefined}\r\n className=\"text-2xl md:text-5xl font-semibold text-neutral-700 mt-4 dark:text-white\"\r\n >\r\n {card.title}\r\n </motion.p>\r\n <div className=\"py-10\">{card.content}</div>\r\n </motion.div>\r\n </div>\r\n )}\r\n </AnimatePresence>\r\n <motion.button\r\n layoutId={layout ? `card-${card.title}` : undefined}\r\n onClick={handleOpen}\r\n className=\"rounded-3xl bg-gray-100 dark:bg-neutral-900 h-80 w-56 md:h-[40rem] md:w-96 overflow-hidden flex flex-col items-start justify-start relative z-10\"\r\n >\r\n <div className=\"absolute h-full top-0 inset-x-0 bg-gradient-to-b from-black/50 via-transparent to-transparent z-30 pointer-events-none\" />\r\n <div className=\"relative z-40 p-8\">\r\n <motion.p\r\n layoutId={layout ? `category-${card.title}` : undefined}\r\n className=\"text-white text-sm md:text-base font-medium text-left\"\r\n >\r\n {card.category}\r\n </motion.p>\r\n <motion.p\r\n layoutId={layout ? `title-${card.title}` : undefined}\r\n className=\"text-white text-xl md:text-3xl font-semibold max-w-xs text-left mt-2\"\r\n >\r\n {card.title}\r\n </motion.p>\r\n </div>\r\n <img\r\n src={card.src}\r\n alt={card.title}\r\n className=\"object-cover absolute z-10 inset-0 w-full h-full\"\r\n />\r\n </motion.button>\r\n </>\r\n );\r\n}\r\n\r\n// Carousel Component\r\nexport function Carousel({ items }: { items: ReactNode[] }) {\r\n const carouselRef = useRef<HTMLDivElement>(null);\r\n const [canScrollLeft, setCanScrollLeft] = useState(false);\r\n const [canScrollRight, setCanScrollRight] = useState(true);\r\n const [currentIndex, setCurrentIndex] = useState(0);\r\n\r\n useEffect(() => {\r\n checkScrollability();\r\n }, []);\r\n\r\n const checkScrollability = () => {\r\n if (carouselRef.current) {\r\n const { scrollLeft, scrollWidth, clientWidth } = carouselRef.current;\r\n setCanScrollLeft(scrollLeft > 0);\r\n setCanScrollRight(scrollLeft < scrollWidth - clientWidth);\r\n }\r\n };\r\n\r\n const scrollLeft = () => {\r\n if (carouselRef.current) {\r\n carouselRef.current.scrollBy({ left: -300, behavior: \"smooth\" });\r\n }\r\n };\r\n\r\n const scrollRight = () => {\r\n if (carouselRef.current) {\r\n carouselRef.current.scrollBy({ left: 300, behavior: \"smooth\" });\r\n }\r\n };\r\n\r\n const handleCardClose = (index: number) => {\r\n if (carouselRef.current) {\r\n const cardWidth = isMobile() ? 230 : 384;\r\n const gap = isMobile() ? 4 : 8;\r\n const scrollPosition = (cardWidth + gap) * (index + 1);\r\n carouselRef.current.scrollTo({\r\n left: scrollPosition,\r\n behavior: \"smooth\",\r\n });\r\n setCurrentIndex(index);\r\n }\r\n };\r\n\r\n const isMobile = () => {\r\n return window && window.innerWidth < 768;\r\n };\r\n\r\n return (\r\n <CarouselContext.Provider value={{ onCardClose: handleCardClose, currentIndex }}>\r\n <div className=\"relative w-full\">\r\n <div\r\n className=\"flex w-full overflow-x-scroll overscroll-x-auto py-10 md:py-20 scroll-smooth [scrollbar-width:none]\"\r\n ref={carouselRef}\r\n onScroll={checkScrollability}\r\n >\r\n <div className=\"absolute right-0 z-[1000] h-auto w-[5%] overflow-hidden bg-gradient-to-l from-background to-transparent pointer-events-none\" />\r\n <div className=\"flex flex-row justify-start gap-4 pl-4 max-w-7xl mx-auto\">\r\n {items.map((item, index) => (\r\n <motion.div\r\n initial={{ opacity: 0, y: 20 }}\r\n animate={{\r\n opacity: 1,\r\n y: 0,\r\n transition: {\r\n duration: 0.5,\r\n delay: 0.2 * index,\r\n ease: \"easeOut\",\r\n },\r\n }}\r\n key={\"card\" + index}\r\n className=\"last:pr-[5%] md:last:pr-[33%] rounded-3xl\"\r\n >\r\n {item}\r\n </motion.div>\r\n ))}\r\n </div>\r\n </div>\r\n <div className=\"flex justify-end gap-2 mr-10\">\r\n <button\r\n className=\"relative z-40 h-10 w-10 rounded-full bg-gray-100 flex items-center justify-center disabled:opacity-50\"\r\n onClick={scrollLeft}\r\n disabled={!canScrollLeft}\r\n >\r\n <ChevronLeft className=\"h-6 w-6 text-gray-500\" />\r\n </button>\r\n <button\r\n className=\"relative z-40 h-10 w-10 rounded-full bg-gray-100 flex items-center justify-center disabled:opacity-50\"\r\n onClick={scrollRight}\r\n disabled={!canScrollRight}\r\n >\r\n <ChevronRight className=\"h-6 w-6 text-gray-500\" />\r\n </button>\r\n </div>\r\n </div>\r\n </CarouselContext.Provider>\r\n );\r\n}\r\n\r\n// Section Component\r\ninterface CardsCarouselSectionProps {\r\n title?: string;\r\n items: CardData[];\r\n className?: string;\r\n}\r\n\r\nexport function CardsCarouselSection({\r\n title,\r\n items,\r\n className,\r\n}: CardsCarouselSectionProps) {\r\n const cards = items.map((card, index) => (\r\n <Card key={card.src} card={card} index={index} />\r\n ));\r\n\r\n return (\r\n <section className={cn(\"w-full py-16 md:py-20\", className)}>\r\n {title && (\r\n <h2 className=\"max-w-7xl pl-4 mx-auto text-xl md:text-5xl font-bold text-neutral-800 dark:text-neutral-200\">\r\n {title}\r\n </h2>\r\n )}\r\n <Carousel items={cards} />\r\n </section>\r\n );\r\n}\r\n"
23
+ "content": "import { useEffect, useRef, useState, createContext, useContext } from \"react\";\r\nimport type { ReactNode } from \"react\";\r\nimport { motion, AnimatePresence } from \"framer-motion\";\r\nimport { X, ChevronLeft, ChevronRight } from \"lucide-react\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\n// Context for outside click detection\r\nconst CarouselContext = createContext<{\r\n onCardClose: (index: number) => void;\r\n currentIndex: number;\r\n}>({\r\n onCardClose: () => {},\r\n currentIndex: 0,\r\n});\r\n\r\n// Card interface\r\ninterface CardData {\r\n category: string;\r\n title: string;\r\n src: string;\r\n content: ReactNode;\r\n}\r\n\r\n// Card Component\r\nexport function Card({\r\n card,\r\n index,\r\n layout = false,\r\n}: {\r\n card: CardData;\r\n index: number;\r\n layout?: boolean;\r\n}) {\r\n const [open, setOpen] = useState(false);\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const { onCardClose } = useContext(CarouselContext);\r\n\r\n const handleOpen = () => setOpen(true);\r\n const handleClose = () => {\r\n setOpen(false);\r\n onCardClose(index);\r\n };\r\n\r\n useEffect(() => {\r\n function onKeyDown(event: KeyboardEvent) {\r\n if (event.key === \"Escape\") {\r\n handleClose();\r\n }\r\n }\r\n\r\n if (open) {\r\n document.body.style.overflow = \"hidden\";\r\n window.addEventListener(\"keydown\", onKeyDown);\r\n } else {\r\n document.body.style.overflow = \"auto\";\r\n }\r\n\r\n return () => {\r\n window.removeEventListener(\"keydown\", onKeyDown);\r\n document.body.style.overflow = \"auto\";\r\n };\r\n }, [open, handleClose]);\r\n\r\n return (\r\n <>\r\n <AnimatePresence>\r\n {open && (\r\n <div className=\"fixed inset-0 h-screen z-50 overflow-auto\">\r\n <motion.div\r\n initial={{ opacity: 0 }}\r\n animate={{ opacity: 1 }}\r\n exit={{ opacity: 0 }}\r\n className=\"bg-black/80 backdrop-blur-lg h-full w-full fixed inset-0\"\r\n onClick={handleClose}\r\n />\r\n <motion.div\r\n initial={{ opacity: 0 }}\r\n animate={{ opacity: 1 }}\r\n exit={{ opacity: 0 }}\r\n ref={containerRef}\r\n layoutId={layout ? `card-${card.title}` : undefined}\r\n className=\"max-w-5xl mx-auto bg-white dark:bg-neutral-900 h-fit z-[60] my-10 p-4 md:p-10 rounded-3xl relative\"\r\n >\r\n <button\r\n className=\"sticky top-4 h-8 w-8 right-0 ml-auto bg-black dark:bg-white rounded-full flex items-center justify-center\"\r\n onClick={handleClose}\r\n >\r\n <X className=\"h-6 w-6 text-neutral-100 dark:text-neutral-900\" />\r\n </button>\r\n <motion.p\r\n layoutId={layout ? `category-${card.title}` : undefined}\r\n className=\"text-base font-medium text-black dark:text-white\"\r\n >\r\n {card.category}\r\n </motion.p>\r\n <motion.p\r\n layoutId={layout ? `title-${card.title}` : undefined}\r\n className=\"text-2xl md:text-5xl font-semibold text-neutral-700 mt-4 dark:text-white\"\r\n >\r\n {card.title}\r\n </motion.p>\r\n <div className=\"py-10\">{card.content}</div>\r\n </motion.div>\r\n </div>\r\n )}\r\n </AnimatePresence>\r\n <motion.button\r\n layoutId={layout ? `card-${card.title}` : undefined}\r\n onClick={handleOpen}\r\n className=\"rounded-3xl bg-gray-100 dark:bg-neutral-900 h-80 w-56 md:h-[40rem] md:w-96 overflow-hidden flex flex-col items-start justify-start relative z-10\"\r\n >\r\n <div className=\"absolute h-full top-0 inset-x-0 bg-gradient-to-b from-black/50 via-transparent to-transparent z-30 pointer-events-none\" />\r\n <div className=\"relative z-40 p-8\">\r\n <motion.p\r\n layoutId={layout ? `category-${card.title}` : undefined}\r\n className=\"text-white text-sm md:text-base font-medium text-left\"\r\n >\r\n {card.category}\r\n </motion.p>\r\n <motion.p\r\n layoutId={layout ? `title-${card.title}` : undefined}\r\n className=\"text-white text-xl md:text-3xl font-semibold max-w-xs text-left mt-2\"\r\n >\r\n {card.title}\r\n </motion.p>\r\n </div>\r\n <img\r\n src={card.src}\r\n alt={card.title}\r\n className=\"object-cover absolute z-10 inset-0 w-full h-full\"\r\n />\r\n </motion.button>\r\n </>\r\n );\r\n}\r\n\r\n// Carousel Component\r\nexport function Carousel({ items }: { items: ReactNode[] }) {\r\n const carouselRef = useRef<HTMLDivElement>(null);\r\n const [canScrollLeft, setCanScrollLeft] = useState(false);\r\n const [canScrollRight, setCanScrollRight] = useState(true);\r\n const [currentIndex, setCurrentIndex] = useState(0);\r\n\r\n const checkScrollability = () => {\r\n if (carouselRef.current) {\r\n const { scrollLeft, scrollWidth, clientWidth } = carouselRef.current;\r\n setCanScrollLeft(scrollLeft > 0);\r\n setCanScrollRight(scrollLeft < scrollWidth - clientWidth);\r\n }\r\n };\r\n\r\n useEffect(() => {\r\n checkScrollability();\r\n }, []);\r\n\r\n const scrollLeft = () => {\r\n if (carouselRef.current) {\r\n carouselRef.current.scrollBy({ left: -300, behavior: \"smooth\" });\r\n }\r\n };\r\n\r\n const scrollRight = () => {\r\n if (carouselRef.current) {\r\n carouselRef.current.scrollBy({ left: 300, behavior: \"smooth\" });\r\n }\r\n };\r\n\r\n const handleCardClose = (index: number) => {\r\n if (carouselRef.current) {\r\n const cardWidth = isMobile() ? 230 : 384;\r\n const gap = isMobile() ? 4 : 8;\r\n const scrollPosition = (cardWidth + gap) * (index + 1);\r\n carouselRef.current.scrollTo({\r\n left: scrollPosition,\r\n behavior: \"smooth\",\r\n });\r\n setCurrentIndex(index);\r\n }\r\n };\r\n\r\n const isMobile = () => {\r\n return window && window.innerWidth < 768;\r\n };\r\n\r\n return (\r\n <CarouselContext.Provider value={{ onCardClose: handleCardClose, currentIndex }}>\r\n <div className=\"relative w-full\">\r\n <div\r\n className=\"flex w-full overflow-x-scroll overscroll-x-auto py-10 md:py-20 scroll-smooth [scrollbar-width:none]\"\r\n ref={carouselRef}\r\n onScroll={checkScrollability}\r\n >\r\n <div className=\"absolute right-0 z-[1000] h-auto w-[5%] overflow-hidden bg-gradient-to-l from-background to-transparent pointer-events-none\" />\r\n <div className=\"flex flex-row justify-start gap-4 pl-4 max-w-7xl mx-auto\">\r\n {items.map((item, index) => (\r\n <motion.div\r\n initial={{ opacity: 0, y: 20 }}\r\n animate={{\r\n opacity: 1,\r\n y: 0,\r\n transition: {\r\n duration: 0.5,\r\n delay: 0.2 * index,\r\n ease: \"easeOut\",\r\n },\r\n }}\r\n key={\"card\" + index}\r\n className=\"last:pr-[5%] md:last:pr-[33%] rounded-3xl\"\r\n >\r\n {item}\r\n </motion.div>\r\n ))}\r\n </div>\r\n </div>\r\n <div className=\"flex justify-end gap-2 mr-10\">\r\n <button\r\n className=\"relative z-40 h-10 w-10 rounded-full bg-gray-100 flex items-center justify-center disabled:opacity-50\"\r\n onClick={scrollLeft}\r\n disabled={!canScrollLeft}\r\n >\r\n <ChevronLeft className=\"h-6 w-6 text-gray-500\" />\r\n </button>\r\n <button\r\n className=\"relative z-40 h-10 w-10 rounded-full bg-gray-100 flex items-center justify-center disabled:opacity-50\"\r\n onClick={scrollRight}\r\n disabled={!canScrollRight}\r\n >\r\n <ChevronRight className=\"h-6 w-6 text-gray-500\" />\r\n </button>\r\n </div>\r\n </div>\r\n </CarouselContext.Provider>\r\n );\r\n}\r\n\r\n// Section Component\r\ninterface CardsCarouselSectionProps {\r\n title?: string;\r\n items: CardData[];\r\n className?: string;\r\n}\r\n\r\nexport function CardsCarouselSection({\r\n title,\r\n items,\r\n className,\r\n}: CardsCarouselSectionProps) {\r\n const cards = items.map((card, index) => (\r\n <Card key={card.src} card={card} index={index} />\r\n ));\r\n\r\n return (\r\n <section className={cn(\"w-full py-16 md:py-20\", className)}>\r\n {title && (\r\n <h2 className=\"max-w-7xl pl-4 mx-auto text-xl md:text-5xl font-bold text-neutral-800 dark:text-neutral-200\">\r\n {title}\r\n </h2>\r\n )}\r\n <Carousel items={cards} />\r\n </section>\r\n );\r\n}\r\n"
24
24
  },
25
25
  {
26
26
  "path": "cards-carousel-section/lang/en.json",
@@ -2,12 +2,13 @@
2
2
  "name": "cart-drawer",
3
3
  "type": "registry:component",
4
4
  "title": "Cart Drawer",
5
- "description": "Shopping cart drawer that slides in from the right. Displays cart items with images, prices, quantities, remove buttons, subtotal calculation, checkout button, and continue shopping link. Uses shadcn Sheet component.",
5
+ "description": "Shopping cart drawer that slides in from the right. Integrated with ecommerce-core store. Displays cart items with images, prices, quantity controls, remove buttons, subtotal calculation, and checkout button.",
6
6
  "registryDependencies": [
7
7
  "sheet",
8
- "button"
8
+ "button",
9
+ "ecommerce-core"
9
10
  ],
10
- "usage": "import { CartDrawer } from '@/modules/cart-drawer';\n\nconst [open, setOpen] = useState(false);\n\n<CartDrawer\n open={open}\n onOpenChange={setOpen}\n items={cartItems}\n onRemove={(id) => removeFromCart(id)}\n checkoutHref=\"/checkout\"\n/>",
11
+ "usage": "import { CartDrawer } from '@/modules/cart-drawer';\n\n// Cart state is managed by ecommerce-core store\n<CartDrawer checkoutHref=\"/checkout\" />\n\n// The drawer opens automatically when items are added to cart\n// Or use useCart hook to control it:\nimport { useCart } from '@/modules/ecommerce-core';\nconst { setDrawerOpen } = useCart();\nsetDrawerOpen(true);",
11
12
  "files": [
12
13
  {
13
14
  "path": "cart-drawer/index.ts",
@@ -19,7 +20,7 @@
19
20
  "path": "cart-drawer/cart-drawer.tsx",
20
21
  "type": "registry:component",
21
22
  "target": "$modules$/cart-drawer/cart-drawer.tsx",
22
- "content": "import { Link } from \"react-router\";\r\nimport { ShoppingCart, Minus, Plus } from \"lucide-react\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport {\r\n Sheet,\r\n SheetContent,\r\n SheetHeader,\r\n SheetTitle,\r\n SheetTrigger,\r\n} from \"@/components/ui/sheet\";\r\nimport { useTranslation } from \"react-i18next\";\r\n\r\ninterface CartItem {\r\n id: string | number;\r\n name: string;\r\n href: string;\r\n color?: string;\r\n price: number;\r\n quantity: number;\r\n image: string;\r\n imageAlt?: string;\r\n}\r\n\r\ninterface CartDrawerProps {\r\n items: CartItem[];\r\n currency?: string;\r\n onRemove?: (id: string | number) => void;\r\n onUpdateQuantity?: (id: string | number, quantity: number) => void;\r\n checkoutHref?: string;\r\n open?: boolean;\r\n onOpenChange?: (open: boolean) => void;\r\n /** Hide trigger button when using controlled mode (default: shows trigger only when open is undefined) */\r\n showTrigger?: boolean;\r\n}\r\n\r\nexport function CartDrawer({\r\n items,\r\n currency = \"$\",\r\n onRemove,\r\n onUpdateQuantity,\r\n checkoutHref = \"/checkout\",\r\n open,\r\n onOpenChange,\r\n showTrigger,\r\n}: CartDrawerProps) {\r\n // Hide trigger in controlled mode (when open is provided) unless explicitly shown\r\n const shouldShowTrigger = showTrigger ?? (open === undefined);\r\n const { t } = useTranslation(\"cart-drawer\");\r\n\r\n const subtotal = items.reduce(\r\n (sum, item) => sum + item.price * item.quantity,\r\n 0\r\n );\r\n\r\n return (\r\n <Sheet open={open} onOpenChange={onOpenChange}>\r\n {shouldShowTrigger && (\r\n <SheetTrigger asChild>\r\n <Button variant=\"ghost\" size=\"icon\" className=\"relative\">\r\n <ShoppingCart className=\"h-5 w-5\" />\r\n {items.length > 0 && (\r\n <span className=\"absolute -top-1 -right-1 h-5 w-5 rounded-full bg-primary text-primary-foreground text-xs flex items-center justify-center\">\r\n {items.length}\r\n </span>\r\n )}\r\n </Button>\r\n </SheetTrigger>\r\n )}\r\n <SheetContent className=\"w-full sm:max-w-md flex flex-col px-6 pb-8\">\r\n <SheetHeader>\r\n <SheetTitle>{t(\"title\", \"Shopping cart\")}</SheetTitle>\r\n </SheetHeader>\r\n\r\n <div className=\"flex-1 overflow-y-auto mt-8\">\r\n {items.length === 0 ? (\r\n <p className=\"text-center text-muted-foreground py-8\">\r\n {t(\"empty\", \"Your cart is empty\")}\r\n </p>\r\n ) : (\r\n <ul className=\"-my-6 divide-y divide-border\">\r\n {items.map((item) => (\r\n <li key={item.id} className=\"flex py-6\">\r\n <div className=\"size-24 shrink-0 overflow-hidden rounded-md border border-border\">\r\n <img\r\n alt={item.imageAlt || item.name}\r\n src={item.image}\r\n className=\"size-full object-cover\"\r\n />\r\n </div>\r\n\r\n <div className=\"ml-4 flex flex-1 flex-col\">\r\n <div>\r\n <div className=\"flex justify-between text-base font-medium\">\r\n <h3>\r\n <Link to={item.href}>{item.name}</Link>\r\n </h3>\r\n <p className=\"ml-4\">\r\n {currency}{item.price.toFixed(2)}\r\n </p>\r\n </div>\r\n {item.color && (\r\n <p className=\"mt-1 text-sm text-muted-foreground\">\r\n {item.color}\r\n </p>\r\n )}\r\n </div>\r\n <div className=\"flex flex-1 items-end justify-between text-sm\">\r\n <div className=\"flex items-center gap-1\">\r\n <Button\r\n variant=\"outline\"\r\n size=\"icon\"\r\n className=\"h-6 w-6\"\r\n onClick={() => onUpdateQuantity?.(item.id, item.quantity - 1)}\r\n >\r\n <Minus className=\"h-3 w-3\" />\r\n </Button>\r\n <span className=\"w-8 text-center text-sm\">{item.quantity}</span>\r\n <Button\r\n variant=\"outline\"\r\n size=\"icon\"\r\n className=\"h-6 w-6\"\r\n onClick={() => onUpdateQuantity?.(item.id, item.quantity + 1)}\r\n >\r\n <Plus className=\"h-3 w-3\" />\r\n </Button>\r\n </div>\r\n\r\n <button\r\n type=\"button\"\r\n onClick={() => onRemove?.(item.id)}\r\n className=\"font-medium text-primary hover:text-primary/80\"\r\n >\r\n {t(\"remove\", \"Remove\")}\r\n </button>\r\n </div>\r\n </div>\r\n </li>\r\n ))}\r\n </ul>\r\n )}\r\n </div>\r\n\r\n <div className=\"border-t border-border pt-6 mt-6\">\r\n <div className=\"flex justify-between text-base font-medium\">\r\n <p>{t(\"subtotal\", \"Subtotal\")}</p>\r\n <p>{currency}{subtotal.toFixed(2)}</p>\r\n </div>\r\n <p className=\"mt-0.5 text-sm text-muted-foreground\">\r\n {t(\"shippingNote\", \"Shipping and taxes calculated at checkout.\")}\r\n </p>\r\n <div className=\"mt-6\">\r\n <Button asChild className=\"w-full\">\r\n <Link to={checkoutHref}>{t(\"checkout\", \"Checkout\")}</Link>\r\n </Button>\r\n </div>\r\n </div>\r\n </SheetContent>\r\n </Sheet>\r\n );\r\n}\r\n"
23
+ "content": "import { Link } from \"react-router\";\r\nimport { Minus, Plus } from \"lucide-react\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport {\r\n Sheet,\r\n SheetContent,\r\n SheetHeader,\r\n SheetTitle,\r\n} from \"@/components/ui/sheet\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { useCart, formatPrice } from \"@/modules/ecommerce-core\";\r\nimport constants from \"@/constants/constants.json\";\r\n\r\ninterface CartDrawerProps {\r\n checkoutHref?: string;\r\n className?: string;\r\n}\r\n\r\nexport function CartDrawer({\r\n checkoutHref = \"/checkout\",\r\n className,\r\n}: CartDrawerProps) {\r\n const { t } = useTranslation(\"cart-drawer\");\r\n const {\r\n state,\r\n removeItem,\r\n updateQuantity,\r\n isDrawerOpen,\r\n setDrawerOpen,\r\n } = useCart();\r\n const { items, total } = state;\r\n const currency = (constants.site as any).currency || \"USD\";\r\n\r\n const getProductPrice = (product: {\r\n price: number;\r\n sale_price?: number;\r\n on_sale?: boolean;\r\n }) => {\r\n return product.on_sale && product.sale_price\r\n ? product.sale_price\r\n : product.price;\r\n };\r\n\r\n const handleQuantityChange = (id: string | number, newQuantity: number) => {\r\n if (newQuantity <= 0) {\r\n removeItem(id);\r\n } else {\r\n updateQuantity(id, newQuantity);\r\n }\r\n };\r\n\r\n return (\r\n <Sheet open={isDrawerOpen} onOpenChange={setDrawerOpen} className={className}>\r\n <SheetContent className=\"w-full sm:max-w-md flex flex-col px-6 pb-8\">\r\n <SheetHeader>\r\n <SheetTitle>{t(\"title\", \"Shopping cart\")}</SheetTitle>\r\n </SheetHeader>\r\n\r\n <div className=\"flex-1 overflow-y-auto mt-8\">\r\n {items.length === 0 ? (\r\n <p className=\"text-center text-muted-foreground py-8\">\r\n {t(\"empty\", \"Your cart is empty\")}\r\n </p>\r\n ) : (\r\n <ul className=\"-my-6 divide-y divide-border\">\r\n {items.map((item) => (\r\n <li key={item.id} className=\"flex py-6\">\r\n <div className=\"size-24 shrink-0 overflow-hidden rounded-md border border-border\">\r\n <img\r\n alt={item.product.name}\r\n src={item.product.images[0] || \"/images/placeholder.png\"}\r\n className=\"size-full object-cover\"\r\n />\r\n </div>\r\n\r\n <div className=\"ml-4 flex flex-1 flex-col\">\r\n <div>\r\n <div className=\"flex justify-between text-base font-medium\">\r\n <h3>\r\n <Link\r\n to={`/products/${item.product.slug}`}\r\n onClick={() => setDrawerOpen(false)}\r\n >\r\n {item.product.name}\r\n </Link>\r\n </h3>\r\n <p className=\"ml-4\">\r\n {formatPrice(getProductPrice(item.product), currency)}\r\n </p>\r\n </div>\r\n {item.product.category_name && (\r\n <p className=\"mt-1 text-sm text-muted-foreground\">\r\n {item.product.category_name}\r\n </p>\r\n )}\r\n </div>\r\n <div className=\"flex flex-1 items-end justify-between text-sm\">\r\n <div className=\"flex items-center gap-1\">\r\n <Button\r\n variant=\"outline\"\r\n size=\"icon\"\r\n className=\"h-6 w-6\"\r\n onClick={() =>\r\n handleQuantityChange(item.id, item.quantity - 1)\r\n }\r\n >\r\n <Minus className=\"h-3 w-3\" />\r\n </Button>\r\n <span className=\"w-8 text-center text-sm\">\r\n {item.quantity}\r\n </span>\r\n <Button\r\n variant=\"outline\"\r\n size=\"icon\"\r\n className=\"h-6 w-6\"\r\n onClick={() =>\r\n handleQuantityChange(item.id, item.quantity + 1)\r\n }\r\n >\r\n <Plus className=\"h-3 w-3\" />\r\n </Button>\r\n </div>\r\n\r\n <button\r\n type=\"button\"\r\n onClick={() => removeItem(item.id)}\r\n className=\"font-medium text-primary hover:text-primary/80\"\r\n >\r\n {t(\"remove\", \"Remove\")}\r\n </button>\r\n </div>\r\n </div>\r\n </li>\r\n ))}\r\n </ul>\r\n )}\r\n </div>\r\n\r\n <div className=\"border-t border-border pt-6 mt-6\">\r\n <div className=\"flex justify-between text-base font-medium\">\r\n <p>{t(\"subtotal\", \"Subtotal\")}</p>\r\n <p>{formatPrice(total, currency)}</p>\r\n </div>\r\n <p className=\"mt-0.5 text-sm text-muted-foreground\">\r\n {t(\"shippingNote\", \"Shipping and taxes calculated at checkout.\")}\r\n </p>\r\n <div className=\"mt-6\">\r\n <Button asChild className=\"w-full\" disabled={items.length === 0}>\r\n <Link to={checkoutHref} onClick={() => setDrawerOpen(false)}>\r\n {t(\"checkout\", \"Checkout\")}\r\n </Link>\r\n </Button>\r\n </div>\r\n </div>\r\n </SheetContent>\r\n </Sheet>\r\n );\r\n}\r\n"
23
24
  },
24
25
  {
25
26
  "path": "cart-drawer/lang/en.json",
@@ -29,13 +29,13 @@
29
29
  "path": "case-study-page/lang/en.json",
30
30
  "type": "registry:lang",
31
31
  "target": "$modules$/case-study-page/lang/en.json",
32
- "content": "{\r\n \"pageTitle\": \"Case Study\",\r\n \"title\": \"E-commerce Platform Redesign\",\r\n \"subtitle\": \"Ask Promake to customize this case study based on your actual project.\",\r\n \"sections\": {\r\n \"challenge\": {\r\n \"title\": \"The Challenge\",\r\n \"content\": \"The client needed a complete overhaul of their e-commerce platform to improve user experience, increase conversion rates, and modernize their brand presence. The existing platform was outdated, had poor mobile support, and suffered from slow load times that were affecting sales.\"\r\n },\r\n \"solution\": {\r\n \"title\": \"Our Solution\",\r\n \"content\": \"We designed and developed a modern, responsive e-commerce platform with a focus on performance and user experience. Key improvements included a streamlined checkout process, advanced product filtering, personalized recommendations, and a complete visual redesign that aligned with the client's evolved brand identity.\"\r\n },\r\n \"results\": {\r\n \"title\": \"The Results\",\r\n \"content\": \"After launch, the client saw a 45% increase in conversion rates, 60% improvement in page load times, and a 35% increase in mobile sales. Customer satisfaction scores improved by 28%, and the average order value increased by 22%.\"\r\n }\r\n },\r\n \"gallery\": \"Project Gallery\",\r\n \"projectDetails\": \"Project Details\",\r\n \"labels\": {\r\n \"client\": \"Client\",\r\n \"date\": \"Date\",\r\n \"category\": \"Category\",\r\n \"services\": \"Services\"\r\n },\r\n \"details\": {\r\n \"client\": \"Acme Corporation\",\r\n \"date\": \"January 2024\",\r\n \"category\": \"Web Design\",\r\n \"services\": [\"UX Design\", \"UI Design\", \"Development\"]\r\n },\r\n \"visitWebsite\": \"Visit Website\",\r\n \"share\": \"Share This Project\",\r\n \"navigation\": {\r\n \"prev\": \"Previous Project\",\r\n \"next\": \"Next Project\"\r\n },\r\n \"prev\": \"Prev\",\r\n \"next\": \"Next\",\r\n \"allProjects\": \"All Projects\"\r\n}\r\n"
32
+ "content": "{\r\n \"pageTitle\": \"Case Study\",\r\n \"title\": \"E-commerce Platform Redesign\",\r\n \"subtitle\": \"Let Promake tailor this case study to your actual project.\",\r\n \"sections\": {\r\n \"challenge\": {\r\n \"title\": \"The Challenge\",\r\n \"content\": \"The client needed a complete overhaul of their e-commerce platform to improve user experience, increase conversion rates, and modernize their brand presence. The existing platform was outdated, had poor mobile support, and suffered from slow load times that were affecting sales.\"\r\n },\r\n \"solution\": {\r\n \"title\": \"Our Solution\",\r\n \"content\": \"We designed and developed a modern, responsive e-commerce platform with a focus on performance and user experience. Key improvements included a streamlined checkout process, advanced product filtering, personalized recommendations, and a complete visual redesign that aligned with the client's evolved brand identity.\"\r\n },\r\n \"results\": {\r\n \"title\": \"The Results\",\r\n \"content\": \"After launch, the client saw a 45% increase in conversion rates, 60% improvement in page load times, and a 35% increase in mobile sales. Customer satisfaction scores improved by 28%, and the average order value increased by 22%.\"\r\n }\r\n },\r\n \"gallery\": \"Project Gallery\",\r\n \"projectDetails\": \"Project Details\",\r\n \"labels\": {\r\n \"client\": \"Client\",\r\n \"date\": \"Date\",\r\n \"category\": \"Category\",\r\n \"services\": \"Services\"\r\n },\r\n \"details\": {\r\n \"client\": \"Acme Corporation\",\r\n \"date\": \"January 2024\",\r\n \"category\": \"Web Design\",\r\n \"services\": [\"UX Design\", \"UI Design\", \"Development\"]\r\n },\r\n \"visitWebsite\": \"Visit Website\",\r\n \"share\": \"Share This Project\",\r\n \"navigation\": {\r\n \"prev\": \"Previous Project\",\r\n \"next\": \"Next Project\"\r\n },\r\n \"prev\": \"Prev\",\r\n \"next\": \"Next\",\r\n \"allProjects\": \"All Projects\"\r\n}\r\n"
33
33
  },
34
34
  {
35
35
  "path": "case-study-page/lang/tr.json",
36
36
  "type": "registry:lang",
37
37
  "target": "$modules$/case-study-page/lang/tr.json",
38
- "content": "{\r\n \"pageTitle\": \"Vaka Çalışması\",\r\n \"title\": \"E-ticaret Platformu Yenileme\",\r\n \"subtitle\": \"Bu vaka çalışmasını gerçek projenize göre özelleştirmek için Promake'e sorun.\",\r\n \"sections\": {\r\n \"challenge\": {\r\n \"title\": \"Zorluk\",\r\n \"content\": \"Müşteri, kullanıcı deneyimini iyileştirmek, dönüşüm oranlarını artırmak ve marka varlığını modernize etmek için e-ticaret platformunun tamamen yenilenmesini istedi. Mevcut platform eskimişti, mobil desteği zayıftı ve satışları etkileyen yavaş yükleme süreleri yaşıyordu.\"\r\n },\r\n \"solution\": {\r\n \"title\": \"Çözümümüz\",\r\n \"content\": \"Performans ve kullanıcı deneyimine odaklanan modern, duyarlı bir e-ticaret platformu tasarladık ve geliştirdik. Temel iyileştirmeler arasında basitleştirilmiş ödeme süreci, gelişmiş ürün filtreleme, kişiselleştirilmiş öneriler ve müşterinin gelişen marka kimliğiyle uyumlu tam bir görsel yenileme yer aldı.\"\r\n },\r\n \"results\": {\r\n \"title\": \"Sonuçlar\",\r\n \"content\": \"Lansmandan sonra müşteri, dönüşüm oranlarında %45 artış, sayfa yükleme sürelerinde %60 iyileşme ve mobil satışlarda %35 artış gördü. Müşteri memnuniyeti puanları %28 iyileşti ve ortalama sipariş değeri %22 arttı.\"\r\n }\r\n },\r\n \"gallery\": \"Proje Galerisi\",\r\n \"projectDetails\": \"Proje Detayları\",\r\n \"labels\": {\r\n \"client\": \"Müşteri\",\r\n \"date\": \"Tarih\",\r\n \"category\": \"Kategori\",\r\n \"services\": \"Hizmetler\"\r\n },\r\n \"details\": {\r\n \"client\": \"Acme Corporation\",\r\n \"date\": \"Ocak 2024\",\r\n \"category\": \"Web Tasarımı\",\r\n \"services\": [\"UX Tasarımı\", \"UI Tasarımı\", \"Geliştirme\"]\r\n },\r\n \"visitWebsite\": \"Web Sitesini Ziyaret Et\",\r\n \"share\": \"Bu Projeyi Paylaş\",\r\n \"navigation\": {\r\n \"prev\": \"Önceki Proje\",\r\n \"next\": \"Sonraki Proje\"\r\n },\r\n \"prev\": \"Önceki\",\r\n \"next\": \"Sonraki\",\r\n \"allProjects\": \"Tüm Projeler\"\r\n}\r\n"
38
+ "content": "{\r\n \"pageTitle\": \"Vaka Çalışması\",\r\n \"title\": \"E-ticaret Platformu Yenileme\",\r\n \"subtitle\": \"Promake ile bu vaka çalışmasını gerçek projenize göre uyarlayın.\",\r\n \"sections\": {\r\n \"challenge\": {\r\n \"title\": \"Zorluk\",\r\n \"content\": \"Müşteri, kullanıcı deneyimini iyileştirmek, dönüşüm oranlarını artırmak ve marka varlığını modernize etmek için e-ticaret platformunun tamamen yenilenmesini istedi. Mevcut platform eskimişti, mobil desteği zayıftı ve satışları etkileyen yavaş yükleme süreleri yaşıyordu.\"\r\n },\r\n \"solution\": {\r\n \"title\": \"Çözümümüz\",\r\n \"content\": \"Performans ve kullanıcı deneyimine odaklanan modern, duyarlı bir e-ticaret platformu tasarladık ve geliştirdik. Temel iyileştirmeler arasında basitleştirilmiş ödeme süreci, gelişmiş ürün filtreleme, kişiselleştirilmiş öneriler ve müşterinin gelişen marka kimliğiyle uyumlu tam bir görsel yenileme yer aldı.\"\r\n },\r\n \"results\": {\r\n \"title\": \"Sonuçlar\",\r\n \"content\": \"Lansmandan sonra müşteri, dönüşüm oranlarında %45 artış, sayfa yükleme sürelerinde %60 iyileşme ve mobil satışlarda %35 artış gördü. Müşteri memnuniyeti puanları %28 iyileşti ve ortalama sipariş değeri %22 arttı.\"\r\n }\r\n },\r\n \"gallery\": \"Proje Galerisi\",\r\n \"projectDetails\": \"Proje Detayları\",\r\n \"labels\": {\r\n \"client\": \"Müşteri\",\r\n \"date\": \"Tarih\",\r\n \"category\": \"Kategori\",\r\n \"services\": \"Hizmetler\"\r\n },\r\n \"details\": {\r\n \"client\": \"Acme Corporation\",\r\n \"date\": \"Ocak 2024\",\r\n \"category\": \"Web Tasarımı\",\r\n \"services\": [\"UX Tasarımı\", \"UI Tasarımı\", \"Geliştirme\"]\r\n },\r\n \"visitWebsite\": \"Web Sitesini Ziyaret Et\",\r\n \"share\": \"Bu Projeyi Paylaş\",\r\n \"navigation\": {\r\n \"prev\": \"Önceki Proje\",\r\n \"next\": \"Sonraki Proje\"\r\n },\r\n \"prev\": \"Önceki\",\r\n \"next\": \"Sonraki\",\r\n \"allProjects\": \"Tüm Projeler\"\r\n}\r\n"
39
39
  }
40
40
  ],
41
41
  "exports": {
@@ -26,7 +26,7 @@
26
26
  "path": "coming-soon-page-minimal/lang/en.json",
27
27
  "type": "registry:lang",
28
28
  "target": "$modules$/coming-soon-page-minimal/lang/en.json",
29
- "content": "{\r\n \"title\": \"Coming Soon\",\r\n \"daysLeft\": \"days until launch\",\r\n \"heading\": \"Coming Soon\",\r\n \"description\": \"Ask Promake to customize this description. We're crafting something beautiful.\",\r\n \"emailPlaceholder\": \"your@email.com\",\r\n \"success\": \"We'll be in touch!\"\r\n}\r\n"
29
+ "content": "{\r\n \"title\": \"Coming Soon\",\r\n \"daysLeft\": \"days until launch\",\r\n \"heading\": \"Coming Soon\",\r\n \"description\": \"Use Promake to personalize this description. We're crafting something beautiful.\",\r\n \"emailPlaceholder\": \"your@email.com\",\r\n \"success\": \"We'll be in touch!\"\r\n}\r\n"
30
30
  },
31
31
  {
32
32
  "path": "coming-soon-page-minimal/lang/tr.json",
@@ -28,7 +28,7 @@
28
28
  "path": "coming-soon-page/lang/en.json",
29
29
  "type": "registry:lang",
30
30
  "target": "$modules$/coming-soon-page/lang/en.json",
31
- "content": "{\r\n \"title\": \"Coming Soon\",\r\n \"heading\": \"Something Amazing is Coming\",\r\n \"description\": \"Ask Promake to customize this description based on your upcoming launch. Lorem ipsum dolor sit amet.\",\r\n \"days\": \"Days\",\r\n \"hours\": \"Hours\",\r\n \"minutes\": \"Minutes\",\r\n \"seconds\": \"Seconds\",\r\n \"emailPlaceholder\": \"Enter your email\",\r\n \"notify\": \"Notify Me\",\r\n \"success\": \"Thank you! We'll notify you when we launch.\",\r\n \"followUs\": \"Follow us for updates\"\r\n}\r\n"
31
+ "content": "{\r\n \"title\": \"Coming Soon\",\r\n \"heading\": \"Something Amazing is Coming\",\r\n \"description\": \"Let Promake tailor this description to your upcoming launch. Lorem ipsum dolor sit amet.\",\r\n \"days\": \"Days\",\r\n \"hours\": \"Hours\",\r\n \"minutes\": \"Minutes\",\r\n \"seconds\": \"Seconds\",\r\n \"emailPlaceholder\": \"Enter your email\",\r\n \"notify\": \"Notify Me\",\r\n \"success\": \"Thank you! We'll notify you when we launch.\",\r\n \"followUs\": \"Follow us for updates\"\r\n}\r\n"
32
32
  },
33
33
  {
34
34
  "path": "coming-soon-page/lang/tr.json",
@@ -22,13 +22,13 @@
22
22
  "path": "contact-info-grid/lang/en.json",
23
23
  "type": "registry:lang",
24
24
  "target": "$modules$/contact-info-grid/lang/en.json",
25
- "content": "{\r\n \"title\": \"Get in Touch\",\r\n \"subtitle\": \"Ask Promake to customize this contact subtitle based on your site. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"emailLabel\": \"Email\",\r\n \"emailDesc\": \"We respond to all emails within 24 hours.\",\r\n \"officeLabel\": \"Office\",\r\n \"officeDesc\": \"Drop by our office for a chat.\",\r\n \"phoneLabel\": \"Phone\",\r\n \"phoneDesc\": \"We're available Mon-Fri, 9am-5pm.\",\r\n \"chatLabel\": \"Live Chat\",\r\n \"chatDesc\": \"Get instant help from our support team.\",\r\n \"startChat\": \"Start Chat\"\r\n}\r\n"
25
+ "content": "{\r\n \"title\": \"Get in Touch\",\r\n \"subtitle\": \"Let Promake personalize this contact subtitle for your brand. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"emailLabel\": \"Email\",\r\n \"emailDesc\": \"We respond to all emails within 24 hours.\",\r\n \"officeLabel\": \"Office\",\r\n \"officeDesc\": \"Drop by our office for a chat.\",\r\n \"phoneLabel\": \"Phone\",\r\n \"phoneDesc\": \"We're available Mon-Fri, 9am-5pm.\",\r\n \"chatLabel\": \"Live Chat\",\r\n \"chatDesc\": \"Get instant help from our support team.\",\r\n \"startChat\": \"Start Chat\"\r\n}\r\n"
26
26
  },
27
27
  {
28
28
  "path": "contact-info-grid/lang/tr.json",
29
29
  "type": "registry:lang",
30
30
  "target": "$modules$/contact-info-grid/lang/tr.json",
31
- "content": "{\r\n \"title\": \"İletişime Geçin\",\r\n \"subtitle\": \"AI bu iletişim alt başlığını sitenize göre özelleştirecektir. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"emailLabel\": \"E-posta\",\r\n \"emailDesc\": \"Tüm e-postalara 24 saat içinde yanıt veriyoruz.\",\r\n \"officeLabel\": \"Ofis\",\r\n \"officeDesc\": \"Sohbet etmek için ofisimize uğrayın.\",\r\n \"phoneLabel\": \"Telefon\",\r\n \"phoneDesc\": \"Pazartesi-Cuma, 9:00-17:00 arası müsaitiz.\",\r\n \"chatLabel\": \"Canlı Sohbet\",\r\n \"chatDesc\": \"Destek ekibimizden anında yardım alın.\",\r\n \"startChat\": \"Sohbet Başlat\"\r\n}\r\n"
31
+ "content": "{\r\n \"title\": \"İletişime Geçin\",\r\n \"subtitle\": \"Promake ile bu iletişim alt başlığını markanız için kişiselleştirin. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"emailLabel\": \"E-posta\",\r\n \"emailDesc\": \"Tüm e-postalara 24 saat içinde yanıt veriyoruz.\",\r\n \"officeLabel\": \"Ofis\",\r\n \"officeDesc\": \"Sohbet etmek için ofisimize uğrayın.\",\r\n \"phoneLabel\": \"Telefon\",\r\n \"phoneDesc\": \"Pazartesi-Cuma, 9:00-17:00 arası müsaitiz.\",\r\n \"chatLabel\": \"Canlı Sohbet\",\r\n \"chatDesc\": \"Destek ekibimizden anında yardım alın.\",\r\n \"startChat\": \"Sohbet Başlat\"\r\n}\r\n"
32
32
  }
33
33
  ],
34
34
  "exports": {
@@ -31,13 +31,13 @@
31
31
  "path": "contact-page-centered/lang/en.json",
32
32
  "type": "registry:lang",
33
33
  "target": "$modules$/contact-page-centered/lang/en.json",
34
- "content": "{\r\n \"title\": \"Contact Us\",\r\n \"subtitle\": \"Ask Promake to customize this contact subtitle. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor.\",\r\n \"emailTitle\": \"Email\",\r\n \"phoneTitle\": \"Phone\",\r\n \"addressTitle\": \"Address\",\r\n \"nameLabel\": \"Name\",\r\n \"namePlaceholder\": \"Your name\",\r\n \"emailLabel\": \"Email\",\r\n \"emailPlaceholder\": \"your@email.com\",\r\n \"messageLabel\": \"Message\",\r\n \"messagePlaceholder\": \"How can we help you?\",\r\n \"submit\": \"Send Message\",\r\n \"sending\": \"Sending...\",\r\n \"success\": \"Message sent successfully! We'll get back to you soon.\",\r\n \"error\": \"Something went wrong. Please try again.\"\r\n}\r\n"
34
+ "content": "{\r\n \"title\": \"Contact Us\",\r\n \"subtitle\": \"Use Promake to personalize this contact subtitle. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor.\",\r\n \"emailTitle\": \"Email\",\r\n \"phoneTitle\": \"Phone\",\r\n \"addressTitle\": \"Address\",\r\n \"nameLabel\": \"Name\",\r\n \"namePlaceholder\": \"Your name\",\r\n \"emailLabel\": \"Email\",\r\n \"emailPlaceholder\": \"your@email.com\",\r\n \"messageLabel\": \"Message\",\r\n \"messagePlaceholder\": \"How can we help you?\",\r\n \"submit\": \"Send Message\",\r\n \"sending\": \"Sending...\",\r\n \"success\": \"Message sent successfully! We'll get back to you soon.\",\r\n \"error\": \"Something went wrong. Please try again.\"\r\n}\r\n"
35
35
  },
36
36
  {
37
37
  "path": "contact-page-centered/lang/tr.json",
38
38
  "type": "registry:lang",
39
39
  "target": "$modules$/contact-page-centered/lang/tr.json",
40
- "content": "{\r\n \"title\": \"İletişim\",\r\n \"subtitle\": \"AI bu iletişim alt başlığını özelleştirecektir. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor.\",\r\n \"emailTitle\": \"E-posta\",\r\n \"phoneTitle\": \"Telefon\",\r\n \"addressTitle\": \"Adres\",\r\n \"nameLabel\": \"İsim\",\r\n \"namePlaceholder\": \"Adınız\",\r\n \"emailLabel\": \"E-posta\",\r\n \"emailPlaceholder\": \"email@adresiniz.com\",\r\n \"messageLabel\": \"Mesaj\",\r\n \"messagePlaceholder\": \"Size nasıl yardımcı olabiliriz?\",\r\n \"submit\": \"Mesaj Gönder\",\r\n \"sending\": \"Gönderiliyor...\",\r\n \"success\": \"Mesajınız başarıyla gönderildi! En kısa sürede size döneceğiz.\",\r\n \"error\": \"Bir şeyler yanlış gitti. Lütfen tekrar deneyin.\"\r\n}\r\n"
40
+ "content": "{\r\n \"title\": \"İletişim\",\r\n \"subtitle\": \"Bu iletişim alt başlığını kişiselleştirmek için Promake kullanın. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor.\",\r\n \"emailTitle\": \"E-posta\",\r\n \"phoneTitle\": \"Telefon\",\r\n \"addressTitle\": \"Adres\",\r\n \"nameLabel\": \"İsim\",\r\n \"namePlaceholder\": \"Adınız\",\r\n \"emailLabel\": \"E-posta\",\r\n \"emailPlaceholder\": \"email@adresiniz.com\",\r\n \"messageLabel\": \"Mesaj\",\r\n \"messagePlaceholder\": \"Size nasıl yardımcı olabiliriz?\",\r\n \"submit\": \"Mesaj Gönder\",\r\n \"sending\": \"Gönderiliyor...\",\r\n \"success\": \"Mesajınız başarıyla gönderildi! En kısa sürede size döneceğiz.\",\r\n \"error\": \"Bir şeyler yanlış gitti. Lütfen tekrar deneyin.\"\r\n}\r\n"
41
41
  }
42
42
  ],
43
43
  "exports": {
@@ -30,13 +30,13 @@
30
30
  "path": "contact-page-split/lang/en.json",
31
31
  "type": "registry:lang",
32
32
  "target": "$modules$/contact-page-split/lang/en.json",
33
- "content": "{\r\n \"pageTitle\": \"Contact Us\",\r\n \"title\": \"Let's Start a Conversation\",\r\n \"subtitle\": \"Ask Promake to customize this subtitle based on your contact page purpose. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"emailLabel\": \"Email\",\r\n \"phoneLabel\": \"Phone\",\r\n \"addressLabel\": \"Address\",\r\n \"hoursLabel\": \"Hours\",\r\n \"hours\": \"Mon - Fri: 9:00 AM - 6:00 PM\",\r\n \"formTitle\": \"Send us a message\",\r\n \"formSubtitle\": \"Fill out the form below and we'll get back to you as soon as possible.\",\r\n \"nameLabel\": \"Full Name\",\r\n \"namePlaceholder\": \"John Doe\",\r\n \"emailInputLabel\": \"Email\",\r\n \"emailPlaceholder\": \"john@example.com\",\r\n \"phoneInputLabel\": \"Phone\",\r\n \"phonePlaceholder\": \"+1 234 567 890\",\r\n \"messageLabel\": \"Message\",\r\n \"messagePlaceholder\": \"Tell us about your project...\",\r\n \"submit\": \"Send Message\",\r\n \"sending\": \"Sending...\",\r\n \"success\": \"Message sent! We'll be in touch soon.\",\r\n \"error\": \"Something went wrong. Please try again.\"\r\n}\r\n"
33
+ "content": "{\r\n \"pageTitle\": \"Contact Us\",\r\n \"title\": \"Let's Start a Conversation\",\r\n \"subtitle\": \"Work with Promake to personalize this subtitle for your contact page. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"emailLabel\": \"Email\",\r\n \"phoneLabel\": \"Phone\",\r\n \"addressLabel\": \"Address\",\r\n \"hoursLabel\": \"Hours\",\r\n \"hours\": \"Mon - Fri: 9:00 AM - 6:00 PM\",\r\n \"formTitle\": \"Send us a message\",\r\n \"formSubtitle\": \"Fill out the form below and we'll get back to you as soon as possible.\",\r\n \"nameLabel\": \"Full Name\",\r\n \"namePlaceholder\": \"John Doe\",\r\n \"emailInputLabel\": \"Email\",\r\n \"emailPlaceholder\": \"john@example.com\",\r\n \"phoneInputLabel\": \"Phone\",\r\n \"phonePlaceholder\": \"+1 234 567 890\",\r\n \"messageLabel\": \"Message\",\r\n \"messagePlaceholder\": \"Tell us about your project...\",\r\n \"submit\": \"Send Message\",\r\n \"sending\": \"Sending...\",\r\n \"success\": \"Message sent! We'll be in touch soon.\",\r\n \"error\": \"Something went wrong. Please try again.\"\r\n}\r\n"
34
34
  },
35
35
  {
36
36
  "path": "contact-page-split/lang/tr.json",
37
37
  "type": "registry:lang",
38
38
  "target": "$modules$/contact-page-split/lang/tr.json",
39
- "content": "{\r\n \"pageTitle\": \"İletişim\",\r\n \"title\": \"Bir Sohbet Başlatalım\",\r\n \"subtitle\": \"AI bu alt başlığı iletişim sayfası amacınıza göre özelleştirecektir. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"emailLabel\": \"E-posta\",\r\n \"phoneLabel\": \"Telefon\",\r\n \"addressLabel\": \"Adres\",\r\n \"hoursLabel\": \"Çalışma Saatleri\",\r\n \"hours\": \"Pazartesi - Cuma: 09:00 - 18:00\",\r\n \"formTitle\": \"Bize mesaj gönderin\",\r\n \"formSubtitle\": \"Aşağıdaki formu doldurun, en kısa sürede size döneceğiz.\",\r\n \"nameLabel\": \"Ad Soyad\",\r\n \"namePlaceholder\": \"Ahmet Yılmaz\",\r\n \"emailInputLabel\": \"E-posta\",\r\n \"emailPlaceholder\": \"ahmet@ornek.com\",\r\n \"phoneInputLabel\": \"Telefon\",\r\n \"phonePlaceholder\": \"+90 532 123 4567\",\r\n \"messageLabel\": \"Mesaj\",\r\n \"messagePlaceholder\": \"Projeniz hakkında bize bilgi verin...\",\r\n \"submit\": \"Mesaj Gönder\",\r\n \"sending\": \"Gönderiliyor...\",\r\n \"success\": \"Mesaj gönderildi! En kısa sürede iletişime geçeceğiz.\",\r\n \"error\": \"Bir şeyler yanlış gitti. Lütfen tekrar deneyin.\"\r\n}\r\n"
39
+ "content": "{\r\n \"pageTitle\": \"İletişim\",\r\n \"title\": \"Bir Sohbet Başlatalım\",\r\n \"subtitle\": \"Promake ile bu alt başlığı iletişim sayfanıza göre kişiselleştirin. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"emailLabel\": \"E-posta\",\r\n \"phoneLabel\": \"Telefon\",\r\n \"addressLabel\": \"Adres\",\r\n \"hoursLabel\": \"Çalışma Saatleri\",\r\n \"hours\": \"Pazartesi - Cuma: 09:00 - 18:00\",\r\n \"formTitle\": \"Bize mesaj gönderin\",\r\n \"formSubtitle\": \"Aşağıdaki formu doldurun, en kısa sürede size döneceğiz.\",\r\n \"nameLabel\": \"Ad Soyad\",\r\n \"namePlaceholder\": \"Ahmet Yılmaz\",\r\n \"emailInputLabel\": \"E-posta\",\r\n \"emailPlaceholder\": \"ahmet@ornek.com\",\r\n \"phoneInputLabel\": \"Telefon\",\r\n \"phonePlaceholder\": \"+90 532 123 4567\",\r\n \"messageLabel\": \"Mesaj\",\r\n \"messagePlaceholder\": \"Projeniz hakkında bize bilgi verin...\",\r\n \"submit\": \"Mesaj Gönder\",\r\n \"sending\": \"Gönderiliyor...\",\r\n \"success\": \"Mesaj gönderildi! En kısa sürede iletişime geçeceğiz.\",\r\n \"error\": \"Bir şeyler yanlış gitti. Lütfen tekrar deneyin.\"\r\n}\r\n"
40
40
  }
41
41
  ],
42
42
  "exports": {
@@ -28,13 +28,13 @@
28
28
  "path": "contact-page/lang/en.json",
29
29
  "type": "registry:lang",
30
30
  "target": "$modules$/contact-page/lang/en.json",
31
- "content": "{\r\n \"title\": \"Contact Us\",\r\n \"getInTouch\": \"Get in Touch\",\r\n \"emailUs\": \"Email Us\",\r\n \"callUs\": \"Call Us\",\r\n \"visitUs\": \"Visit Us\",\r\n \"businessHours\": \"Hours\",\r\n \"sendMessage\": \"Send us a Message\",\r\n \"formNotAvailable\": \"Form is not available at the moment.\",\r\n \"fullName\": \"Full Name\",\r\n \"emailAddress\": \"Email Address\",\r\n \"phoneNumber\": \"Phone Number\",\r\n \"subject\": \"Subject\",\r\n \"message\": \"Message\",\r\n \"submit\": \"Send Message\",\r\n \"sending\": \"Sending...\",\r\n \"success\": \"Thank you for your message! We will get back to you soon.\",\r\n \"error\": \"Failed to send message. Please try again later.\",\r\n \"description\": \"Ask Promake to customize this contact page description based on your site and support options. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"email\": \"Email\",\r\n \"phone\": \"Phone\",\r\n \"address\": \"Address\",\r\n \"fullNamePlaceholder\": \"Your full name\",\r\n \"emailPlaceholder\": \"your@email.com\",\r\n \"phonePlaceholder\": \"+1 (555) 123-4567\",\r\n \"subjectPlaceholder\": \"What is this regarding?\",\r\n \"messagePlaceholder\": \"Tell us how we can help you...\",\r\n \"loading\": \"Loading contact information...\",\r\n \"emailResponse\": \"We typically respond within 24 hours\",\r\n \"needSupport\": \"Need Support?\",\r\n \"supportDescription\": \"For technical support or general inquiries, contact our dedicated support team:\",\r\n \"supportEmail\": \"Support Email\",\r\n \"monday_friday\": \"Monday - Friday\",\r\n \"saturday\": \"Saturday\",\r\n \"sunday\": \"Sunday\",\r\n \"closed\": \"Closed\",\r\n \"am\": \"AM\",\r\n \"pm\": \"PM\"\r\n}\r\n"
31
+ "content": "{\r\n \"title\": \"Contact Us\",\r\n \"getInTouch\": \"Get in Touch\",\r\n \"emailUs\": \"Email Us\",\r\n \"callUs\": \"Call Us\",\r\n \"visitUs\": \"Visit Us\",\r\n \"businessHours\": \"Hours\",\r\n \"sendMessage\": \"Send us a Message\",\r\n \"formNotAvailable\": \"Form is not available at the moment.\",\r\n \"fullName\": \"Full Name\",\r\n \"emailAddress\": \"Email Address\",\r\n \"phoneNumber\": \"Phone Number\",\r\n \"subject\": \"Subject\",\r\n \"message\": \"Message\",\r\n \"submit\": \"Send Message\",\r\n \"sending\": \"Sending...\",\r\n \"success\": \"Thank you for your message! We will get back to you soon.\",\r\n \"error\": \"Failed to send message. Please try again later.\",\r\n \"description\": \"Let Promake tailor this contact page description to your support options. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"email\": \"Email\",\r\n \"phone\": \"Phone\",\r\n \"address\": \"Address\",\r\n \"fullNamePlaceholder\": \"Your full name\",\r\n \"emailPlaceholder\": \"your@email.com\",\r\n \"phonePlaceholder\": \"+1 (555) 123-4567\",\r\n \"subjectPlaceholder\": \"What is this regarding?\",\r\n \"messagePlaceholder\": \"Tell us how we can help you...\",\r\n \"loading\": \"Loading contact information...\",\r\n \"emailResponse\": \"We typically respond within 24 hours\",\r\n \"needSupport\": \"Need Support?\",\r\n \"supportDescription\": \"For technical support or general inquiries, contact our dedicated support team:\",\r\n \"supportEmail\": \"Support Email\",\r\n \"monday_friday\": \"Monday - Friday\",\r\n \"saturday\": \"Saturday\",\r\n \"sunday\": \"Sunday\",\r\n \"closed\": \"Closed\",\r\n \"am\": \"AM\",\r\n \"pm\": \"PM\"\r\n}\r\n"
32
32
  },
33
33
  {
34
34
  "path": "contact-page/lang/tr.json",
35
35
  "type": "registry:lang",
36
36
  "target": "$modules$/contact-page/lang/tr.json",
37
- "content": "{\r\n \"title\": \"İletişim\",\r\n \"getInTouch\": \"Bize Ulaşın\",\r\n \"emailUs\": \"E-posta Gönderin\",\r\n \"callUs\": \"Bizi Arayın\",\r\n \"visitUs\": \"Bizi Ziyaret Edin\",\r\n \"businessHours\": \"Çalışma Saatleri\",\r\n \"sendMessage\": \"Bize Mesaj Gönderin\",\r\n \"formNotAvailable\": \"Form şu anda kullanılamıyor.\",\r\n \"fullName\": \"Ad Soyad\",\r\n \"emailAddress\": \"E-posta Adresi\",\r\n \"phoneNumber\": \"Telefon Numarası\",\r\n \"subject\": \"Konu\",\r\n \"message\": \"Mesaj\",\r\n \"submit\": \"Mesaj Gönder\",\r\n \"sending\": \"Gönderiliyor...\",\r\n \"success\": \"Mesajınız için teşekkürler! En kısa sürede size dönüş yapacağız.\",\r\n \"error\": \"Mesaj gönderilemedi. Lütfen tekrar deneyin.\",\r\n \"description\": \"AI bu iletişim sayfası açıklamasını sitenize ve destek seçeneklerinize göre özelleştirecektir. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"email\": \"E-posta\",\r\n \"phone\": \"Telefon\",\r\n \"address\": \"Adres\",\r\n \"fullNamePlaceholder\": \"Adınız ve soyadınız\",\r\n \"emailPlaceholder\": \"eposta@adresiniz.com\",\r\n \"phonePlaceholder\": \"+90 5XX XXX XX XX\",\r\n \"subjectPlaceholder\": \"Konu nedir?\",\r\n \"messagePlaceholder\": \"Size nasıl yardımcı olabiliriz...\",\r\n \"loading\": \"İletişim bilgileri yükleniyor...\",\r\n \"emailResponse\": \"Genellikle 24 saat içinde yanıt veririz\",\r\n \"needSupport\": \"Desteğe İhtiyacınız mı Var?\",\r\n \"supportDescription\": \"Teknik destek veya sipariş sorularınız için özel destek ekibimizle iletişime geçin:\",\r\n \"supportEmail\": \"Destek E-postası\",\r\n \"monday_friday\": \"Pazartesi - Cuma\",\r\n \"saturday\": \"Cumartesi\",\r\n \"sunday\": \"Pazar\",\r\n \"closed\": \"Kapalı\",\r\n \"am\": \"\",\r\n \"pm\": \"\"\r\n}\r\n"
37
+ "content": "{\r\n \"title\": \"İletişim\",\r\n \"getInTouch\": \"Bize Ulaşın\",\r\n \"emailUs\": \"E-posta Gönderin\",\r\n \"callUs\": \"Bizi Arayın\",\r\n \"visitUs\": \"Bizi Ziyaret Edin\",\r\n \"businessHours\": \"Çalışma Saatleri\",\r\n \"sendMessage\": \"Bize Mesaj Gönderin\",\r\n \"formNotAvailable\": \"Form şu anda kullanılamıyor.\",\r\n \"fullName\": \"Ad Soyad\",\r\n \"emailAddress\": \"E-posta Adresi\",\r\n \"phoneNumber\": \"Telefon Numarası\",\r\n \"subject\": \"Konu\",\r\n \"message\": \"Mesaj\",\r\n \"submit\": \"Mesaj Gönder\",\r\n \"sending\": \"Gönderiliyor...\",\r\n \"success\": \"Mesajınız için teşekkürler! En kısa sürede size dönüş yapacağız.\",\r\n \"error\": \"Mesaj gönderilemedi. Lütfen tekrar deneyin.\",\r\n \"description\": \"Promake ile bu iletişim sayfası açıklamasını destek seçeneklerinize göre uyarlayın. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"email\": \"E-posta\",\r\n \"phone\": \"Telefon\",\r\n \"address\": \"Adres\",\r\n \"fullNamePlaceholder\": \"Adınız ve soyadınız\",\r\n \"emailPlaceholder\": \"eposta@adresiniz.com\",\r\n \"phonePlaceholder\": \"+90 5XX XXX XX XX\",\r\n \"subjectPlaceholder\": \"Konu nedir?\",\r\n \"messagePlaceholder\": \"Size nasıl yardımcı olabiliriz...\",\r\n \"loading\": \"İletişim bilgileri yükleniyor...\",\r\n \"emailResponse\": \"Genellikle 24 saat içinde yanıt veririz\",\r\n \"needSupport\": \"Desteğe İhtiyacınız mı Var?\",\r\n \"supportDescription\": \"Teknik destek veya sipariş sorularınız için özel destek ekibimizle iletişime geçin:\",\r\n \"supportEmail\": \"Destek E-postası\",\r\n \"monday_friday\": \"Pazartesi - Cuma\",\r\n \"saturday\": \"Cumartesi\",\r\n \"sunday\": \"Pazar\",\r\n \"closed\": \"Kapalı\",\r\n \"am\": \"\",\r\n \"pm\": \"\"\r\n}\r\n"
38
38
  }
39
39
  ],
40
40
  "exports": {
@@ -22,13 +22,13 @@
22
22
  "path": "cta-section/lang/en.json",
23
23
  "type": "registry:lang",
24
24
  "target": "$modules$/cta-section/lang/en.json",
25
- "content": "{\r\n \"title\": \"Ready to Start Your Project?\",\r\n \"description\": \"Ask Promake to customize this CTA description based on your site goals. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"primaryButton\": \"Get Free Quote\",\r\n \"secondaryButton\": \"Learn About Us\"\r\n}\r\n"
25
+ "content": "{\r\n \"title\": \"Ready to Start Your Project?\",\r\n \"description\": \"Let Promake tailor this CTA description to your site goals. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"primaryButton\": \"Get Free Quote\",\r\n \"secondaryButton\": \"Learn About Us\"\r\n}\r\n"
26
26
  },
27
27
  {
28
28
  "path": "cta-section/lang/tr.json",
29
29
  "type": "registry:lang",
30
30
  "target": "$modules$/cta-section/lang/tr.json",
31
- "content": "{\r\n \"title\": \"Projenizi Başlatmaya Hazır mısınız?\",\r\n \"description\": \"AI bu CTA açıklamasını tekliflerinize göre özelleştirecektir. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"primaryButton\": \"Ücretsiz Teklif Al\",\r\n \"secondaryButton\": \"Hakkımızda\"\r\n}\r\n"
31
+ "content": "{\r\n \"title\": \"Projenizi Başlatmaya Hazır mısınız?\",\r\n \"description\": \"Promake ile bu CTA açıklamasını hedeflerinize göre uyarlayın. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"primaryButton\": \"Ücretsiz Teklif Al\",\r\n \"secondaryButton\": \"Hakkımızda\"\r\n}\r\n"
32
32
  }
33
33
  ],
34
34
  "exports": {
@@ -1,6 +1,6 @@
1
1
  # Blog Section
2
2
 
3
- Blog posts preview section with 3-column card grid. Features tagline badge, heading, subtitle, and 'view all' link. Each card shows image, category badge, date, title, summary, and 'read more' link. Great for homepage blog previews.
3
+ Blog posts preview section with 3-column card grid. Features tagline badge, heading, subtitle, and 'view all' link. Each card shows image, category badge, date, title, summary, and 'read more' link. Integrated with blog-core for data fetching.
4
4
 
5
5
  ## Files
6
6
 
@@ -37,3 +37,5 @@ import { BlogSection } from '@/modules/blog-section';
37
37
  This component requires:
38
38
  - `card`
39
39
  - `badge`
40
+ - `button`
41
+ - `blog-core`