@promakeai/cli 0.9.8 → 0.9.10

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 (137) hide show
  1. package/README.md +111 -111
  2. package/dist/index.js +142 -142
  3. package/dist/registry/about-page.json +3 -3
  4. package/dist/registry/about-section.json +4 -4
  5. package/dist/registry/animations.json +2 -2
  6. package/dist/registry/announcement-bar.json +4 -4
  7. package/dist/registry/api.json +1 -1
  8. package/dist/registry/auth-core.json +2 -2
  9. package/dist/registry/bento-grid-section.json +4 -4
  10. package/dist/registry/blog-core.json +5 -5
  11. package/dist/registry/blog-list-page.json +4 -4
  12. package/dist/registry/blog-section.json +4 -4
  13. package/dist/registry/cards-carousel-section.json +4 -4
  14. package/dist/registry/cart-drawer.json +4 -4
  15. package/dist/registry/cart-page.json +4 -4
  16. package/dist/registry/case-study-page.json +3 -3
  17. package/dist/registry/category-section.json +4 -4
  18. package/dist/registry/checkout-page.json +4 -4
  19. package/dist/registry/coming-soon-page-minimal.json +4 -4
  20. package/dist/registry/coming-soon-page.json +4 -4
  21. package/dist/registry/contact-info-grid.json +4 -4
  22. package/dist/registry/contact-page-centered.json +4 -4
  23. package/dist/registry/contact-page-map-overlay.json +3 -3
  24. package/dist/registry/contact-page-map-split.json +3 -3
  25. package/dist/registry/contact-page-split.json +4 -4
  26. package/dist/registry/contact-page.json +4 -4
  27. package/dist/registry/content-section.json +4 -4
  28. package/dist/registry/cookie-consent.json +4 -4
  29. package/dist/registry/cookies-page.json +3 -3
  30. package/dist/registry/cta-section.json +3 -3
  31. package/dist/registry/ecommerce-core.json +8 -8
  32. package/dist/registry/empty-page.json +3 -3
  33. package/dist/registry/faq-categorized.json +4 -4
  34. package/dist/registry/faq-simple.json +4 -4
  35. package/dist/registry/favorites-blog-block.json +1 -1
  36. package/dist/registry/favorites-blog-page.json +4 -4
  37. package/dist/registry/favorites-ecommerce-block.json +1 -1
  38. package/dist/registry/favorites-ecommerce-page.json +4 -4
  39. package/dist/registry/feature-section.json +3 -3
  40. package/dist/registry/featured-products.json +4 -4
  41. package/dist/registry/footer-detailed.json +4 -4
  42. package/dist/registry/footer-minimal.json +3 -3
  43. package/dist/registry/footer.json +3 -3
  44. package/dist/registry/forgot-password-page-split.json +4 -4
  45. package/dist/registry/forgot-password-page.json +4 -4
  46. package/dist/registry/google-adsense.json +4 -4
  47. package/dist/registry/google-map.json +2 -2
  48. package/dist/registry/header-centered-pill.json +4 -4
  49. package/dist/registry/header-ecommerce.json +4 -4
  50. package/dist/registry/header-mega.json +4 -4
  51. package/dist/registry/header-minimal.json +4 -4
  52. package/dist/registry/header-simple.json +3 -3
  53. package/dist/registry/hero-carousel.json +3 -3
  54. package/dist/registry/hero-cta.json +4 -4
  55. package/dist/registry/hero-gradient.json +4 -4
  56. package/dist/registry/hero-grid.json +4 -4
  57. package/dist/registry/hero-profile.json +3 -3
  58. package/dist/registry/hero.json +3 -3
  59. package/dist/registry/index.json +103 -103
  60. package/dist/registry/landing-page-app.json +3 -3
  61. package/dist/registry/landing-page-saas.json +3 -3
  62. package/dist/registry/login-page-split.json +4 -4
  63. package/dist/registry/login-page.json +4 -4
  64. package/dist/registry/logo-cloud.json +4 -4
  65. package/dist/registry/masonry-grid.json +3 -3
  66. package/dist/registry/my-orders-page.json +4 -4
  67. package/dist/registry/newsletter-section.json +4 -4
  68. package/dist/registry/order-card-compact.json +3 -3
  69. package/dist/registry/order-confirmation-page.json +4 -4
  70. package/dist/registry/order-detail-block.json +1 -1
  71. package/dist/registry/orders-list-block.json +1 -1
  72. package/dist/registry/payment-success-block.json +2 -2
  73. package/dist/registry/portfolio-page.json +4 -4
  74. package/dist/registry/post-card.json +4 -4
  75. package/dist/registry/post-detail-block.json +4 -4
  76. package/dist/registry/post-detail-page.json +4 -4
  77. package/dist/registry/pricing-card.json +3 -3
  78. package/dist/registry/pricing-page.json +4 -4
  79. package/dist/registry/pricing-section.json +4 -4
  80. package/dist/registry/privacy-page.json +3 -3
  81. package/dist/registry/product-card-detailed.json +4 -4
  82. package/dist/registry/product-card-hover.json +4 -4
  83. package/dist/registry/product-card.json +4 -4
  84. package/dist/registry/product-detail-block.json +2 -2
  85. package/dist/registry/product-detail-page.json +4 -4
  86. package/dist/registry/product-detail-section.json +4 -4
  87. package/dist/registry/product-quick-view.json +4 -4
  88. package/dist/registry/products-page.json +4 -4
  89. package/dist/registry/reading-progress.json +4 -4
  90. package/dist/registry/register-page-split.json +4 -4
  91. package/dist/registry/register-page.json +4 -4
  92. package/dist/registry/related-posts-block.json +1 -1
  93. package/dist/registry/related-products-block.json +2 -2
  94. package/dist/registry/reset-password-page-split.json +4 -4
  95. package/dist/registry/reset-password-page.json +4 -4
  96. package/dist/registry/service-card.json +1 -1
  97. package/dist/registry/share-buttons.json +4 -4
  98. package/dist/registry/skill-card.json +1 -1
  99. package/dist/registry/team-page.json +4 -4
  100. package/dist/registry/terms-page.json +3 -3
  101. package/dist/registry/testimonials-carousel.json +4 -4
  102. package/dist/registry/testimonials-grid.json +4 -4
  103. package/dist/registry/timeline-section.json +4 -4
  104. package/dist/registry/verify-email-page.json +4 -4
  105. package/dist/registry/video-hero.json +4 -4
  106. package/dist/registry/youtube-embed.json +4 -4
  107. package/package.json +1 -1
  108. package/template/.env +5 -5
  109. package/template/README.md +54 -54
  110. package/template/eslint.config.js +41 -41
  111. package/template/index.html +237 -237
  112. package/template/package.json +96 -96
  113. package/template/public/_redirects +1 -1
  114. package/template/public/robots.txt +14 -14
  115. package/template/scripts/init-db.ts +18 -18
  116. package/template/src/App.tsx +21 -21
  117. package/template/src/components/FormField.tsx +48 -48
  118. package/template/src/components/FormFileInput.tsx +75 -75
  119. package/template/src/components/GoogleAnalytics.tsx +34 -34
  120. package/template/src/components/LanguageSwitcher.tsx +53 -53
  121. package/template/src/components/MetriaAnalytics.tsx +68 -68
  122. package/template/src/components/PasswordInput.tsx +60 -60
  123. package/template/src/components/ScriptInjector.tsx +62 -62
  124. package/template/src/components/Stack.tsx +39 -39
  125. package/template/src/constants/constants.json +71 -71
  126. package/template/src/db/index.ts +21 -21
  127. package/template/src/db/provider.tsx +106 -106
  128. package/template/src/db/schema.json +278 -278
  129. package/template/src/db/types.ts +195 -195
  130. package/template/src/hooks/use-debounced-value.ts +12 -12
  131. package/template/src/hooks/use-page-title.ts +55 -55
  132. package/template/src/lang/index.ts +90 -90
  133. package/template/src/lib/api.ts +345 -345
  134. package/template/src/lib/env.ts +19 -19
  135. package/template/src/router.tsx +14 -14
  136. package/template/src/vite-env.d.ts +1 -1
  137. package/template/vite.config.ts +194 -194
@@ -16,7 +16,7 @@
16
16
  "path": "about-page/index.ts",
17
17
  "type": "registry:index",
18
18
  "target": "$modules$/about-page/index.ts",
19
- "content": "export * from './about-page';\r\nexport { AboutPage as default } from './about-page';\r\n"
19
+ "content": "export * from './about-page';\nexport { AboutPage as default } from './about-page';\n"
20
20
  },
21
21
  {
22
22
  "path": "about-page/about-page.tsx",
@@ -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\": \"Tell your brand's story, vision, and value in a few sentences.\",\r\n \"storyTitle\": \"Our Story\",\r\n \"storyP1\": \"Tell your brand's origins, motivation, and current position. Include key turning points.\",\r\n \"storyP2\": \"Continue your story. Deepen the journey mentioned in the first paragraph.\",\r\n \"missionTitle\": \"Our Mission\",\r\n \"missionDesc\": \"Describe your brand's core purpose and user value.\",\r\n \"valuesTitle\": \"Our Values\",\r\n \"valuesDesc\": \"Share the core values that define your brand.\",\r\n \"teamTitle\": \"Our Team\",\r\n \"teamDesc\": \"Describe your team structure, culture, and collaboration.\",\r\n \"qualityTitle\": \"Quality First\",\r\n \"qualityDesc\": \"Emphasize quality standards and user experience focus.\",\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 based on your goals.\"\r\n}"
31
+ "content": "{\n \"title\": \"About Us\",\n \"subtitle\": \"Tell your brand's story, vision, and value in a few sentences.\",\n \"storyTitle\": \"Our Story\",\n \"storyP1\": \"Tell your brand's origins, motivation, and current position. Include key turning points.\",\n \"storyP2\": \"Continue your story. Deepen the journey mentioned in the first paragraph.\",\n \"missionTitle\": \"Our Mission\",\n \"missionDesc\": \"Describe your brand's core purpose and user value.\",\n \"valuesTitle\": \"Our Values\",\n \"valuesDesc\": \"Share the core values that define your brand.\",\n \"teamTitle\": \"Our Team\",\n \"teamDesc\": \"Describe your team structure, culture, and collaboration.\",\n \"qualityTitle\": \"Quality First\",\n \"qualityDesc\": \"Emphasize quality standards and user experience focus.\",\n \"customersValue\": \"500+\",\n \"customersLabel\": \"Happy Customers\",\n \"projectsValue\": \"1000+\",\n \"projectsLabel\": \"Projects Completed\",\n \"experienceValue\": \"10+\",\n \"experienceLabel\": \"Years Experience\",\n \"satisfactionValue\": \"99%\",\n \"satisfactionLabel\": \"Client Satisfaction\",\n \"ctaTitle\": \"Ready to Work Together?\",\n \"ctaDesc\": \"Update this CTA based on your goals.\"\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\": \"Markanızın hikayesini, vizyonunu ve değerini birkaç cümleyle anlatın.\",\r\n \"storyTitle\": \"Hikayemiz\",\r\n \"storyP1\": \"Markanızın doğuşunu, motivasyonunu ve bugünkü konumunu anlatın. Dönüm noktalarını ekleyin.\",\r\n \"storyP2\": \"Hikayenizin devamı. İlk paragraftaki yolculuğu derinleştirin.\",\r\n \"missionTitle\": \"Misyonumuz\",\r\n \"missionDesc\": \"Markanızın temel amacını ve kullanıcı değerini anlatın.\",\r\n \"valuesTitle\": \"Değerlerimiz\",\r\n \"valuesDesc\": \"Markanızı tanımlayan temel değerleri paylaşın.\",\r\n \"teamTitle\": \"Ekibimiz\",\r\n \"teamDesc\": \"Ekip yapınızı, kültürünüzü ve işbirliğinizi anlatın.\",\r\n \"qualityTitle\": \"Önce Kalite\",\r\n \"qualityDesc\": \"Kalite standartlarınızı ve kullanıcı deneyimi odağınızı vurgulayın.\",\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'yı hedeflerinize göre güncelleyin.\"\r\n}"
37
+ "content": "{\n \"title\": \"Hakkımızda\",\n \"subtitle\": \"Markanızın hikayesini, vizyonunu ve değerini birkaç cümleyle anlatın.\",\n \"storyTitle\": \"Hikayemiz\",\n \"storyP1\": \"Markanızın doğuşunu, motivasyonunu ve bugünkü konumunu anlatın. Dönüm noktalarını ekleyin.\",\n \"storyP2\": \"Hikayenizin devamı. İlk paragraftaki yolculuğu derinleştirin.\",\n \"missionTitle\": \"Misyonumuz\",\n \"missionDesc\": \"Markanızın temel amacını ve kullanıcı değerini anlatın.\",\n \"valuesTitle\": \"Değerlerimiz\",\n \"valuesDesc\": \"Markanızı tanımlayan temel değerleri paylaşın.\",\n \"teamTitle\": \"Ekibimiz\",\n \"teamDesc\": \"Ekip yapınızı, kültürünüzü ve işbirliğinizi anlatın.\",\n \"qualityTitle\": \"Önce Kalite\",\n \"qualityDesc\": \"Kalite standartlarınızı ve kullanıcı deneyimi odağınızı vurgulayın.\",\n \"customersValue\": \"500+\",\n \"customersLabel\": \"Mutlu Müşteri\",\n \"projectsValue\": \"1000+\",\n \"projectsLabel\": \"Tamamlanan Proje\",\n \"experienceValue\": \"10+\",\n \"experienceLabel\": \"Yıllık Deneyim\",\n \"satisfactionValue\": \"%99\",\n \"satisfactionLabel\": \"Müşteri Memnuniyeti\",\n \"ctaTitle\": \"Birlikte Çalışmaya Hazır mısınız?\",\n \"ctaDesc\": \"Bu CTA'yı hedeflerinize göre güncelleyin.\"\n}"
38
38
  }
39
39
  ],
40
40
  "exports": {
@@ -10,25 +10,25 @@
10
10
  "path": "about-section/index.ts",
11
11
  "type": "registry:index",
12
12
  "target": "$modules$/about-section/index.ts",
13
- "content": "export * from './about-section';\r\n"
13
+ "content": "export * from './about-section';\n"
14
14
  },
15
15
  {
16
16
  "path": "about-section/about-section.tsx",
17
17
  "type": "registry:component",
18
18
  "target": "$modules$/about-section/about-section.tsx",
19
- "content": "import { Link } from \"react-router\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { Button } from \"@/components/ui/button\";\r\n\r\ninterface AboutSectionProps {\r\n className?: string;\r\n}\r\n\r\nexport function AboutSection({ className }: AboutSectionProps) {\r\n const { t } = useTranslation(\"about-section\");\r\n\r\n const stats = [\r\n { value: t(\"stat1Value\", \"500+\"), label: t(\"stat1Label\", \"Happy Clients\") },\r\n { value: t(\"stat2Value\", \"1000+\"), label: t(\"stat2Label\", \"Projects Completed\") },\r\n { value: t(\"stat3Value\", \"99%\"), label: t(\"stat3Label\", \"Satisfaction Rate\") },\r\n { value: t(\"stat4Value\", \"15+\"), label: t(\"stat4Label\", \"Years Experience\") },\r\n ];\r\n\r\n const companies = [\"Google\", \"Microsoft\", \"Amazon\", \"Apple\", \"Meta\"];\r\n\r\n return (\r\n <section className={cn(\"py-16 md:py-24\", className)}>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n {/* Header */}\r\n <div className=\"grid md:grid-cols-2 gap-8 mb-12\">\r\n <h2 className=\"text-3xl md:text-4xl lg:text-5xl font-bold\">\r\n {t(\"title\", \"About Our Company\")}\r\n </h2>\r\n <p className=\"text-muted-foreground text-lg\">\r\n {t(\"description\", \"We are a passionate team dedicated to creating innovative solutions that empower businesses to thrive in the digital age. Our mission is to deliver excellence in everything we do.\")}\r\n </p>\r\n </div>\r\n\r\n {/* Images Grid */}\r\n <div className=\"grid lg:grid-cols-3 gap-6 mb-16\">\r\n {/* Main Image */}\r\n <div className=\"lg:col-span-2\">\r\n <div className=\"aspect-[16/10] lg:aspect-auto lg:h-full rounded-2xl bg-muted overflow-hidden\">\r\n <img\r\n src=\"/images/placeholder.png\"\r\n alt={t(\"mainImageAlt\", \"Our team\")}\r\n className=\"w-full h-full object-cover\"\r\n onError={(e) => {\r\n e.currentTarget.style.display = \"none\";\r\n }}\r\n />\r\n </div>\r\n </div>\r\n\r\n {/* Side Column */}\r\n <div className=\"flex flex-col gap-6\">\r\n {/* Info Card */}\r\n <div className=\"bg-muted rounded-2xl p-6 flex flex-col justify-between flex-1\">\r\n <div>\r\n <h3 className=\"text-lg font-semibold mb-2\">\r\n {t(\"cardTitle\", \"Our Mission\")}\r\n </h3>\r\n <p className=\"text-muted-foreground text-sm mb-4\">\r\n {t(\"cardDescription\", \"Providing businesses with effective tools to improve workflows, boost efficiency, and encourage sustainable growth.\")}\r\n </p>\r\n </div>\r\n <Button variant=\"outline\" asChild className=\"w-fit\">\r\n <Link to=\"/about\">{t(\"cardButton\", \"Learn More\")}</Link>\r\n </Button>\r\n </div>\r\n\r\n {/* Secondary Image */}\r\n <div className=\"aspect-square rounded-2xl bg-muted overflow-hidden flex-1\">\r\n <img\r\n src=\"/images/placeholder.png\"\r\n alt={t(\"secondaryImageAlt\", \"Our office\")}\r\n className=\"w-full h-full object-cover\"\r\n onError={(e) => {\r\n e.currentTarget.style.display = \"none\";\r\n }}\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n\r\n {/* Companies */}\r\n <div className=\"text-center mb-16\">\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\"companiesTitle\", \"Trusted by leading companies worldwide\")}\r\n </p>\r\n <div className=\"flex flex-wrap justify-center items-center gap-8 md:gap-12\">\r\n {companies.map((company) => (\r\n <span\r\n key={company}\r\n className=\"text-xl md:text-2xl font-semibold text-muted-foreground/50\"\r\n >\r\n {company}\r\n </span>\r\n ))}\r\n </div>\r\n </div>\r\n\r\n {/* Stats */}\r\n <div className=\"bg-muted rounded-2xl p-8 md:p-12\">\r\n <div className=\"text-center md:text-left mb-8\">\r\n <h3 className=\"text-2xl md:text-3xl font-bold mb-2\">\r\n {t(\"statsTitle\", \"Our Achievements\")}\r\n </h3>\r\n <p className=\"text-muted-foreground max-w-xl\">\r\n {t(\"statsDescription\", \"Numbers that reflect our commitment to excellence and client satisfaction.\")}\r\n </p>\r\n </div>\r\n <div className=\"grid grid-cols-2 lg:grid-cols-4 gap-8\">\r\n {stats.map((stat, index) => (\r\n <div key={index} className=\"text-center\">\r\n <div className=\"text-3xl md:text-4xl lg:text-5xl font-bold text-primary mb-2\">\r\n {stat.value}\r\n </div>\r\n <p className=\"text-sm md:text-base text-muted-foreground\">\r\n {stat.label}\r\n </p>\r\n </div>\r\n ))}\r\n </div>\r\n </div>\r\n </div>\r\n </section>\r\n );\r\n}\r\n"
19
+ "content": "import { Link } from \"react-router\";\nimport { useTranslation } from \"react-i18next\";\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/button\";\n\ninterface AboutSectionProps {\n className?: string;\n}\n\nexport function AboutSection({ className }: AboutSectionProps) {\n const { t } = useTranslation(\"about-section\");\n\n const stats = [\n { value: t(\"stat1Value\", \"500+\"), label: t(\"stat1Label\", \"Happy Clients\") },\n { value: t(\"stat2Value\", \"1000+\"), label: t(\"stat2Label\", \"Projects Completed\") },\n { value: t(\"stat3Value\", \"99%\"), label: t(\"stat3Label\", \"Satisfaction Rate\") },\n { value: t(\"stat4Value\", \"15+\"), label: t(\"stat4Label\", \"Years Experience\") },\n ];\n\n const companies = [\"Google\", \"Microsoft\", \"Amazon\", \"Apple\", \"Meta\"];\n\n return (\n <section className={cn(\"py-16 md:py-24\", className)}>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\n {/* Header */}\n <div className=\"grid md:grid-cols-2 gap-8 mb-12\">\n <h2 className=\"text-3xl md:text-4xl lg:text-5xl font-bold\">\n {t(\"title\", \"About Our Company\")}\n </h2>\n <p className=\"text-muted-foreground text-lg\">\n {t(\"description\", \"We are a passionate team dedicated to creating innovative solutions that empower businesses to thrive in the digital age. Our mission is to deliver excellence in everything we do.\")}\n </p>\n </div>\n\n {/* Images Grid */}\n <div className=\"grid lg:grid-cols-3 gap-6 mb-16\">\n {/* Main Image */}\n <div className=\"lg:col-span-2\">\n <div className=\"aspect-[16/10] lg:aspect-auto lg:h-full rounded-2xl bg-muted overflow-hidden\">\n <img\n src=\"/images/placeholder.png\"\n alt={t(\"mainImageAlt\", \"Our team\")}\n className=\"w-full h-full object-cover\"\n onError={(e) => {\n e.currentTarget.style.display = \"none\";\n }}\n />\n </div>\n </div>\n\n {/* Side Column */}\n <div className=\"flex flex-col gap-6\">\n {/* Info Card */}\n <div className=\"bg-muted rounded-2xl p-6 flex flex-col justify-between flex-1\">\n <div>\n <h3 className=\"text-lg font-semibold mb-2\">\n {t(\"cardTitle\", \"Our Mission\")}\n </h3>\n <p className=\"text-muted-foreground text-sm mb-4\">\n {t(\"cardDescription\", \"Providing businesses with effective tools to improve workflows, boost efficiency, and encourage sustainable growth.\")}\n </p>\n </div>\n <Button variant=\"outline\" asChild className=\"w-fit\">\n <Link to=\"/about\">{t(\"cardButton\", \"Learn More\")}</Link>\n </Button>\n </div>\n\n {/* Secondary Image */}\n <div className=\"aspect-square rounded-2xl bg-muted overflow-hidden flex-1\">\n <img\n src=\"/images/placeholder.png\"\n alt={t(\"secondaryImageAlt\", \"Our office\")}\n className=\"w-full h-full object-cover\"\n onError={(e) => {\n e.currentTarget.style.display = \"none\";\n }}\n />\n </div>\n </div>\n </div>\n\n {/* Companies */}\n <div className=\"text-center mb-16\">\n <p className=\"text-muted-foreground mb-6\">\n {t(\"companiesTitle\", \"Trusted by leading companies worldwide\")}\n </p>\n <div className=\"flex flex-wrap justify-center items-center gap-8 md:gap-12\">\n {companies.map((company) => (\n <span\n key={company}\n className=\"text-xl md:text-2xl font-semibold text-muted-foreground/50\"\n >\n {company}\n </span>\n ))}\n </div>\n </div>\n\n {/* Stats */}\n <div className=\"bg-muted rounded-2xl p-8 md:p-12\">\n <div className=\"text-center md:text-left mb-8\">\n <h3 className=\"text-2xl md:text-3xl font-bold mb-2\">\n {t(\"statsTitle\", \"Our Achievements\")}\n </h3>\n <p className=\"text-muted-foreground max-w-xl\">\n {t(\"statsDescription\", \"Numbers that reflect our commitment to excellence and client satisfaction.\")}\n </p>\n </div>\n <div className=\"grid grid-cols-2 lg:grid-cols-4 gap-8\">\n {stats.map((stat, index) => (\n <div key={index} className=\"text-center\">\n <div className=\"text-3xl md:text-4xl lg:text-5xl font-bold text-primary mb-2\">\n {stat.value}\n </div>\n <p className=\"text-sm md:text-base text-muted-foreground\">\n {stat.label}\n </p>\n </div>\n ))}\n </div>\n </div>\n </div>\n </section>\n );\n}\n"
20
20
  },
21
21
  {
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\": \"We're a team of passionate professionals dedicated to delivering exceptional results for our clients.\",\r\n \"mainImageAlt\": \"Our team\",\r\n \"secondaryImageAlt\": \"Our office\",\r\n \"cardTitle\": \"Our Mission\",\r\n \"cardDescription\": \"To empower businesses with innovative solutions that drive growth and success.\",\r\n \"cardButton\": \"Learn More\",\r\n \"companiesTitle\": \"Trusted by Leading Brands\",\r\n \"statsTitle\": \"Our Achievements\",\r\n \"statsDescription\": \"Numbers that showcase our commitment to excellence.\",\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}"
25
+ "content": "{\n \"title\": \"About Us\",\n \"description\": \"We're a team of passionate professionals dedicated to delivering exceptional results for our clients.\",\n \"mainImageAlt\": \"Our team\",\n \"secondaryImageAlt\": \"Our office\",\n \"cardTitle\": \"Our Mission\",\n \"cardDescription\": \"To empower businesses with innovative solutions that drive growth and success.\",\n \"cardButton\": \"Learn More\",\n \"companiesTitle\": \"Trusted by Leading Brands\",\n \"statsTitle\": \"Our Achievements\",\n \"statsDescription\": \"Numbers that showcase our commitment to excellence.\",\n \"stat1Value\": \"500+\",\n \"stat1Label\": \"Happy Clients\",\n \"stat2Value\": \"1000+\",\n \"stat2Label\": \"Projects Completed\",\n \"stat3Value\": \"99%\",\n \"stat3Label\": \"Satisfaction Rate\",\n \"stat4Value\": \"15+\",\n \"stat4Label\": \"Years Experience\"\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\": \"Müşterilerimize olağanüstü sonuçlar sunmaya kendini adamış, tutkulu profesyonellerden oluşan bir ekibiz.\",\r\n \"mainImageAlt\": \"Ekibimiz\",\r\n \"secondaryImageAlt\": \"Ofisimiz\",\r\n \"cardTitle\": \"Misyonumuz\",\r\n \"cardDescription\": \"İşletmeleri büyüme ve başarı sağlayan yenilikçi çözümlerle güçlendirmek.\",\r\n \"cardButton\": \"Daha Fazla\",\r\n \"companiesTitle\": \"Önde Gelen Markalar Tarafından Güvenilir\",\r\n \"statsTitle\": \"Başarılarımız\",\r\n \"statsDescription\": \"Mükemmeliyete olan bağlılığımızı gösteren rakamlar.\",\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}"
31
+ "content": "{\n \"title\": \"Hakkımızda\",\n \"description\": \"Müşterilerimize olağanüstü sonuçlar sunmaya kendini adamış, tutkulu profesyonellerden oluşan bir ekibiz.\",\n \"mainImageAlt\": \"Ekibimiz\",\n \"secondaryImageAlt\": \"Ofisimiz\",\n \"cardTitle\": \"Misyonumuz\",\n \"cardDescription\": \"İşletmeleri büyüme ve başarı sağlayan yenilikçi çözümlerle güçlendirmek.\",\n \"cardButton\": \"Daha Fazla\",\n \"companiesTitle\": \"Önde Gelen Markalar Tarafından Güvenilir\",\n \"statsTitle\": \"Başarılarımız\",\n \"statsDescription\": \"Mükemmeliyete olan bağlılığımızı gösteren rakamlar.\",\n \"stat1Value\": \"500+\",\n \"stat1Label\": \"Mutlu Müşteri\",\n \"stat2Value\": \"1000+\",\n \"stat2Label\": \"Tamamlanan Proje\",\n \"stat3Value\": \"%99\",\n \"stat3Label\": \"Memnuniyet Oranı\",\n \"stat4Value\": \"15+\",\n \"stat4Label\": \"Yıllık Deneyim\"\n}"
32
32
  }
33
33
  ],
34
34
  "exports": {
@@ -13,13 +13,13 @@
13
13
  "path": "animations/index.ts",
14
14
  "type": "registry:index",
15
15
  "target": "$modules$/animations/index.ts",
16
- "content": "export * from \"./animations\";\r\nexport * from \"./components\";\r\n"
16
+ "content": "export * from \"./animations\";\nexport * from \"./components\";\n"
17
17
  },
18
18
  {
19
19
  "path": "animations/animations.ts",
20
20
  "type": "registry:lib",
21
21
  "target": "$modules$/animations/animations.ts",
22
- "content": "import type { Variants, Transition } from \"motion/react\";\r\n\r\n/**\r\n * Fade in from bottom animation\r\n * Use for: Hero sections, cards on scroll, page content\r\n */\r\nexport const fadeInUp: Variants = {\r\n initial: { opacity: 0, y: 20 },\r\n animate: { opacity: 1, y: 0 },\r\n exit: { opacity: 0, y: -10 },\r\n};\r\n\r\n/**\r\n * Fade in from top animation\r\n * Use for: Dropdowns, modals, notifications\r\n */\r\nexport const fadeInDown: Variants = {\r\n initial: { opacity: 0, y: -20 },\r\n animate: { opacity: 1, y: 0 },\r\n exit: { opacity: 0, y: -10 },\r\n};\r\n\r\n/**\r\n * Slide in from left animation\r\n * Use for: Sidebars, navigation, list items\r\n */\r\nexport const slideInLeft: Variants = {\r\n initial: { opacity: 0, x: -20 },\r\n animate: { opacity: 1, x: 0 },\r\n exit: { opacity: 0, x: -20 },\r\n};\r\n\r\n/**\r\n * Slide in from right animation\r\n * Use for: Panels, drawers, cart sidebars\r\n */\r\nexport const slideInRight: Variants = {\r\n initial: { opacity: 0, x: 20 },\r\n animate: { opacity: 1, x: 0 },\r\n exit: { opacity: 0, x: 20 },\r\n};\r\n\r\n/**\r\n * Scale up animation\r\n * Use for: Modals, popups, dialogs\r\n */\r\nexport const scaleUp: Variants = {\r\n initial: { opacity: 0, scale: 0.95 },\r\n animate: { opacity: 1, scale: 1 },\r\n exit: { opacity: 0, scale: 0.95 },\r\n};\r\n\r\n/**\r\n * Simple fade animation\r\n * Use for: Overlays, backgrounds, subtle transitions\r\n */\r\nexport const fade: Variants = {\r\n initial: { opacity: 0 },\r\n animate: { opacity: 1 },\r\n exit: { opacity: 0 },\r\n};\r\n\r\n/**\r\n * Stagger container for child animations\r\n * Use with children that have their own variants\r\n *\r\n * @example\r\n * <motion.div variants={staggerContainer} initial=\"initial\" animate=\"animate\">\r\n * {items.map(item => (\r\n * <motion.div key={item.id} variants={fadeInUp}>\r\n * {item.content}\r\n * </motion.div>\r\n * ))}\r\n * </motion.div>\r\n */\r\nexport const staggerContainer: Variants = {\r\n initial: {},\r\n animate: {\r\n transition: {\r\n staggerChildren: 0.1,\r\n delayChildren: 0.1,\r\n },\r\n },\r\n};\r\n\r\n/**\r\n * Fast stagger for many items\r\n */\r\nexport const staggerContainerFast: Variants = {\r\n initial: {},\r\n animate: {\r\n transition: {\r\n staggerChildren: 0.05,\r\n delayChildren: 0.05,\r\n },\r\n },\r\n};\r\n\r\n/**\r\n * Slow stagger for fewer, larger items\r\n */\r\nexport const staggerContainerSlow: Variants = {\r\n initial: {},\r\n animate: {\r\n transition: {\r\n staggerChildren: 0.15,\r\n delayChildren: 0.2,\r\n },\r\n },\r\n};\r\n\r\n/**\r\n * Default spring transition\r\n * Use for: Most animations\r\n */\r\nexport const springTransition: Transition = {\r\n type: \"spring\",\r\n stiffness: 300,\r\n damping: 30,\r\n};\r\n\r\n/**\r\n * Smooth ease transition\r\n * Use for: Subtle, elegant animations\r\n */\r\nexport const easeTransition: Transition = {\r\n type: \"tween\",\r\n ease: \"easeOut\",\r\n duration: 0.3,\r\n};\r\n\r\n/**\r\n * Quick transition\r\n * Use for: Hover effects, quick feedback\r\n */\r\nexport const quickTransition: Transition = {\r\n type: \"tween\",\r\n ease: \"easeOut\",\r\n duration: 0.15,\r\n};\r\n\r\n/**\r\n * Hover scale effect props\r\n * Use with motion components for card hover effects\r\n *\r\n * @example\r\n * <motion.div {...hoverScale}>\r\n * <Card>...</Card>\r\n * </motion.div>\r\n */\r\nexport const hoverScale = {\r\n whileHover: { scale: 1.02 },\r\n whileTap: { scale: 0.98 },\r\n transition: springTransition,\r\n};\r\n\r\n/**\r\n * Subtle hover scale for smaller elements\r\n */\r\nexport const hoverScaleSubtle = {\r\n whileHover: { scale: 1.01 },\r\n whileTap: { scale: 0.99 },\r\n transition: quickTransition,\r\n};\r\n\r\n/**\r\n * Hover lift effect (scale + shadow simulation via y)\r\n */\r\nexport const hoverLift = {\r\n whileHover: { scale: 1.02, y: -4 },\r\n whileTap: { scale: 0.98, y: 0 },\r\n transition: springTransition,\r\n};\r\n\r\n/**\r\n * Button press effect\r\n */\r\nexport const buttonPress = {\r\n whileHover: { scale: 1.02 },\r\n whileTap: { scale: 0.95 },\r\n transition: quickTransition,\r\n};\r\n\r\n/**\r\n * Icon hover rotation\r\n */\r\nexport const iconHoverRotate = {\r\n whileHover: { rotate: 15 },\r\n transition: springTransition,\r\n};\r\n\r\n/**\r\n * Accordion/collapse animation variants\r\n * Use with AnimatePresence for expand/collapse\r\n *\r\n * @example\r\n * <AnimatePresence>\r\n * {isOpen && (\r\n * <motion.div\r\n * variants={collapseVariants}\r\n * initial=\"collapsed\"\r\n * animate=\"expanded\"\r\n * exit=\"collapsed\"\r\n * >\r\n * Content\r\n * </motion.div>\r\n * )}\r\n * </AnimatePresence>\r\n */\r\nexport const collapseVariants: Variants = {\r\n collapsed: {\r\n height: 0,\r\n opacity: 0,\r\n overflow: \"hidden\",\r\n },\r\n expanded: {\r\n height: \"auto\",\r\n opacity: 1,\r\n overflow: \"visible\",\r\n transition: {\r\n height: { duration: 0.3, ease: \"easeOut\" },\r\n opacity: { duration: 0.2, delay: 0.1 },\r\n },\r\n },\r\n};\r\n\r\n/**\r\n * Page transition variants\r\n * Use for route transitions with AnimatePresence\r\n */\r\nexport const pageTransition: Variants = {\r\n initial: { opacity: 0, y: 10 },\r\n animate: {\r\n opacity: 1,\r\n y: 0,\r\n transition: {\r\n duration: 0.3,\r\n ease: \"easeOut\",\r\n },\r\n },\r\n exit: {\r\n opacity: 0,\r\n y: -10,\r\n transition: {\r\n duration: 0.2,\r\n ease: \"easeIn\",\r\n },\r\n },\r\n};\r\n\r\n/**\r\n * Carousel slide variants\r\n * Direction should be passed as custom prop\r\n *\r\n * @example\r\n * <motion.div\r\n * custom={direction}\r\n * variants={carouselSlide}\r\n * initial=\"enter\"\r\n * animate=\"center\"\r\n * exit=\"exit\"\r\n * >\r\n */\r\nexport const carouselSlide: Variants = {\r\n enter: (direction: number) => ({\r\n x: direction > 0 ? 300 : -300,\r\n opacity: 0,\r\n }),\r\n center: {\r\n x: 0,\r\n opacity: 1,\r\n transition: {\r\n x: { type: \"spring\", stiffness: 300, damping: 30 },\r\n opacity: { duration: 0.2 },\r\n },\r\n },\r\n exit: (direction: number) => ({\r\n x: direction < 0 ? 300 : -300,\r\n opacity: 0,\r\n transition: {\r\n x: { type: \"spring\", stiffness: 300, damping: 30 },\r\n opacity: { duration: 0.2 },\r\n },\r\n }),\r\n};\r\n\r\n/**\r\n * Notification/toast animation\r\n */\r\nexport const notificationVariants: Variants = {\r\n initial: { opacity: 0, y: -20, scale: 0.95 },\r\n animate: {\r\n opacity: 1,\r\n y: 0,\r\n scale: 1,\r\n transition: springTransition,\r\n },\r\n exit: {\r\n opacity: 0,\r\n y: -20,\r\n scale: 0.95,\r\n transition: quickTransition,\r\n },\r\n};\r\n\r\n/**\r\n * List item animation for staggered lists\r\n */\r\nexport const listItem: Variants = {\r\n initial: { opacity: 0, x: -10 },\r\n animate: { opacity: 1, x: 0 },\r\n exit: { opacity: 0, x: 10 },\r\n};\r\n\r\n/**\r\n * Grid item animation for staggered grids\r\n */\r\nexport const gridItem: Variants = {\r\n initial: { opacity: 0, scale: 0.9 },\r\n animate: { opacity: 1, scale: 1 },\r\n exit: { opacity: 0, scale: 0.9 },\r\n};\r\n"
22
+ "content": "import type { Variants, Transition } from \"motion/react\";\n\n/**\n * Fade in from bottom animation\n * Use for: Hero sections, cards on scroll, page content\n */\nexport const fadeInUp: Variants = {\n initial: { opacity: 0, y: 20 },\n animate: { opacity: 1, y: 0 },\n exit: { opacity: 0, y: -10 },\n};\n\n/**\n * Fade in from top animation\n * Use for: Dropdowns, modals, notifications\n */\nexport const fadeInDown: Variants = {\n initial: { opacity: 0, y: -20 },\n animate: { opacity: 1, y: 0 },\n exit: { opacity: 0, y: -10 },\n};\n\n/**\n * Slide in from left animation\n * Use for: Sidebars, navigation, list items\n */\nexport const slideInLeft: Variants = {\n initial: { opacity: 0, x: -20 },\n animate: { opacity: 1, x: 0 },\n exit: { opacity: 0, x: -20 },\n};\n\n/**\n * Slide in from right animation\n * Use for: Panels, drawers, cart sidebars\n */\nexport const slideInRight: Variants = {\n initial: { opacity: 0, x: 20 },\n animate: { opacity: 1, x: 0 },\n exit: { opacity: 0, x: 20 },\n};\n\n/**\n * Scale up animation\n * Use for: Modals, popups, dialogs\n */\nexport const scaleUp: Variants = {\n initial: { opacity: 0, scale: 0.95 },\n animate: { opacity: 1, scale: 1 },\n exit: { opacity: 0, scale: 0.95 },\n};\n\n/**\n * Simple fade animation\n * Use for: Overlays, backgrounds, subtle transitions\n */\nexport const fade: Variants = {\n initial: { opacity: 0 },\n animate: { opacity: 1 },\n exit: { opacity: 0 },\n};\n\n/**\n * Stagger container for child animations\n * Use with children that have their own variants\n *\n * @example\n * <motion.div variants={staggerContainer} initial=\"initial\" animate=\"animate\">\n * {items.map(item => (\n * <motion.div key={item.id} variants={fadeInUp}>\n * {item.content}\n * </motion.div>\n * ))}\n * </motion.div>\n */\nexport const staggerContainer: Variants = {\n initial: {},\n animate: {\n transition: {\n staggerChildren: 0.1,\n delayChildren: 0.1,\n },\n },\n};\n\n/**\n * Fast stagger for many items\n */\nexport const staggerContainerFast: Variants = {\n initial: {},\n animate: {\n transition: {\n staggerChildren: 0.05,\n delayChildren: 0.05,\n },\n },\n};\n\n/**\n * Slow stagger for fewer, larger items\n */\nexport const staggerContainerSlow: Variants = {\n initial: {},\n animate: {\n transition: {\n staggerChildren: 0.15,\n delayChildren: 0.2,\n },\n },\n};\n\n/**\n * Default spring transition\n * Use for: Most animations\n */\nexport const springTransition: Transition = {\n type: \"spring\",\n stiffness: 300,\n damping: 30,\n};\n\n/**\n * Smooth ease transition\n * Use for: Subtle, elegant animations\n */\nexport const easeTransition: Transition = {\n type: \"tween\",\n ease: \"easeOut\",\n duration: 0.3,\n};\n\n/**\n * Quick transition\n * Use for: Hover effects, quick feedback\n */\nexport const quickTransition: Transition = {\n type: \"tween\",\n ease: \"easeOut\",\n duration: 0.15,\n};\n\n/**\n * Hover scale effect props\n * Use with motion components for card hover effects\n *\n * @example\n * <motion.div {...hoverScale}>\n * <Card>...</Card>\n * </motion.div>\n */\nexport const hoverScale = {\n whileHover: { scale: 1.02 },\n whileTap: { scale: 0.98 },\n transition: springTransition,\n};\n\n/**\n * Subtle hover scale for smaller elements\n */\nexport const hoverScaleSubtle = {\n whileHover: { scale: 1.01 },\n whileTap: { scale: 0.99 },\n transition: quickTransition,\n};\n\n/**\n * Hover lift effect (scale + shadow simulation via y)\n */\nexport const hoverLift = {\n whileHover: { scale: 1.02, y: -4 },\n whileTap: { scale: 0.98, y: 0 },\n transition: springTransition,\n};\n\n/**\n * Button press effect\n */\nexport const buttonPress = {\n whileHover: { scale: 1.02 },\n whileTap: { scale: 0.95 },\n transition: quickTransition,\n};\n\n/**\n * Icon hover rotation\n */\nexport const iconHoverRotate = {\n whileHover: { rotate: 15 },\n transition: springTransition,\n};\n\n/**\n * Accordion/collapse animation variants\n * Use with AnimatePresence for expand/collapse\n *\n * @example\n * <AnimatePresence>\n * {isOpen && (\n * <motion.div\n * variants={collapseVariants}\n * initial=\"collapsed\"\n * animate=\"expanded\"\n * exit=\"collapsed\"\n * >\n * Content\n * </motion.div>\n * )}\n * </AnimatePresence>\n */\nexport const collapseVariants: Variants = {\n collapsed: {\n height: 0,\n opacity: 0,\n overflow: \"hidden\",\n },\n expanded: {\n height: \"auto\",\n opacity: 1,\n overflow: \"visible\",\n transition: {\n height: { duration: 0.3, ease: \"easeOut\" },\n opacity: { duration: 0.2, delay: 0.1 },\n },\n },\n};\n\n/**\n * Page transition variants\n * Use for route transitions with AnimatePresence\n */\nexport const pageTransition: Variants = {\n initial: { opacity: 0, y: 10 },\n animate: {\n opacity: 1,\n y: 0,\n transition: {\n duration: 0.3,\n ease: \"easeOut\",\n },\n },\n exit: {\n opacity: 0,\n y: -10,\n transition: {\n duration: 0.2,\n ease: \"easeIn\",\n },\n },\n};\n\n/**\n * Carousel slide variants\n * Direction should be passed as custom prop\n *\n * @example\n * <motion.div\n * custom={direction}\n * variants={carouselSlide}\n * initial=\"enter\"\n * animate=\"center\"\n * exit=\"exit\"\n * >\n */\nexport const carouselSlide: Variants = {\n enter: (direction: number) => ({\n x: direction > 0 ? 300 : -300,\n opacity: 0,\n }),\n center: {\n x: 0,\n opacity: 1,\n transition: {\n x: { type: \"spring\", stiffness: 300, damping: 30 },\n opacity: { duration: 0.2 },\n },\n },\n exit: (direction: number) => ({\n x: direction < 0 ? 300 : -300,\n opacity: 0,\n transition: {\n x: { type: \"spring\", stiffness: 300, damping: 30 },\n opacity: { duration: 0.2 },\n },\n }),\n};\n\n/**\n * Notification/toast animation\n */\nexport const notificationVariants: Variants = {\n initial: { opacity: 0, y: -20, scale: 0.95 },\n animate: {\n opacity: 1,\n y: 0,\n scale: 1,\n transition: springTransition,\n },\n exit: {\n opacity: 0,\n y: -20,\n scale: 0.95,\n transition: quickTransition,\n },\n};\n\n/**\n * List item animation for staggered lists\n */\nexport const listItem: Variants = {\n initial: { opacity: 0, x: -10 },\n animate: { opacity: 1, x: 0 },\n exit: { opacity: 0, x: 10 },\n};\n\n/**\n * Grid item animation for staggered grids\n */\nexport const gridItem: Variants = {\n initial: { opacity: 0, scale: 0.9 },\n animate: { opacity: 1, scale: 1 },\n exit: { opacity: 0, scale: 0.9 },\n};\n"
23
23
  },
24
24
  {
25
25
  "path": "animations/components.tsx",
@@ -13,25 +13,25 @@
13
13
  "path": "announcement-bar/index.ts",
14
14
  "type": "registry:index",
15
15
  "target": "$modules$/announcement-bar/index.ts",
16
- "content": "export { AnnouncementBar } from \"./announcement-bar\";\r\n"
16
+ "content": "export { AnnouncementBar } from \"./announcement-bar\";\n"
17
17
  },
18
18
  {
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 } 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"
22
+ "content": "import { useState } from \"react\";\nimport { Link } from \"react-router\";\nimport { useTranslation } from \"react-i18next\";\nimport { X, ArrowRight, Sparkles, Megaphone, Gift, Zap } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport { motion, AnimatePresence } from \"motion/react\";\n\ntype BarVariant = \"default\" | \"primary\" | \"warning\" | \"success\" | \"gradient\";\n\ninterface AnnouncementBarProps {\n message?: string;\n linkText?: string;\n linkUrl?: string;\n variant?: BarVariant;\n icon?: \"sparkles\" | \"megaphone\" | \"gift\" | \"zap\" | \"none\";\n dismissible?: boolean;\n storageKey?: string;\n className?: string;\n}\n\nconst icons = {\n sparkles: Sparkles,\n megaphone: Megaphone,\n gift: Gift,\n zap: Zap,\n none: null,\n};\n\nconst variantStyles: Record<BarVariant, string> = {\n default: \"bg-muted text-muted-foreground\",\n primary: \"bg-primary text-primary-foreground\",\n warning: \"bg-yellow-500 text-yellow-950\",\n success: \"bg-green-500 text-white\",\n gradient: \"bg-gradient-to-r from-primary via-purple-500 to-pink-500 text-white\",\n};\n\nexport function AnnouncementBar({\n message,\n linkText,\n linkUrl = \"#\",\n variant = \"primary\",\n icon = \"sparkles\",\n dismissible = true,\n storageKey = \"announcement-bar-dismissed\",\n className,\n}: AnnouncementBarProps) {\n const { t } = useTranslation(\"announcement-bar\");\n const [isVisible, setIsVisible] = useState(() => {\n if (typeof window === \"undefined\") return false;\n if (dismissible) {\n return !localStorage.getItem(storageKey);\n }\n return true;\n });\n\n const displayMessage = message || t(\"message\", \"Exciting news! Check out our latest features.\");\n const displayLinkText = linkText || t(\"linkText\", \"Learn more\");\n\n const handleDismiss = () => {\n if (dismissible) {\n localStorage.setItem(storageKey, \"true\");\n }\n setIsVisible(false);\n };\n\n const IconComponent = icons[icon];\n\n return (\n <AnimatePresence>\n {isVisible && (\n <motion.div\n initial={{ height: 0, opacity: 0 }}\n animate={{ height: \"auto\", opacity: 1 }}\n exit={{ height: 0, opacity: 0 }}\n transition={{ duration: 0.3 }}\n className={cn(\n \"relative overflow-hidden\",\n variantStyles[variant],\n className\n )}\n >\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\n <div className=\"flex items-center justify-center gap-2 py-2.5 text-sm font-medium\">\n {IconComponent && (\n <IconComponent className=\"h-4 w-4 flex-shrink-0\" />\n )}\n <span className=\"text-center\">{displayMessage}</span>\n {linkUrl && (\n <Link\n to={linkUrl}\n className=\"inline-flex items-center gap-1 font-semibold hover:underline underline-offset-2\"\n >\n {displayLinkText}\n <ArrowRight className=\"h-3 w-3\" />\n </Link>\n )}\n {dismissible && (\n <button\n onClick={handleDismiss}\n className=\"absolute right-4 p-1 rounded hover:bg-black/10 transition-colors\"\n aria-label={t(\"dismiss\", \"Dismiss\")}\n >\n <X className=\"h-4 w-4\" />\n </button>\n )}\n </div>\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n );\n}\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\": \"Important announcement: Limited time offer - Learn more →\",\r\n \"linkText\": \"Learn more\",\r\n \"dismiss\": \"Dismiss\"\r\n}"
28
+ "content": "{\n \"message\": \"Important announcement: Limited time offer - Learn more →\",\n \"linkText\": \"Learn more\",\n \"dismiss\": \"Dismiss\"\n}"
29
29
  },
30
30
  {
31
31
  "path": "announcement-bar/lang/tr.json",
32
32
  "type": "registry:lang",
33
33
  "target": "$modules$/announcement-bar/lang/tr.json",
34
- "content": "{\r\n \"message\": \"Önemli duyuru: Sınırlı süreli kampanya - Detaylar →\",\r\n \"linkText\": \"Daha fazla\",\r\n \"dismiss\": \"Kapat\"\r\n}"
34
+ "content": "{\n \"message\": \"Önemli duyuru: Sınırlı süreli kampanya - Detaylar →\",\n \"linkText\": \"Daha fazla\",\n \"dismiss\": \"Kapat\"\n}"
35
35
  }
36
36
  ],
37
37
  "exports": {
@@ -13,7 +13,7 @@
13
13
  "path": "api/index.ts",
14
14
  "type": "registry:index",
15
15
  "target": "$modules$/api/index.ts",
16
- "content": "export { customerClient } from \"./customer-client\";\r\nexport type { ApiClient } from \"./customer-client\";\r\nexport { getErrorMessage } from \"./get-error-message\";\r\n"
16
+ "content": "export { customerClient } from \"./customer-client\";\nexport type { ApiClient } from \"./customer-client\";\nexport { getErrorMessage } from \"./get-error-message\";\n"
17
17
  },
18
18
  {
19
19
  "path": "api/customer-client.ts",
@@ -21,13 +21,13 @@
21
21
  "path": "auth-core/index.ts",
22
22
  "type": "registry:index",
23
23
  "target": "$modules$/auth-core/index.ts",
24
- "content": "// Store\r\nexport { useAuthStore } from \"./auth-store\";\r\nexport type { User, AuthTokens } from \"./auth-store\";\r\n\r\n// Hook\r\nexport { useAuth } from \"./use-auth\";\r\n"
24
+ "content": "// Store\nexport { useAuthStore } from \"./auth-store\";\nexport type { User, AuthTokens } from \"./auth-store\";\n\n// Hook\nexport { useAuth } from \"./use-auth\";\n"
25
25
  },
26
26
  {
27
27
  "path": "auth-core/auth-store.ts",
28
28
  "type": "registry:store",
29
29
  "target": "$modules$/auth-core/auth-store.ts",
30
- "content": "import { create } from \"zustand\";\r\nimport { persist } from \"zustand/middleware\";\r\n\r\nexport interface User {\r\n username: string;\r\n email?: string;\r\n}\r\n\r\nexport interface AuthTokens {\r\n accessToken: string;\r\n refreshToken?: string;\r\n idToken?: string;\r\n encryptionKey?: string;\r\n expiresAt?: number; // Unix timestamp in milliseconds\r\n}\r\n\r\ninterface AuthState {\r\n user: User | null;\r\n tokens: AuthTokens | null;\r\n isAuthenticated: boolean;\r\n setAuth: (user: User, tokens: AuthTokens) => void;\r\n updateTokens: (tokens: AuthTokens) => void;\r\n clearAuth: () => void;\r\n isTokenExpired: () => boolean;\r\n getTimeUntilExpiry: () => number | null;\r\n}\r\n\r\nexport const useAuthStore = create<AuthState>()(\r\n persist(\r\n (set, get) => ({\r\n user: null,\r\n tokens: null,\r\n isAuthenticated: false,\r\n\r\n setAuth: (user, tokens) => set({ user, tokens, isAuthenticated: true }),\r\n\r\n updateTokens: (tokens) => set({ tokens }),\r\n\r\n clearAuth: () =>\r\n set({ user: null, tokens: null, isAuthenticated: false }),\r\n\r\n isTokenExpired: () => {\r\n const { tokens } = get();\r\n if (!tokens?.expiresAt) return false;\r\n // Consider token expired 30 seconds before actual expiry for safety margin\r\n return Date.now() >= tokens.expiresAt - 30000;\r\n },\r\n\r\n getTimeUntilExpiry: () => {\r\n const { tokens } = get();\r\n if (!tokens?.expiresAt) return null;\r\n return tokens.expiresAt - Date.now();\r\n },\r\n }),\r\n { name: \"auth-storage\" },\r\n ),\r\n);\r\n"
30
+ "content": "import { create } from \"zustand\";\nimport { persist } from \"zustand/middleware\";\n\nexport interface User {\n username: string;\n email?: string;\n}\n\nexport interface AuthTokens {\n accessToken: string;\n refreshToken?: string;\n idToken?: string;\n encryptionKey?: string;\n expiresAt?: number; // Unix timestamp in milliseconds\n}\n\ninterface AuthState {\n user: User | null;\n tokens: AuthTokens | null;\n isAuthenticated: boolean;\n setAuth: (user: User, tokens: AuthTokens) => void;\n updateTokens: (tokens: AuthTokens) => void;\n clearAuth: () => void;\n isTokenExpired: () => boolean;\n getTimeUntilExpiry: () => number | null;\n}\n\nexport const useAuthStore = create<AuthState>()(\n persist(\n (set, get) => ({\n user: null,\n tokens: null,\n isAuthenticated: false,\n\n setAuth: (user, tokens) => set({ user, tokens, isAuthenticated: true }),\n\n updateTokens: (tokens) => set({ tokens }),\n\n clearAuth: () =>\n set({ user: null, tokens: null, isAuthenticated: false }),\n\n isTokenExpired: () => {\n const { tokens } = get();\n if (!tokens?.expiresAt) return false;\n // Consider token expired 30 seconds before actual expiry for safety margin\n return Date.now() >= tokens.expiresAt - 30000;\n },\n\n getTimeUntilExpiry: () => {\n const { tokens } = get();\n if (!tokens?.expiresAt) return null;\n return tokens.expiresAt - Date.now();\n },\n }),\n { name: \"auth-storage\" },\n ),\n);\n"
31
31
  },
32
32
  {
33
33
  "path": "auth-core/use-auth.ts",
@@ -10,25 +10,25 @@
10
10
  "path": "bento-grid-section/index.ts",
11
11
  "type": "registry:index",
12
12
  "target": "$modules$/bento-grid-section/index.ts",
13
- "content": "export * from './bento-grid-section';\r\n"
13
+ "content": "export * from './bento-grid-section';\n"
14
14
  },
15
15
  {
16
16
  "path": "bento-grid-section/bento-grid-section.tsx",
17
17
  "type": "registry:component",
18
18
  "target": "$modules$/bento-grid-section/bento-grid-section.tsx",
19
- "content": "import type { ReactNode } from \"react\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\n// Bento Grid Components\r\nexport function BentoGrid({\r\n className,\r\n children,\r\n}: {\r\n className?: string;\r\n children?: ReactNode;\r\n}) {\r\n return (\r\n <div\r\n className={cn(\r\n \"grid md:auto-rows-[18rem] grid-cols-1 md:grid-cols-3 gap-4 max-w-7xl mx-auto\",\r\n className\r\n )}\r\n >\r\n {children}\r\n </div>\r\n );\r\n}\r\n\r\nexport function BentoGridItem({\r\n className,\r\n title,\r\n description,\r\n header,\r\n icon,\r\n}: {\r\n className?: string;\r\n title?: string | ReactNode;\r\n description?: string | ReactNode;\r\n header?: ReactNode;\r\n icon?: ReactNode;\r\n}) {\r\n return (\r\n <div\r\n className={cn(\r\n \"row-span-1 rounded-xl group/bento hover:shadow-xl transition duration-200 shadow-input dark:shadow-none p-4 dark:bg-black dark:border-white/[0.2] bg-white border border-transparent justify-between flex flex-col space-y-4\",\r\n className\r\n )}\r\n >\r\n {header}\r\n <div className=\"group-hover/bento:translate-x-2 transition duration-200\">\r\n {icon}\r\n <div className=\"font-sans font-bold text-neutral-600 dark:text-neutral-200 mb-2 mt-2\">\r\n {title}\r\n </div>\r\n <div className=\"font-sans font-normal text-neutral-600 text-xs dark:text-neutral-300\">\r\n {description}\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n}\r\n\r\n// Section Component\r\ninterface BentoGridSectionProps {\r\n title?: string;\r\n items: Array<{\r\n title: string;\r\n description: string;\r\n header?: ReactNode;\r\n icon?: ReactNode;\r\n className?: string;\r\n }>;\r\n className?: string;\r\n}\r\n\r\nexport function BentoGridSection({\r\n title,\r\n items,\r\n className,\r\n}: BentoGridSectionProps) {\r\n return (\r\n <section className={cn(\"py-16 md:py-24\", className)}>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n {title && (\r\n <h2 className=\"text-3xl md:text-4xl font-bold text-center mb-12\">\r\n {title}\r\n </h2>\r\n )}\r\n <BentoGrid>\r\n {items.map((item, i) => (\r\n <BentoGridItem\r\n key={i}\r\n title={item.title}\r\n description={item.description}\r\n header={item.header}\r\n icon={item.icon}\r\n className={item.className}\r\n />\r\n ))}\r\n </BentoGrid>\r\n </div>\r\n </section>\r\n );\r\n}\r\n"
19
+ "content": "import type { ReactNode } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\n// Bento Grid Components\nexport function BentoGrid({\n className,\n children,\n}: {\n className?: string;\n children?: ReactNode;\n}) {\n return (\n <div\n className={cn(\n \"grid md:auto-rows-[18rem] grid-cols-1 md:grid-cols-3 gap-4 max-w-7xl mx-auto\",\n className\n )}\n >\n {children}\n </div>\n );\n}\n\nexport function BentoGridItem({\n className,\n title,\n description,\n header,\n icon,\n}: {\n className?: string;\n title?: string | ReactNode;\n description?: string | ReactNode;\n header?: ReactNode;\n icon?: ReactNode;\n}) {\n return (\n <div\n className={cn(\n \"row-span-1 rounded-xl group/bento hover:shadow-xl transition duration-200 shadow-input dark:shadow-none p-4 dark:bg-black dark:border-white/[0.2] bg-white border border-transparent justify-between flex flex-col space-y-4\",\n className\n )}\n >\n {header}\n <div className=\"group-hover/bento:translate-x-2 transition duration-200\">\n {icon}\n <div className=\"font-sans font-bold text-neutral-600 dark:text-neutral-200 mb-2 mt-2\">\n {title}\n </div>\n <div className=\"font-sans font-normal text-neutral-600 text-xs dark:text-neutral-300\">\n {description}\n </div>\n </div>\n </div>\n );\n}\n\n// Section Component\ninterface BentoGridSectionProps {\n title?: string;\n items: Array<{\n title: string;\n description: string;\n header?: ReactNode;\n icon?: ReactNode;\n className?: string;\n }>;\n className?: string;\n}\n\nexport function BentoGridSection({\n title,\n items,\n className,\n}: BentoGridSectionProps) {\n return (\n <section className={cn(\"py-16 md:py-24\", className)}>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\n {title && (\n <h2 className=\"text-3xl md:text-4xl font-bold text-center mb-12\">\n {title}\n </h2>\n )}\n <BentoGrid>\n {items.map((item, i) => (\n <BentoGridItem\n key={i}\n title={item.title}\n description={item.description}\n header={item.header}\n icon={item.icon}\n className={item.className}\n />\n ))}\n </BentoGrid>\n </div>\n </section>\n );\n}\n"
20
20
  },
21
21
  {
22
22
  "path": "bento-grid-section/lang/en.json",
23
23
  "type": "registry:lang",
24
24
  "target": "$modules$/bento-grid-section/lang/en.json",
25
- "content": "{\r\n \"title\": \"Features\"\r\n}\r\n"
25
+ "content": "{\n \"title\": \"Features\"\n}\n"
26
26
  },
27
27
  {
28
28
  "path": "bento-grid-section/lang/tr.json",
29
29
  "type": "registry:lang",
30
30
  "target": "$modules$/bento-grid-section/lang/tr.json",
31
- "content": "{\r\n \"title\": \"Özellikler\"\r\n}\r\n"
31
+ "content": "{\n \"title\": \"Özellikler\"\n}\n"
32
32
  }
33
33
  ],
34
34
  "exports": {
@@ -13,31 +13,31 @@
13
13
  "path": "blog-core/index.ts",
14
14
  "type": "registry:index",
15
15
  "target": "$modules$/blog-core/index.ts",
16
- "content": "// Types\r\nexport * from './types';\r\n\r\n// Store (Zustand)\r\nexport { useBlogStore, useBlog } from './stores/blog-store';\r\n"
16
+ "content": "// Types\nexport * from './types';\n\n// Store (Zustand)\nexport { useBlogStore, useBlog } from './stores/blog-store';\n"
17
17
  },
18
18
  {
19
19
  "path": "blog-core/types.ts",
20
20
  "type": "registry:type",
21
21
  "target": "$modules$/blog-core/types.ts",
22
- "content": "export interface Post {\r\n id: number;\r\n title: string;\r\n slug: string;\r\n content: string;\r\n excerpt: string;\r\n featured_image: string;\r\n images: string[];\r\n categories: number[];\r\n author: string;\r\n author_avatar: string;\r\n published_at: string;\r\n updated_at: string;\r\n tags: string[];\r\n read_time: number;\r\n view_count: number;\r\n featured: boolean;\r\n published: boolean;\r\n meta_description: string;\r\n meta_keywords: string;\r\n}\r\n\r\nexport interface Author {\r\n id: number;\r\n name: string;\r\n slug: string;\r\n bio?: string;\r\n avatar?: string;\r\n social?: {\r\n twitter?: string;\r\n linkedin?: string;\r\n github?: string;\r\n website?: string;\r\n };\r\n}\r\n\r\nexport interface Comment {\r\n id: number;\r\n post_id: number;\r\n author_name: string;\r\n author_email: string;\r\n content: string;\r\n created_at: string;\r\n approved: boolean;\r\n}\r\n\r\nexport interface BlogCategory {\r\n id: number;\r\n name: string;\r\n slug: string;\r\n description?: string;\r\n image?: string;\r\n}\r\n\r\nexport interface BlogContextType {\r\n favorites: Post[];\r\n addToFavorites: (post: Post) => void;\r\n removeFromFavorites: (postId: string) => void;\r\n isFavorite: (postId: string) => boolean;\r\n clearFavorites: () => void;\r\n}\r\n\r\nexport interface BlogSettings {\r\n site: {\r\n name: string;\r\n description: string;\r\n url: string;\r\n logo?: string;\r\n };\r\n author: {\r\n name: string;\r\n bio?: string;\r\n avatar?: string;\r\n };\r\n social: {\r\n twitter?: string;\r\n linkedin?: string;\r\n github?: string;\r\n facebook?: string;\r\n };\r\n comments: {\r\n enabled: boolean;\r\n moderation: boolean;\r\n };\r\n newsletter: {\r\n enabled: boolean;\r\n provider?: string;\r\n };\r\n}\r\n"
22
+ "content": "export interface Post {\n id: number;\n title: string;\n slug: string;\n content: string;\n excerpt: string;\n featured_image: string;\n images: string[];\n categories: number[];\n author: string;\n author_avatar: string;\n published_at: string;\n updated_at: string;\n tags: string[];\n read_time: number;\n view_count: number;\n featured: boolean;\n published: boolean;\n meta_description: string;\n meta_keywords: string;\n}\n\nexport interface Author {\n id: number;\n name: string;\n slug: string;\n bio?: string;\n avatar?: string;\n social?: {\n twitter?: string;\n linkedin?: string;\n github?: string;\n website?: string;\n };\n}\n\nexport interface Comment {\n id: number;\n post_id: number;\n author_name: string;\n author_email: string;\n content: string;\n created_at: string;\n approved: boolean;\n}\n\nexport interface BlogCategory {\n id: number;\n name: string;\n slug: string;\n description?: string;\n image?: string;\n}\n\nexport interface BlogContextType {\n favorites: Post[];\n addToFavorites: (post: Post) => void;\n removeFromFavorites: (postId: string) => void;\n isFavorite: (postId: string) => boolean;\n clearFavorites: () => void;\n}\n\nexport interface BlogSettings {\n site: {\n name: string;\n description: string;\n url: string;\n logo?: string;\n };\n author: {\n name: string;\n bio?: string;\n avatar?: string;\n };\n social: {\n twitter?: string;\n linkedin?: string;\n github?: string;\n facebook?: string;\n };\n comments: {\n enabled: boolean;\n moderation: boolean;\n };\n newsletter: {\n enabled: boolean;\n provider?: string;\n };\n}\n"
23
23
  },
24
24
  {
25
25
  "path": "blog-core/stores/blog-store.ts",
26
26
  "type": "registry:store",
27
27
  "target": "$modules$/blog-core/stores/blog-store.ts",
28
- "content": "import { create } from \"zustand\";\r\nimport { persist } from \"zustand/middleware\";\r\nimport type { Post, BlogContextType } from \"../types\";\r\n\r\ninterface BlogStore {\r\n favorites: Post[];\r\n addToFavorites: (post: Post) => void;\r\n removeFromFavorites: (postId: string) => void;\r\n isFavorite: (postId: string) => boolean;\r\n clearFavorites: () => void;\r\n}\r\n\r\nexport const useBlogStore = create<BlogStore>()(\r\n persist(\r\n (set, get) => ({\r\n favorites: [],\r\n\r\n addToFavorites: (post) =>\r\n set((state) => {\r\n if (state.favorites.some((p) => p.id === post.id)) {\r\n return state;\r\n }\r\n return { favorites: [...state.favorites, post] };\r\n }),\r\n\r\n removeFromFavorites: (postId) =>\r\n set((state) => ({\r\n favorites: state.favorites.filter((p) => String(p.id) !== postId),\r\n })),\r\n\r\n isFavorite: (postId) => {\r\n return get().favorites.some((p) => String(p.id) === postId);\r\n },\r\n\r\n clearFavorites: () => set({ favorites: [] }),\r\n }),\r\n { name: \"blog_favorites\" }\r\n )\r\n);\r\n\r\n// Backward compatible hook - matches BlogContextType\r\nexport const useBlog = (): BlogContextType => {\r\n const store = useBlogStore();\r\n return {\r\n favorites: store.favorites,\r\n addToFavorites: store.addToFavorites,\r\n removeFromFavorites: store.removeFromFavorites,\r\n isFavorite: store.isFavorite,\r\n clearFavorites: store.clearFavorites,\r\n };\r\n};\r\n"
28
+ "content": "import { create } from \"zustand\";\nimport { persist } from \"zustand/middleware\";\nimport type { Post, BlogContextType } from \"../types\";\n\ninterface BlogStore {\n favorites: Post[];\n addToFavorites: (post: Post) => void;\n removeFromFavorites: (postId: string) => void;\n isFavorite: (postId: string) => boolean;\n clearFavorites: () => void;\n}\n\nexport const useBlogStore = create<BlogStore>()(\n persist(\n (set, get) => ({\n favorites: [],\n\n addToFavorites: (post) =>\n set((state) => {\n if (state.favorites.some((p) => p.id === post.id)) {\n return state;\n }\n return { favorites: [...state.favorites, post] };\n }),\n\n removeFromFavorites: (postId) =>\n set((state) => ({\n favorites: state.favorites.filter((p) => String(p.id) !== postId),\n })),\n\n isFavorite: (postId) => {\n return get().favorites.some((p) => String(p.id) === postId);\n },\n\n clearFavorites: () => set({ favorites: [] }),\n }),\n { name: \"blog_favorites\" }\n )\n);\n\n// Backward compatible hook - matches BlogContextType\nexport const useBlog = (): BlogContextType => {\n const store = useBlogStore();\n return {\n favorites: store.favorites,\n addToFavorites: store.addToFavorites,\n removeFromFavorites: store.removeFromFavorites,\n isFavorite: store.isFavorite,\n clearFavorites: store.clearFavorites,\n };\n};\n"
29
29
  },
30
30
  {
31
31
  "path": "blog-core/lang/en.json",
32
32
  "type": "registry:lang",
33
33
  "target": "$modules$/blog-core/lang/en.json",
34
- "content": "{\r\n \"favorites\": \"Favorites\",\r\n \"addedToFavorites\": \"Added to favorites\",\r\n \"removedFromFavorites\": \"Removed from favorites\",\r\n \"noFavorites\": \"No favorites yet\",\r\n \"clearFavorites\": \"Clear all favorites\",\r\n \"posts\": \"Posts\",\r\n \"categories\": \"Categories\",\r\n \"tags\": \"Tags\",\r\n \"readMore\": \"Read more\",\r\n \"minRead\": \"min read\",\r\n \"views\": \"views\",\r\n \"loadingPosts\": \"Loading posts...\",\r\n \"noPosts\": \"No posts found\",\r\n \"errorLoadingPosts\": \"Error loading posts\"\r\n}\r\n"
34
+ "content": "{\n \"favorites\": \"Favorites\",\n \"addedToFavorites\": \"Added to favorites\",\n \"removedFromFavorites\": \"Removed from favorites\",\n \"noFavorites\": \"No favorites yet\",\n \"clearFavorites\": \"Clear all favorites\",\n \"posts\": \"Posts\",\n \"categories\": \"Categories\",\n \"tags\": \"Tags\",\n \"readMore\": \"Read more\",\n \"minRead\": \"min read\",\n \"views\": \"views\",\n \"loadingPosts\": \"Loading posts...\",\n \"noPosts\": \"No posts found\",\n \"errorLoadingPosts\": \"Error loading posts\"\n}\n"
35
35
  },
36
36
  {
37
37
  "path": "blog-core/lang/tr.json",
38
38
  "type": "registry:lang",
39
39
  "target": "$modules$/blog-core/lang/tr.json",
40
- "content": "{\r\n \"favorites\": \"Favoriler\",\r\n \"addedToFavorites\": \"Favorilere eklendi\",\r\n \"removedFromFavorites\": \"Favorilerden kaldırıldı\",\r\n \"noFavorites\": \"Henüz favori yok\",\r\n \"clearFavorites\": \"Tüm favorileri temizle\",\r\n \"posts\": \"Yazılar\",\r\n \"categories\": \"Kategoriler\",\r\n \"tags\": \"Etiketler\",\r\n \"readMore\": \"Devamını oku\",\r\n \"minRead\": \"dk okuma\",\r\n \"views\": \"görüntüleme\",\r\n \"loadingPosts\": \"Yazılar yükleniyor...\",\r\n \"noPosts\": \"Yazı bulunamadı\",\r\n \"errorLoadingPosts\": \"Yazılar yüklenirken hata oluştu\"\r\n}\r\n"
40
+ "content": "{\n \"favorites\": \"Favoriler\",\n \"addedToFavorites\": \"Favorilere eklendi\",\n \"removedFromFavorites\": \"Favorilerden kaldırıldı\",\n \"noFavorites\": \"Henüz favori yok\",\n \"clearFavorites\": \"Tüm favorileri temizle\",\n \"posts\": \"Yazılar\",\n \"categories\": \"Kategoriler\",\n \"tags\": \"Etiketler\",\n \"readMore\": \"Devamını oku\",\n \"minRead\": \"dk okuma\",\n \"views\": \"görüntüleme\",\n \"loadingPosts\": \"Yazılar yükleniyor...\",\n \"noPosts\": \"Yazı bulunamadı\",\n \"errorLoadingPosts\": \"Yazılar yüklenirken hata oluştu\"\n}\n"
41
41
  }
42
42
  ],
43
43
  "exports": {
@@ -18,25 +18,25 @@
18
18
  "path": "blog-list-page/index.ts",
19
19
  "type": "registry:index",
20
20
  "target": "$modules$/blog-list-page/index.ts",
21
- "content": "export * from './blog-list-page';\r\nexport { BlogListPage as default } from './blog-list-page';\r\n"
21
+ "content": "export * from './blog-list-page';\nexport { BlogListPage as default } from './blog-list-page';\n"
22
22
  },
23
23
  {
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, useMemo } from \"react\";\r\nimport { useSearchParams } from \"react-router\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { Search, Filter } from \"lucide-react\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { FadeIn } from \"@/modules/animations\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport {\r\n Select,\r\n SelectContent,\r\n SelectItem,\r\n SelectTrigger,\r\n SelectValue,\r\n} from \"@/components/ui/select\";\r\nimport {\r\n Sheet,\r\n SheetContent,\r\n SheetDescription,\r\n SheetHeader,\r\n SheetTitle,\r\n SheetTrigger,\r\n} from \"@/components/ui/sheet\";\r\nimport { Checkbox } from \"@/components/ui/checkbox\";\r\nimport { PostCard } from \"@/modules/post-card/post-card\";\r\nimport { useDbList } from \"@/db\";\r\nimport type { Post, BlogCategory } from \"@/modules/blog-core/types\";\r\n\r\ninterface FilterSectionProps {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n t: any;\r\n searchTerm: string;\r\n setSearchTerm: (term: string) => void;\r\n categories: BlogCategory[];\r\n selectedCategories: string[];\r\n handleCategoryChange: (slug: string, checked: boolean) => void;\r\n allTags: string[];\r\n selectedTags: string[];\r\n handleTagChange: (tag: string, checked: boolean) => void;\r\n clearFilters: () => void;\r\n}\r\n\r\nfunction FilterSection({\r\n t,\r\n searchTerm,\r\n setSearchTerm,\r\n categories,\r\n selectedCategories,\r\n handleCategoryChange,\r\n allTags,\r\n selectedTags,\r\n handleTagChange,\r\n clearFilters,\r\n}: FilterSectionProps) {\r\n return (\r\n <div className=\"space-y-6\">\r\n <div>\r\n <h3 className=\"font-semibold mb-3 flex items-center gap-2\">\r\n <Search className=\"h-4 w-4\" />\r\n {t(\"search\")}\r\n </h3>\r\n <Input\r\n placeholder={t(\"searchPlaceholder\")}\r\n value={searchTerm}\r\n onChange={(e) => setSearchTerm(e.target.value)}\r\n />\r\n </div>\r\n\r\n <div>\r\n <h3 className=\"font-semibold mb-3\">{t(\"categories\")}</h3>\r\n <div className=\"space-y-2\">\r\n {categories.map((category) => (\r\n <div key={category.slug} className=\"flex items-center space-x-2\" data-db-table=\"blog_categories\" data-db-id={category.id || category.slug}>\r\n <Checkbox\r\n id={`category-${category.slug}`}\r\n checked={selectedCategories.includes(category.slug)}\r\n onCheckedChange={(checked) =>\r\n handleCategoryChange(category.slug, checked as boolean)\r\n }\r\n />\r\n <label\r\n htmlFor={`category-${category.slug}`}\r\n className=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer\"\r\n >\r\n {category.name}\r\n </label>\r\n </div>\r\n ))}\r\n </div>\r\n </div>\r\n\r\n {allTags.length > 0 && (\r\n <div>\r\n <h3 className=\"font-semibold mb-3\">{t(\"tags\")}</h3>\r\n <div className=\"space-y-2 max-h-48 overflow-y-auto\">\r\n {allTags.slice(0, 20).map((tag) => (\r\n <div key={tag} className=\"flex items-center space-x-2\">\r\n <Checkbox\r\n id={`tag-${tag}`}\r\n checked={selectedTags.includes(tag)}\r\n onCheckedChange={(checked) =>\r\n handleTagChange(tag, checked as boolean)\r\n }\r\n />\r\n <label\r\n htmlFor={`tag-${tag}`}\r\n className=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer\"\r\n >\r\n {tag}\r\n </label>\r\n </div>\r\n ))}\r\n </div>\r\n </div>\r\n )}\r\n\r\n {(searchTerm ||\r\n selectedCategories.length > 0 ||\r\n selectedTags.length > 0) && (\r\n <Button variant=\"outline\" onClick={clearFilters} className=\"w-full\">\r\n {t(\"clearFilters\")}\r\n </Button>\r\n )}\r\n </div>\r\n );\r\n}\r\n\r\nexport function BlogListPage() {\r\n const { t } = useTranslation(\"blog-list-page\");\r\n usePageTitle({ title: t(\"title\") });\r\n\r\n const [searchParams, setSearchParams] = useSearchParams();\r\n const [searchTerm, setSearchTerm] = useState(\r\n searchParams.get(\"search\") || \"\"\r\n );\r\n const [selectedCategories, setSelectedCategories] = useState<string[]>(\r\n searchParams.get(\"categories\")?.split(\",\").filter(Boolean) || []\r\n );\r\n const [selectedTags, setSelectedTags] = useState<string[]>(\r\n searchParams.get(\"tags\")?.split(\",\").filter(Boolean) || []\r\n );\r\n const [sortBy, setSortBy] = useState(searchParams.get(\"sort\") || \"newest\");\r\n const [viewMode, _setViewMode] = useState<\"grid\" | \"list\">(\"grid\");\r\n\r\n const { data: posts = [], isLoading: loading, error } = useDbList<Post>(\"posts\", {\r\n where: { published: 1 },\r\n });\r\n const { data: categories = [] } = useDbList<BlogCategory>(\"blog_categories\");\r\n\r\n const selectedCategoryIds = useMemo(() => {\r\n if (selectedCategories.length === 0) return new Set<number>();\r\n return new Set(\r\n categories.filter(c => selectedCategories.includes(c.slug)).map(c => c.id)\r\n );\r\n }, [selectedCategories, categories]);\r\n\r\n const filteredPosts = posts.filter((post) => {\r\n if (searchTerm) {\r\n const searchLower = searchTerm.toLowerCase();\r\n if (\r\n !post.title.toLowerCase().includes(searchLower) &&\r\n !post.excerpt.toLowerCase().includes(searchLower) &&\r\n !post.content.toLowerCase().includes(searchLower)\r\n ) {\r\n return false;\r\n }\r\n }\r\n\r\n if (selectedCategories.length > 0) {\r\n const hasMatchingCategory = post.categories?.some(\r\n (id) => selectedCategoryIds.has(id)\r\n );\r\n if (!hasMatchingCategory) return false;\r\n }\r\n\r\n if (selectedTags.length > 0) {\r\n const hasMatchingTag = selectedTags.some((tag) =>\r\n post.tags.includes(tag)\r\n );\r\n if (!hasMatchingTag) return false;\r\n }\r\n\r\n return true;\r\n });\r\n\r\n const sortedPosts = [...filteredPosts].sort((a, b) => {\r\n switch (sortBy) {\r\n case \"oldest\":\r\n return (\r\n new Date(a.published_at).getTime() -\r\n new Date(b.published_at).getTime()\r\n );\r\n case \"popular\":\r\n return (b.view_count || 0) - (a.view_count || 0);\r\n case \"reading-time\":\r\n return (a.read_time || 0) - (b.read_time || 0);\r\n case \"newest\":\r\n default:\r\n return (\r\n new Date(b.published_at).getTime() -\r\n new Date(a.published_at).getTime()\r\n );\r\n }\r\n });\r\n\r\n useEffect(() => {\r\n const params = new URLSearchParams();\r\n if (searchTerm) params.set(\"search\", searchTerm);\r\n if (selectedCategories.length)\r\n params.set(\"categories\", selectedCategories.join(\",\"));\r\n if (selectedTags.length) params.set(\"tags\", selectedTags.join(\",\"));\r\n if (sortBy !== \"newest\") params.set(\"sort\", sortBy);\r\n\r\n setSearchParams(params);\r\n }, [searchTerm, selectedCategories, selectedTags, sortBy, setSearchParams]);\r\n\r\n const handleCategoryChange = (categorySlug: string, checked: boolean) => {\r\n if (checked) {\r\n setSelectedCategories([...selectedCategories, categorySlug]);\r\n } else {\r\n setSelectedCategories(\r\n selectedCategories.filter((c) => c !== categorySlug)\r\n );\r\n }\r\n };\r\n\r\n const handleTagChange = (tag: string, checked: boolean) => {\r\n if (checked) {\r\n setSelectedTags([...selectedTags, tag]);\r\n } else {\r\n setSelectedTags(selectedTags.filter((t) => t !== tag));\r\n }\r\n };\r\n\r\n const allTags = Array.from(new Set(posts.flatMap((post) => post.tags)));\r\n\r\n const clearFilters = () => {\r\n setSearchTerm(\"\");\r\n setSelectedCategories([]);\r\n setSelectedTags([]);\r\n setSortBy(\"newest\");\r\n };\r\n\r\n const filterProps: FilterSectionProps = {\r\n t,\r\n searchTerm,\r\n setSearchTerm,\r\n categories,\r\n selectedCategories,\r\n handleCategoryChange,\r\n allTags,\r\n selectedTags,\r\n handleTagChange,\r\n clearFilters,\r\n };\r\n\r\n if (loading) {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\r\n <div className=\"animate-pulse space-y-4\">\r\n {Array.from({ length: 6 }).map((_, i) => (\r\n <div key={i} className=\"h-48 bg-muted rounded-lg\"></div>\r\n ))}\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n if (error) {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8 text-center\">\r\n <p className=\"text-destructive\">{t(\"error\")}</p>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\r\n <FadeIn className=\"flex flex-col lg:flex-row justify-between items-start lg:items-center gap-4 mb-8\">\r\n <div>\r\n <h1 className=\"text-3xl font-bold mb-2\">{t(\"title\")}</h1>\r\n <p className=\"text-muted-foreground\">{t(\"subtitle\")}</p>\r\n </div>\r\n\r\n <div className=\"flex items-center gap-4\">\r\n <Select value={sortBy} onValueChange={setSortBy}>\r\n <SelectTrigger className=\"w-[180px]\">\r\n <SelectValue />\r\n </SelectTrigger>\r\n <SelectContent>\r\n <SelectItem value=\"newest\">{t(\"sortNewest\")}</SelectItem>\r\n <SelectItem value=\"oldest\">{t(\"sortOldest\")}</SelectItem>\r\n <SelectItem value=\"popular\">{t(\"sortPopular\")}</SelectItem>\r\n <SelectItem value=\"reading-time\">\r\n {t(\"sortReadingTime\")}\r\n </SelectItem>\r\n </SelectContent>\r\n </Select>\r\n\r\n <Sheet>\r\n <SheetTrigger asChild>\r\n <Button variant=\"outline\" size=\"sm\" className=\"lg:hidden\">\r\n <Filter className=\"h-4 w-4 mr-2\" />\r\n {t(\"filters\")}\r\n </Button>\r\n </SheetTrigger>\r\n <SheetContent>\r\n <SheetHeader>\r\n <SheetTitle>{t(\"filters\")}</SheetTitle>\r\n <SheetDescription>{t(\"filterDescription\")}</SheetDescription>\r\n </SheetHeader>\r\n <div className=\"mt-6\">\r\n <FilterSection {...filterProps} />\r\n </div>\r\n </SheetContent>\r\n </Sheet>\r\n </div>\r\n </FadeIn>\r\n\r\n <div className=\"flex flex-col lg:flex-row gap-8\">\r\n <div className=\"hidden lg:block w-64 flex-shrink-0\">\r\n <div className=\"sticky top-4\">\r\n <FilterSection {...filterProps} />\r\n </div>\r\n </div>\r\n\r\n <div className=\"flex-1\">\r\n <div className=\"flex items-center justify-between mb-6\">\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"showing\")} {sortedPosts.length} {t(\"of\")} {posts.length}{\" \"}\r\n {t(\"posts\")}\r\n {searchTerm && (\r\n <span className=\"ml-1\">\r\n {t(\"for\")} \"<strong>{searchTerm}</strong>\"\r\n </span>\r\n )}\r\n </p>\r\n </div>\r\n\r\n {sortedPosts.length > 0 ? (\r\n <div\r\n className={`grid gap-6 ${\r\n viewMode === \"grid\"\r\n ? \"grid-cols-1 md:grid-cols-2 xl:grid-cols-3\"\r\n : \"grid-cols-1\"\r\n }`}\r\n >\r\n {sortedPosts.map((post) => (\r\n <div key={post.id} className=\"contents\" data-db-table=\"posts\" data-db-id={post.id}>\r\n <PostCard post={post} layout={viewMode} />\r\n </div>\r\n ))}\r\n </div>\r\n ) : (\r\n <div className=\"text-center py-12\">\r\n <p className=\"text-muted-foreground mb-4\">\r\n {t(\"noPostsFound\")}\r\n </p>\r\n <Button onClick={clearFilters} variant=\"outline\">\r\n {t(\"clearFilters\")}\r\n </Button>\r\n </div>\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default BlogListPage;\r\n"
27
+ "content": "import { useState, useEffect, useMemo } 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 { useDbList } from \"@/db\";\nimport type { Post, BlogCategory } from \"@/modules/blog-core/types\";\n\ninterface FilterSectionProps {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n t: any;\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\" data-db-table=\"blog_categories\" data-db-id={category.id || category.slug}>\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 { data: posts = [], isLoading: loading, error } = useDbList<Post>(\"posts\", {\n where: { published: 1 },\n });\n const { data: categories = [] } = useDbList<BlogCategory>(\"blog_categories\");\n\n const selectedCategoryIds = useMemo(() => {\n if (selectedCategories.length === 0) return new Set<number>();\n return new Set(\n categories.filter(c => selectedCategories.includes(c.slug)).map(c => c.id)\n );\n }, [selectedCategories, categories]);\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 = post.categories?.some(\n (id) => selectedCategoryIds.has(id)\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 <div key={post.id} className=\"contents\" data-db-table=\"posts\" data-db-id={post.id}>\n <PostCard post={post} layout={viewMode} />\n </div>\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",
31
31
  "type": "registry:lang",
32
32
  "target": "$modules$/blog-list-page/lang/en.json",
33
- "content": "{\r\n \"title\": \"Blog\",\r\n \"subtitle\": \"Discover our latest articles and insights\",\r\n \"allPosts\": \"All Posts\",\r\n \"featuredPosts\": \"Featured Posts\",\r\n \"recentPosts\": \"Recent Posts\",\r\n \"popularPosts\": \"Popular Posts\",\r\n \"categories\": \"Categories\",\r\n \"tags\": \"Tags\",\r\n \"filters\": \"Filters\",\r\n \"search\": \"Search\",\r\n \"searchPlaceholder\": \"Search posts...\",\r\n \"sortBy\": \"Sort By\",\r\n \"sortNewest\": \"Newest First\",\r\n \"sortOldest\": \"Oldest First\",\r\n \"sortPopular\": \"Most Popular\",\r\n \"sortReadingTime\": \"Reading Time\",\r\n \"showing\": \"Showing\",\r\n \"of\": \"of\",\r\n \"posts\": \"posts\",\r\n \"for\": \"for\",\r\n \"noPostsFound\": \"No posts found matching your criteria.\",\r\n \"clearFilters\": \"Clear Filters\",\r\n \"filterDescription\": \"Filter posts by category, tags, and more\",\r\n \"readMore\": \"Read More\",\r\n \"minRead\": \"min\",\r\n \"views\": \"views\",\r\n \"error\": \"Failed to load posts\"\r\n}\r\n"
33
+ "content": "{\n \"title\": \"Blog\",\n \"subtitle\": \"Discover our latest articles and insights\",\n \"allPosts\": \"All Posts\",\n \"featuredPosts\": \"Featured Posts\",\n \"recentPosts\": \"Recent Posts\",\n \"popularPosts\": \"Popular Posts\",\n \"categories\": \"Categories\",\n \"tags\": \"Tags\",\n \"filters\": \"Filters\",\n \"search\": \"Search\",\n \"searchPlaceholder\": \"Search posts...\",\n \"sortBy\": \"Sort By\",\n \"sortNewest\": \"Newest First\",\n \"sortOldest\": \"Oldest First\",\n \"sortPopular\": \"Most Popular\",\n \"sortReadingTime\": \"Reading Time\",\n \"showing\": \"Showing\",\n \"of\": \"of\",\n \"posts\": \"posts\",\n \"for\": \"for\",\n \"noPostsFound\": \"No posts found matching your criteria.\",\n \"clearFilters\": \"Clear Filters\",\n \"filterDescription\": \"Filter posts by category, tags, and more\",\n \"readMore\": \"Read More\",\n \"minRead\": \"min\",\n \"views\": \"views\",\n \"error\": \"Failed to load posts\"\n}\n"
34
34
  },
35
35
  {
36
36
  "path": "blog-list-page/lang/tr.json",
37
37
  "type": "registry:lang",
38
38
  "target": "$modules$/blog-list-page/lang/tr.json",
39
- "content": "{\r\n \"title\": \"Blog\",\r\n \"subtitle\": \"En son makalelerimizi ve görüşlerimizi keşfedin\",\r\n \"allPosts\": \"Tüm Yazılar\",\r\n \"featuredPosts\": \"Öne Çıkan Yazılar\",\r\n \"recentPosts\": \"Son Yazılar\",\r\n \"popularPosts\": \"Popüler Yazılar\",\r\n \"categories\": \"Kategoriler\",\r\n \"tags\": \"Etiketler\",\r\n \"filters\": \"Filtreler\",\r\n \"search\": \"Arama\",\r\n \"searchPlaceholder\": \"Yazı ara...\",\r\n \"sortBy\": \"Sırala\",\r\n \"sortNewest\": \"En Yeni\",\r\n \"sortOldest\": \"En Eski\",\r\n \"sortPopular\": \"En Popüler\",\r\n \"sortReadingTime\": \"Okuma Süresi\",\r\n \"showing\": \"Gösterilen\",\r\n \"of\": \"/\",\r\n \"posts\": \"yazı\",\r\n \"for\": \"için\",\r\n \"noPostsFound\": \"Kriterlere uygun yazı bulunamadı.\",\r\n \"clearFilters\": \"Filtreleri Temizle\",\r\n \"filterDescription\": \"Yazıları kategori, etiket ve daha fazlasına göre filtreleyin\",\r\n \"readMore\": \"Devamını Oku\",\r\n \"minRead\": \"dk\",\r\n \"views\": \"görüntülenme\",\r\n \"error\": \"Yazılar yüklenemedi\"\r\n}\r\n"
39
+ "content": "{\n \"title\": \"Blog\",\n \"subtitle\": \"En son makalelerimizi ve görüşlerimizi keşfedin\",\n \"allPosts\": \"Tüm Yazılar\",\n \"featuredPosts\": \"Öne Çıkan Yazılar\",\n \"recentPosts\": \"Son Yazılar\",\n \"popularPosts\": \"Popüler Yazılar\",\n \"categories\": \"Kategoriler\",\n \"tags\": \"Etiketler\",\n \"filters\": \"Filtreler\",\n \"search\": \"Arama\",\n \"searchPlaceholder\": \"Yazı ara...\",\n \"sortBy\": \"Sırala\",\n \"sortNewest\": \"En Yeni\",\n \"sortOldest\": \"En Eski\",\n \"sortPopular\": \"En Popüler\",\n \"sortReadingTime\": \"Okuma Süresi\",\n \"showing\": \"Gösterilen\",\n \"of\": \"/\",\n \"posts\": \"yazı\",\n \"for\": \"için\",\n \"noPostsFound\": \"Kriterlere uygun yazı bulunamadı.\",\n \"clearFilters\": \"Filtreleri Temizle\",\n \"filterDescription\": \"Yazıları kategori, etiket ve daha fazlasına göre filtreleyin\",\n \"readMore\": \"Devamını Oku\",\n \"minRead\": \"dk\",\n \"views\": \"görüntülenme\",\n \"error\": \"Yazılar yüklenemedi\"\n}\n"
40
40
  }
41
41
  ],
42
42
  "exports": {
@@ -15,25 +15,25 @@
15
15
  "path": "blog-section/index.ts",
16
16
  "type": "registry:index",
17
17
  "target": "$modules$/blog-section/index.ts",
18
- "content": "export * from './blog-section';\r\n"
18
+ "content": "export * from './blog-section';\n"
19
19
  },
20
20
  {
21
21
  "path": "blog-section/blog-section.tsx",
22
22
  "type": "registry:component",
23
23
  "target": "$modules$/blog-section/blog-section.tsx",
24
- "content": "import { useMemo } from \"react\";\r\nimport { Link } from \"react-router\";\r\nimport { ArrowRight } from \"lucide-react\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { Badge } from \"@/components/ui/badge\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport {\r\n Card,\r\n CardContent,\r\n CardFooter,\r\n CardHeader,\r\n} from \"@/components/ui/card\";\r\nimport { useDbList } from \"@/db\";\r\nimport type { Post, BlogCategory } from \"@/modules/blog-core/types\";\r\n\r\ninterface BlogSectionProps {\r\n posts?: Post[];\r\n loading?: boolean;\r\n className?: string;\r\n}\r\n\r\nexport function BlogSection({\r\n posts: propPosts,\r\n loading: propLoading,\r\n className,\r\n}: BlogSectionProps) {\r\n const { t } = useTranslation(\"blog-section\");\r\n const { data: blogCategories = [] } = useDbList<BlogCategory>(\"blog_categories\");\r\n const categoryMap = useMemo(() => new Map(blogCategories.map(c => [c.id, c])), [blogCategories]);\r\n const { data: hookPosts = [], isLoading: hookLoading } = useDbList<Post>(\"posts\", {\r\n where: { published: 1 },\r\n orderBy: [{ field: \"published_at\", direction: \"DESC\" }],\r\n limit: 3,\r\n });\r\n\r\n const posts = propPosts ?? hookPosts;\r\n const loading = propLoading ?? hookLoading;\r\n\r\n return (\r\n <section className={cn(\"py-16 md:py-24\", className)}>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n {/* Header */}\r\n <div className=\"text-center mb-12\">\r\n <Badge variant=\"secondary\" className=\"mb-4\">\r\n {t(\"tagline\", \"Latest Updates\")}\r\n </Badge>\r\n <h2 className=\"text-3xl font-bold md:text-4xl lg:text-5xl mb-4\">\r\n {t(\"title\", \"From Our Blog\")}\r\n </h2>\r\n <p className=\"text-muted-foreground max-w-2xl mx-auto mb-6\">\r\n {t(\r\n \"subtitle\",\r\n \"Discover the latest trends, tips, and insights from our team of experts.\"\r\n )}\r\n </p>\r\n <Button variant=\"link\" asChild>\r\n <Link to=\"/blog\">\r\n {t(\"viewAll\", \"View all articles\")}\r\n <ArrowRight className=\"ml-2 h-4 w-4\" />\r\n </Link>\r\n </Button>\r\n </div>\r\n\r\n {/* Posts Grid */}\r\n <div className=\"grid md:grid-cols-2 lg:grid-cols-3 gap-6 max-w-6xl mx-auto\">\r\n {loading ? (\r\n [...Array(3)].map((_, i) => (\r\n <Card key={i} className=\"overflow-hidden p-0 animate-pulse\">\r\n <div className=\"aspect-video bg-muted\"></div>\r\n <CardHeader className=\"pt-6 pb-2\">\r\n <div className=\"flex items-center gap-2 mb-2\">\r\n <div className=\"h-5 w-16 bg-muted rounded\"></div>\r\n <div className=\"h-4 w-20 bg-muted rounded\"></div>\r\n </div>\r\n <div className=\"h-6 w-3/4 bg-muted rounded\"></div>\r\n </CardHeader>\r\n <CardContent className=\"py-0\">\r\n <div className=\"h-4 w-full bg-muted rounded mb-2\"></div>\r\n <div className=\"h-4 w-2/3 bg-muted rounded\"></div>\r\n </CardContent>\r\n <CardFooter className=\"pb-2\">\r\n <div className=\"h-4 w-24 bg-muted rounded\"></div>\r\n </CardFooter>\r\n </Card>\r\n ))\r\n ) : (\r\n posts.map((post) => (\r\n <div key={post.id} className=\"contents\" data-db-table=\"posts\" data-db-id={post.id}>\r\n <Card className=\"overflow-hidden group p-0\">\r\n <div className=\"aspect-video overflow-hidden\">\r\n <Link to={`/blog/${post.slug}`}>\r\n <img\r\n src={post.featured_image || \"/images/placeholder.png\"}\r\n alt={post.title}\r\n className=\"w-full h-full object-cover transition-transform duration-300 group-hover:scale-105\"\r\n onError={(e) => {\r\n e.currentTarget.style.display = \"none\";\r\n }}\r\n />\r\n </Link>\r\n </div>\r\n <CardHeader className=\"pt-6 pb-2\">\r\n <div className=\"flex items-center gap-2 mb-2\">\r\n <Badge variant=\"outline\" className=\"text-xs\">\r\n {categoryMap.get(post.categories?.[0] as number)?.name}\r\n </Badge>\r\n <span className=\"text-xs text-muted-foreground\">\r\n {new Date(post.published_at).toLocaleDateString()}\r\n </span>\r\n </div>\r\n <Link to={`/blog/${post.slug}`}>\r\n <h3 className=\"text-lg font-semibold hover:text-primary transition-colors line-clamp-2\">\r\n {post.title}\r\n </h3>\r\n </Link>\r\n </CardHeader>\r\n <CardContent className=\"py-0\">\r\n <p className=\"text-sm text-muted-foreground line-clamp-2\">\r\n {post.excerpt}\r\n </p>\r\n </CardContent>\r\n <CardFooter className=\"pb-2\">\r\n <Link\r\n to={`/blog/${post.slug}`}\r\n className=\"text-sm font-medium text-primary hover:underline inline-flex items-center whitespace-nowrap\"\r\n >\r\n {t(\"readMore\", \"Read more\")}\r\n <ArrowRight className=\"ml-1 h-3 w-3 shrink-0\" />\r\n </Link>\r\n </CardFooter>\r\n </Card>\r\n </div>\r\n ))\r\n )}\r\n </div>\r\n </div>\r\n </section>\r\n );\r\n}\r\n"
24
+ "content": "import { useMemo } from \"react\";\nimport { Link } from \"react-router\";\nimport { ArrowRight } from \"lucide-react\";\nimport { useTranslation } from \"react-i18next\";\nimport { cn } from \"@/lib/utils\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n Card,\n CardContent,\n CardFooter,\n CardHeader,\n} from \"@/components/ui/card\";\nimport { useDbList } from \"@/db\";\nimport type { Post, BlogCategory } from \"@/modules/blog-core/types\";\n\ninterface BlogSectionProps {\n posts?: Post[];\n loading?: boolean;\n className?: string;\n}\n\nexport function BlogSection({\n posts: propPosts,\n loading: propLoading,\n className,\n}: BlogSectionProps) {\n const { t } = useTranslation(\"blog-section\");\n const { data: blogCategories = [] } = useDbList<BlogCategory>(\"blog_categories\");\n const categoryMap = useMemo(() => new Map(blogCategories.map(c => [c.id, c])), [blogCategories]);\n const { data: hookPosts = [], isLoading: hookLoading } = useDbList<Post>(\"posts\", {\n where: { published: 1 },\n orderBy: [{ field: \"published_at\", direction: \"DESC\" }],\n limit: 3,\n });\n\n const posts = propPosts ?? hookPosts;\n const loading = propLoading ?? hookLoading;\n\n return (\n <section className={cn(\"py-16 md:py-24\", className)}>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\n {/* Header */}\n <div className=\"text-center mb-12\">\n <Badge variant=\"secondary\" className=\"mb-4\">\n {t(\"tagline\", \"Latest Updates\")}\n </Badge>\n <h2 className=\"text-3xl font-bold md:text-4xl lg:text-5xl mb-4\">\n {t(\"title\", \"From Our Blog\")}\n </h2>\n <p className=\"text-muted-foreground max-w-2xl mx-auto mb-6\">\n {t(\n \"subtitle\",\n \"Discover the latest trends, tips, and insights from our team of experts.\"\n )}\n </p>\n <Button variant=\"link\" asChild>\n <Link to=\"/blog\">\n {t(\"viewAll\", \"View all articles\")}\n <ArrowRight className=\"ml-2 h-4 w-4\" />\n </Link>\n </Button>\n </div>\n\n {/* Posts Grid */}\n <div className=\"grid md:grid-cols-2 lg:grid-cols-3 gap-6 max-w-6xl mx-auto\">\n {loading ? (\n [...Array(3)].map((_, i) => (\n <Card key={i} className=\"overflow-hidden p-0 animate-pulse\">\n <div className=\"aspect-video bg-muted\"></div>\n <CardHeader className=\"pt-6 pb-2\">\n <div className=\"flex items-center gap-2 mb-2\">\n <div className=\"h-5 w-16 bg-muted rounded\"></div>\n <div className=\"h-4 w-20 bg-muted rounded\"></div>\n </div>\n <div className=\"h-6 w-3/4 bg-muted rounded\"></div>\n </CardHeader>\n <CardContent className=\"py-0\">\n <div className=\"h-4 w-full bg-muted rounded mb-2\"></div>\n <div className=\"h-4 w-2/3 bg-muted rounded\"></div>\n </CardContent>\n <CardFooter className=\"pb-2\">\n <div className=\"h-4 w-24 bg-muted rounded\"></div>\n </CardFooter>\n </Card>\n ))\n ) : (\n posts.map((post) => (\n <div key={post.id} className=\"contents\" data-db-table=\"posts\" data-db-id={post.id}>\n <Card className=\"overflow-hidden group p-0\">\n <div className=\"aspect-video overflow-hidden\">\n <Link to={`/blog/${post.slug}`}>\n <img\n src={post.featured_image || \"/images/placeholder.png\"}\n alt={post.title}\n className=\"w-full h-full object-cover transition-transform duration-300 group-hover:scale-105\"\n onError={(e) => {\n e.currentTarget.style.display = \"none\";\n }}\n />\n </Link>\n </div>\n <CardHeader className=\"pt-6 pb-2\">\n <div className=\"flex items-center gap-2 mb-2\">\n <Badge variant=\"outline\" className=\"text-xs\">\n {categoryMap.get(post.categories?.[0] as number)?.name}\n </Badge>\n <span className=\"text-xs text-muted-foreground\">\n {new Date(post.published_at).toLocaleDateString()}\n </span>\n </div>\n <Link to={`/blog/${post.slug}`}>\n <h3 className=\"text-lg font-semibold hover:text-primary transition-colors line-clamp-2\">\n {post.title}\n </h3>\n </Link>\n </CardHeader>\n <CardContent className=\"py-0\">\n <p className=\"text-sm text-muted-foreground line-clamp-2\">\n {post.excerpt}\n </p>\n </CardContent>\n <CardFooter className=\"pb-2\">\n <Link\n to={`/blog/${post.slug}`}\n className=\"text-sm font-medium text-primary hover:underline inline-flex items-center whitespace-nowrap\"\n >\n {t(\"readMore\", \"Read more\")}\n <ArrowRight className=\"ml-1 h-3 w-3 shrink-0\" />\n </Link>\n </CardFooter>\n </Card>\n </div>\n ))\n )}\n </div>\n </div>\n </section>\n );\n}\n"
25
25
  },
26
26
  {
27
27
  "path": "blog-section/lang/en.json",
28
28
  "type": "registry:lang",
29
29
  "target": "$modules$/blog-section/lang/en.json",
30
- "content": "{\r\n \"tagline\": \"Latest Updates\",\r\n \"title\": \"From Our Blog\",\r\n \"subtitle\": \"Insights, tutorials, and stories to help you succeed.\",\r\n \"viewAll\": \"View all articles\",\r\n \"readMore\": \"Read more\",\r\n \"post1Title\": \"Getting Started: A Complete Guide\",\r\n \"post1Summary\": \"Learn the essential steps to launch your project successfully and avoid common pitfalls.\",\r\n \"post1Category\": \"Tutorial\",\r\n \"post1Author\": \"Sarah Chen\",\r\n \"post1Date\": \"Jan 15, 2024\",\r\n \"post2Title\": \"10 Best Practices You Need to Know\",\r\n \"post2Summary\": \"Discover proven strategies that will help you work smarter and achieve better results.\",\r\n \"post2Category\": \"Best Practices\",\r\n \"post2Author\": \"Michael Park\",\r\n \"post2Date\": \"Jan 10, 2024\",\r\n \"post3Title\": \"Design Trends That Matter in 2024\",\r\n \"post3Summary\": \"Stay ahead of the curve with these emerging design trends shaping the industry.\",\r\n \"post3Category\": \"Design\",\r\n \"post3Author\": \"Emily Davis\",\r\n \"post3Date\": \"Jan 5, 2024\"\r\n}"
30
+ "content": "{\n \"tagline\": \"Latest Updates\",\n \"title\": \"From Our Blog\",\n \"subtitle\": \"Insights, tutorials, and stories to help you succeed.\",\n \"viewAll\": \"View all articles\",\n \"readMore\": \"Read more\",\n \"post1Title\": \"Getting Started: A Complete Guide\",\n \"post1Summary\": \"Learn the essential steps to launch your project successfully and avoid common pitfalls.\",\n \"post1Category\": \"Tutorial\",\n \"post1Author\": \"Sarah Chen\",\n \"post1Date\": \"Jan 15, 2024\",\n \"post2Title\": \"10 Best Practices You Need to Know\",\n \"post2Summary\": \"Discover proven strategies that will help you work smarter and achieve better results.\",\n \"post2Category\": \"Best Practices\",\n \"post2Author\": \"Michael Park\",\n \"post2Date\": \"Jan 10, 2024\",\n \"post3Title\": \"Design Trends That Matter in 2024\",\n \"post3Summary\": \"Stay ahead of the curve with these emerging design trends shaping the industry.\",\n \"post3Category\": \"Design\",\n \"post3Author\": \"Emily Davis\",\n \"post3Date\": \"Jan 5, 2024\"\n}"
31
31
  },
32
32
  {
33
33
  "path": "blog-section/lang/tr.json",
34
34
  "type": "registry:lang",
35
35
  "target": "$modules$/blog-section/lang/tr.json",
36
- "content": "{\r\n \"tagline\": \"Son Güncellemeler\",\r\n \"title\": \"Blogumuzdan\",\r\n \"subtitle\": \"Ekibimizden içgörüler, ipuçları ve hikayeleri keşfedin.\",\r\n \"viewAll\": \"Tüm makaleleri görüntüle\",\r\n \"readMore\": \"Devamını oku\",\r\n \"post1Title\": \"Başlangıç Rehberi\",\r\n \"post1Summary\": \"Kapsamlı rehberimizle hızlı bir şekilde başlamak için temel adımları öğrenin.\",\r\n \"post1Category\": \"Eğitim\",\r\n \"post1Author\": \"Sarah Chen\",\r\n \"post1Date\": \"15 Ocak 2024\",\r\n \"post2Title\": \"Başarı İçin En İyi Uygulamalar\",\r\n \"post2Summary\": \"Daha iyi sonuçlar elde etmenize yardımcı olan kanıtlanmış stratejileri keşfedin.\",\r\n \"post2Category\": \"En İyi Uygulamalar\",\r\n \"post2Author\": \"Michael Park\",\r\n \"post2Date\": \"10 Ocak 2024\",\r\n \"post3Title\": \"2024 Tasarım Trendleri\",\r\n \"post3Summary\": \"En son tasarım trendlerini ve projelerinizde nasıl uygulayacağınızı keşfedin.\",\r\n \"post3Category\": \"Tasarım\",\r\n \"post3Author\": \"Emily Davis\",\r\n \"post3Date\": \"5 Ocak 2024\"\r\n}"
36
+ "content": "{\n \"tagline\": \"Son Güncellemeler\",\n \"title\": \"Blogumuzdan\",\n \"subtitle\": \"Ekibimizden içgörüler, ipuçları ve hikayeleri keşfedin.\",\n \"viewAll\": \"Tüm makaleleri görüntüle\",\n \"readMore\": \"Devamını oku\",\n \"post1Title\": \"Başlangıç Rehberi\",\n \"post1Summary\": \"Kapsamlı rehberimizle hızlı bir şekilde başlamak için temel adımları öğrenin.\",\n \"post1Category\": \"Eğitim\",\n \"post1Author\": \"Sarah Chen\",\n \"post1Date\": \"15 Ocak 2024\",\n \"post2Title\": \"Başarı İçin En İyi Uygulamalar\",\n \"post2Summary\": \"Daha iyi sonuçlar elde etmenize yardımcı olan kanıtlanmış stratejileri keşfedin.\",\n \"post2Category\": \"En İyi Uygulamalar\",\n \"post2Author\": \"Michael Park\",\n \"post2Date\": \"10 Ocak 2024\",\n \"post3Title\": \"2024 Tasarım Trendleri\",\n \"post3Summary\": \"En son tasarım trendlerini ve projelerinizde nasıl uygulayacağınızı keşfedin.\",\n \"post3Category\": \"Tasarım\",\n \"post3Author\": \"Emily Davis\",\n \"post3Date\": \"5 Ocak 2024\"\n}"
37
37
  }
38
38
  ],
39
39
  "exports": {