@promakeai/cli 0.0.6 → 0.1.1

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 (114) hide show
  1. package/dist/index.js +31 -31
  2. package/dist/registry/about-page.json +4 -2
  3. package/dist/registry/about-section.json +2 -2
  4. package/dist/registry/announcement-bar.json +43 -0
  5. package/dist/registry/auth-core.json +43 -0
  6. package/dist/registry/auth.json +1 -1
  7. package/dist/registry/blog-list-page.json +3 -2
  8. package/dist/registry/blog-section.json +1 -1
  9. package/dist/registry/cart-page.json +3 -3
  10. package/dist/registry/case-study-page.json +48 -0
  11. package/dist/registry/checkout-page.json +6 -5
  12. package/dist/registry/coming-soon-page-minimal.json +45 -0
  13. package/dist/registry/coming-soon-page.json +47 -0
  14. package/dist/registry/contact-info-grid.json +1 -1
  15. package/dist/registry/contact-page-centered.json +2 -2
  16. package/dist/registry/contact-page-map-overlay.json +4 -3
  17. package/dist/registry/contact-page-map-split.json +4 -3
  18. package/dist/registry/contact-page-split.json +3 -3
  19. package/dist/registry/contact-page.json +5 -3
  20. package/dist/registry/cookie-consent.json +43 -0
  21. package/dist/registry/cookies-page.json +3 -1
  22. package/dist/registry/cta-section.json +1 -1
  23. package/dist/registry/db.json +1 -1
  24. package/dist/registry/docs/about-page.md +5 -0
  25. package/dist/registry/docs/announcement-bar.md +38 -0
  26. package/dist/registry/docs/auth-core.md +64 -0
  27. package/dist/registry/docs/blog-list-page.md +1 -0
  28. package/dist/registry/docs/case-study-page.md +39 -0
  29. package/dist/registry/docs/checkout-page.md +2 -1
  30. package/dist/registry/docs/coming-soon-page-minimal.md +32 -0
  31. package/dist/registry/docs/coming-soon-page.md +37 -0
  32. package/dist/registry/docs/contact-page-map-overlay.md +2 -2
  33. package/dist/registry/docs/contact-page-map-split.md +2 -2
  34. package/dist/registry/docs/contact-page.md +5 -0
  35. package/dist/registry/docs/cookie-consent.md +37 -0
  36. package/dist/registry/docs/cookies-page.md +5 -0
  37. package/dist/registry/docs/ecommerce-core.md +4 -3
  38. package/dist/registry/docs/forgot-password-page-split.md +45 -0
  39. package/dist/registry/docs/forgot-password-page.md +14 -5
  40. package/dist/registry/docs/header-ecommerce.md +1 -0
  41. package/dist/registry/docs/hero-carousel.md +37 -0
  42. package/dist/registry/docs/landing-page-app.md +43 -0
  43. package/dist/registry/docs/landing-page-saas.md +41 -0
  44. package/dist/registry/docs/login-page-split.md +13 -4
  45. package/dist/registry/docs/login-page.md +17 -4
  46. package/dist/registry/docs/logo-cloud.md +33 -0
  47. package/dist/registry/docs/masonry-grid.md +37 -0
  48. package/dist/registry/docs/my-orders-page.md +44 -0
  49. package/dist/registry/docs/order-confirmation-page.md +41 -0
  50. package/dist/registry/docs/portfolio-page.md +38 -0
  51. package/dist/registry/docs/pricing-page.md +38 -0
  52. package/dist/registry/docs/privacy-page.md +5 -0
  53. package/dist/registry/docs/product-quick-view.md +37 -0
  54. package/dist/registry/docs/reading-progress.md +31 -0
  55. package/dist/registry/docs/register-page-split.md +45 -0
  56. package/dist/registry/docs/register-page.md +14 -7
  57. package/dist/registry/docs/reset-password-page-split.md +45 -0
  58. package/dist/registry/docs/reset-password-page.md +36 -0
  59. package/dist/registry/docs/share-buttons.md +37 -0
  60. package/dist/registry/docs/team-page.md +38 -0
  61. package/dist/registry/docs/terms-page.md +5 -0
  62. package/dist/registry/docs/timeline-section.md +37 -0
  63. package/dist/registry/docs/video-hero.md +41 -0
  64. package/dist/registry/ecommerce-core.json +17 -1
  65. package/dist/registry/empty-page.json +1 -1
  66. package/dist/registry/faq-categorized.json +1 -1
  67. package/dist/registry/faq-simple.json +1 -1
  68. package/dist/registry/favorites-ecommerce-block.json +1 -1
  69. package/dist/registry/feature-section.json +2 -2
  70. package/dist/registry/footer.json +1 -1
  71. package/dist/registry/forgot-password-page-split.json +50 -0
  72. package/dist/registry/forgot-password-page.json +9 -7
  73. package/dist/registry/header-ecommerce.json +3 -2
  74. package/dist/registry/header-mega.json +1 -1
  75. package/dist/registry/hero-carousel.json +45 -0
  76. package/dist/registry/hero-cta.json +2 -2
  77. package/dist/registry/hero-gradient.json +1 -1
  78. package/dist/registry/hero.json +1 -1
  79. package/dist/registry/index.json +22 -2
  80. package/dist/registry/landing-page-app.json +47 -0
  81. package/dist/registry/landing-page-saas.json +47 -0
  82. package/dist/registry/login-page-split.json +11 -7
  83. package/dist/registry/login-page.json +4 -4
  84. package/dist/registry/logo-cloud.json +41 -0
  85. package/dist/registry/masonry-grid.json +43 -0
  86. package/dist/registry/my-orders-page.json +52 -0
  87. package/dist/registry/order-confirmation-page.json +49 -0
  88. package/dist/registry/portfolio-page.json +45 -0
  89. package/dist/registry/pricing-page.json +47 -0
  90. package/dist/registry/pricing-section.json +1 -1
  91. package/dist/registry/privacy-page.json +3 -1
  92. package/dist/registry/product-detail-block.json +1 -1
  93. package/dist/registry/product-quick-view.json +46 -0
  94. package/dist/registry/products-page.json +3 -3
  95. package/dist/registry/reading-progress.json +43 -0
  96. package/dist/registry/register-page-split.json +50 -0
  97. package/dist/registry/register-page.json +9 -7
  98. package/dist/registry/reset-password-page-split.json +50 -0
  99. package/dist/registry/reset-password-page.json +39 -0
  100. package/dist/registry/share-buttons.json +46 -0
  101. package/dist/registry/team-page.json +47 -0
  102. package/dist/registry/terms-page.json +3 -1
  103. package/dist/registry/testimonials-carousel.json +1 -1
  104. package/dist/registry/testimonials-grid.json +1 -1
  105. package/dist/registry/timeline-section.json +43 -0
  106. package/dist/registry/video-hero.json +42 -0
  107. package/package.json +1 -1
  108. package/template/index.html +5 -5
  109. package/template/src/App.tsx +4 -0
  110. package/template/src/components/GoogleAnalytics.tsx +34 -0
  111. package/template/src/components/Layout.tsx +1 -1
  112. package/template/src/components/ScriptInjector.tsx +62 -0
  113. package/template/src/constants/constants.json +8 -2
  114. package/template/src/pages/Index.tsx +5 -1
@@ -3,7 +3,9 @@
3
3
  "type": "registry:page",
4
4
  "title": "About Page",
5
5
  "description": "Company/personal about page with hero section, mission statement, team member cards with photos, company timeline/history, values section with icons, and statistics counters. Responsive layout with alternating content sections. Supports both corporate and personal portfolio styles.",
6
- "registryDependencies": [],
6
+ "registryDependencies": [
7
+ "animations"
8
+ ],
7
9
  "usage": "import { AboutPage } from '@/modules/about-page';\n\n<Route path=\"/about\" element={<AboutPage />} />\n\n• Sections: hero, mission, team, timeline, values, stats\n• Edit content in lang/en.json\n• Wrapped with Layout component",
8
10
  "route": {
9
11
  "path": "/about",
@@ -26,7 +28,7 @@
26
28
  "path": "about-page/lang/en.json",
27
29
  "type": "registry:lang",
28
30
  "target": "$modules$/about-page/lang/en.json",
29
- "content": "{\r\n \"title\": \"About Us\",\r\n \"subtitle\": \"AI will customize this about page subtitle based on your site. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"storyTitle\": \"Our Story\",\r\n \"storyP1\": \"This is placeholder text for your story. AI will replace this with your actual background and journey. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"storyP2\": \"Placeholder text for the second paragraph of your story. AI will customize this based on your journey and values. Sed do eiusmod tempor incididunt ut labore.\",\r\n \"missionTitle\": \"Our Mission\",\r\n \"missionDesc\": \"AI will customize this mission description based on your goals. Ut enim ad minim veniam, quis nostrud exercitation.\",\r\n \"valuesTitle\": \"Our Values\",\r\n \"valuesDesc\": \"This values description will be replaced by AI with your actual values. Duis aute irure dolor in reprehenderit.\",\r\n \"teamTitle\": \"Our Team\",\r\n \"teamDesc\": \"Placeholder team description. AI will customize this based on your team structure and culture.\",\r\n \"qualityTitle\": \"Quality First\",\r\n \"qualityDesc\": \"AI will replace this quality description with relevant information about your quality standards.\",\r\n \"customersValue\": \"500+\",\r\n \"customersLabel\": \"Happy Customers\",\r\n \"projectsValue\": \"1000+\",\r\n \"projectsLabel\": \"Projects Completed\",\r\n \"experienceValue\": \"10+\",\r\n \"experienceLabel\": \"Years Experience\",\r\n \"satisfactionValue\": \"99%\",\r\n \"satisfactionLabel\": \"Client Satisfaction\",\r\n \"ctaTitle\": \"Ready to Work Together?\",\r\n \"ctaDesc\": \"AI will customize this CTA description based on your site goals. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\"\r\n}\r\n"
31
+ "content": "{\r\n \"title\": \"About Us\",\r\n \"subtitle\": \"Ask Promake to customize this about page subtitle based on your site. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"storyTitle\": \"Our Story\",\r\n \"storyP1\": \"This is placeholder text for your story. Ask Promake to replace this with your actual background and journey. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"storyP2\": \"Placeholder text for the second paragraph of your story. Ask Promake to customize this based on your journey and values. Sed do eiusmod tempor incididunt ut labore.\",\r\n \"missionTitle\": \"Our Mission\",\r\n \"missionDesc\": \"Ask Promake to customize this mission description based on your goals. Ut enim ad minim veniam, quis nostrud exercitation.\",\r\n \"valuesTitle\": \"Our Values\",\r\n \"valuesDesc\": \"This values description will be replaced by Promake with your actual values. Duis aute irure dolor in reprehenderit.\",\r\n \"teamTitle\": \"Our Team\",\r\n \"teamDesc\": \"Placeholder team description. Ask Promake to customize this based on your team structure and culture.\",\r\n \"qualityTitle\": \"Quality First\",\r\n \"qualityDesc\": \"Ask Promake to replace this quality description with relevant information about your quality standards.\",\r\n \"customersValue\": \"500+\",\r\n \"customersLabel\": \"Happy Customers\",\r\n \"projectsValue\": \"1000+\",\r\n \"projectsLabel\": \"Projects Completed\",\r\n \"experienceValue\": \"10+\",\r\n \"experienceLabel\": \"Years Experience\",\r\n \"satisfactionValue\": \"99%\",\r\n \"satisfactionLabel\": \"Client Satisfaction\",\r\n \"ctaTitle\": \"Ready to Work Together?\",\r\n \"ctaDesc\": \"Ask Promake to customize this CTA description based on your site goals. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\"\r\n}\r\n"
30
32
  },
31
33
  {
32
34
  "path": "about-page/lang/tr.json",
@@ -16,13 +16,13 @@
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] 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\";\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"
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\": \"AI will customize this description based on your site and audience. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.\",\r\n \"mainImageAlt\": \"Our team\",\r\n \"secondaryImageAlt\": \"Our office\",\r\n \"cardTitle\": \"Our Mission\",\r\n \"cardDescription\": \"This mission description will be customized by AI based on your goals and values. Ut enim ad minim veniam, quis nostrud exercitation.\",\r\n \"cardButton\": \"Learn More\",\r\n \"companiesTitle\": \"AI will customize this companies title\",\r\n \"statsTitle\": \"Our Achievements\",\r\n \"statsDescription\": \"This stats description will be replaced by AI with relevant information about your milestones.\",\r\n \"stat1Value\": \"500+\",\r\n \"stat1Label\": \"Happy Clients\",\r\n \"stat2Value\": \"1000+\",\r\n \"stat2Label\": \"Projects Completed\",\r\n \"stat3Value\": \"99%\",\r\n \"stat3Label\": \"Satisfaction Rate\",\r\n \"stat4Value\": \"15+\",\r\n \"stat4Label\": \"Years Experience\"\r\n}\r\n"
25
+ "content": "{\r\n \"title\": \"About Us\",\r\n \"description\": \"Ask Promake to customize this description based on your site and audience. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.\",\r\n \"mainImageAlt\": \"Our team\",\r\n \"secondaryImageAlt\": \"Our office\",\r\n \"cardTitle\": \"Our Mission\",\r\n \"cardDescription\": \"This mission description will be customized by Promake based on your goals and values. Ut enim ad minim veniam, quis nostrud exercitation.\",\r\n \"cardButton\": \"Learn More\",\r\n \"companiesTitle\": \"Ask Promake to customize this companies title\",\r\n \"statsTitle\": \"Our Achievements\",\r\n \"statsDescription\": \"This stats description will be replaced by Promake with relevant information about your milestones.\",\r\n \"stat1Value\": \"500+\",\r\n \"stat1Label\": \"Happy Clients\",\r\n \"stat2Value\": \"1000+\",\r\n \"stat2Label\": \"Projects Completed\",\r\n \"stat3Value\": \"99%\",\r\n \"stat3Label\": \"Satisfaction Rate\",\r\n \"stat4Value\": \"15+\",\r\n \"stat4Label\": \"Years Experience\"\r\n}\r\n"
26
26
  },
27
27
  {
28
28
  "path": "about-section/lang/tr.json",
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "announcement-bar",
3
+ "type": "registry:component",
4
+ "title": "Announcement Bar",
5
+ "description": "Top notification bar with multiple style variants (default, primary, warning, success, gradient), dismissible with localStorage persistence, customizable icon, and link support. Slide animation.",
6
+ "dependencies": [
7
+ "motion"
8
+ ],
9
+ "registryDependencies": [],
10
+ "usage": "import { AnnouncementBar } from '@/modules/announcement-bar';\n\n<AnnouncementBar\n message=\"New features available!\"\n linkText=\"Learn more\"\n linkUrl=\"/blog/new-features\"\n variant=\"gradient\"\n icon=\"sparkles\"\n/>\n\n• Variants: default, primary, warning, success, gradient\n• Icons: sparkles, megaphone, gift, zap, none\n• Dismissible with storage key",
11
+ "files": [
12
+ {
13
+ "path": "announcement-bar/index.ts",
14
+ "type": "registry:index",
15
+ "target": "$modules$/announcement-bar/index.ts",
16
+ "content": "export { AnnouncementBar } from \"./announcement-bar\";\r\n"
17
+ },
18
+ {
19
+ "path": "announcement-bar/announcement-bar.tsx",
20
+ "type": "registry:component",
21
+ "target": "$modules$/announcement-bar/announcement-bar.tsx",
22
+ "content": "import { useState, useEffect } from \"react\";\r\nimport { Link } from \"react-router\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { X, ArrowRight, Sparkles, Megaphone, Gift, Zap } from \"lucide-react\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { motion, AnimatePresence } from \"motion/react\";\r\n\r\ntype BarVariant = \"default\" | \"primary\" | \"warning\" | \"success\" | \"gradient\";\r\n\r\ninterface AnnouncementBarProps {\r\n message?: string;\r\n linkText?: string;\r\n linkUrl?: string;\r\n variant?: BarVariant;\r\n icon?: \"sparkles\" | \"megaphone\" | \"gift\" | \"zap\" | \"none\";\r\n dismissible?: boolean;\r\n storageKey?: string;\r\n className?: string;\r\n}\r\n\r\nconst icons = {\r\n sparkles: Sparkles,\r\n megaphone: Megaphone,\r\n gift: Gift,\r\n zap: Zap,\r\n none: null,\r\n};\r\n\r\nconst variantStyles: Record<BarVariant, string> = {\r\n default: \"bg-muted text-muted-foreground\",\r\n primary: \"bg-primary text-primary-foreground\",\r\n warning: \"bg-yellow-500 text-yellow-950\",\r\n success: \"bg-green-500 text-white\",\r\n gradient: \"bg-gradient-to-r from-primary via-purple-500 to-pink-500 text-white\",\r\n};\r\n\r\nexport function AnnouncementBar({\r\n message,\r\n linkText,\r\n linkUrl = \"#\",\r\n variant = \"primary\",\r\n icon = \"sparkles\",\r\n dismissible = true,\r\n storageKey = \"announcement-bar-dismissed\",\r\n className,\r\n}: AnnouncementBarProps) {\r\n const { t } = useTranslation(\"announcement-bar\");\r\n const [isVisible, setIsVisible] = useState(false);\r\n\r\n const displayMessage = message || t(\"message\", \"Exciting news! Check out our latest features.\");\r\n const displayLinkText = linkText || t(\"linkText\", \"Learn more\");\r\n\r\n useEffect(() => {\r\n if (dismissible) {\r\n const dismissed = localStorage.getItem(storageKey);\r\n if (!dismissed) {\r\n setIsVisible(true);\r\n }\r\n } else {\r\n setIsVisible(true);\r\n }\r\n }, [dismissible, storageKey]);\r\n\r\n const handleDismiss = () => {\r\n if (dismissible) {\r\n localStorage.setItem(storageKey, \"true\");\r\n }\r\n setIsVisible(false);\r\n };\r\n\r\n const IconComponent = icons[icon];\r\n\r\n return (\r\n <AnimatePresence>\r\n {isVisible && (\r\n <motion.div\r\n initial={{ height: 0, opacity: 0 }}\r\n animate={{ height: \"auto\", opacity: 1 }}\r\n exit={{ height: 0, opacity: 0 }}\r\n transition={{ duration: 0.3 }}\r\n className={cn(\r\n \"relative overflow-hidden\",\r\n variantStyles[variant],\r\n className\r\n )}\r\n >\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n <div className=\"flex items-center justify-center gap-2 py-2.5 text-sm font-medium\">\r\n {IconComponent && (\r\n <IconComponent className=\"h-4 w-4 flex-shrink-0\" />\r\n )}\r\n <span className=\"text-center\">{displayMessage}</span>\r\n {linkUrl && (\r\n <Link\r\n to={linkUrl}\r\n className=\"inline-flex items-center gap-1 font-semibold hover:underline underline-offset-2\"\r\n >\r\n {displayLinkText}\r\n <ArrowRight className=\"h-3 w-3\" />\r\n </Link>\r\n )}\r\n {dismissible && (\r\n <button\r\n onClick={handleDismiss}\r\n className=\"absolute right-4 p-1 rounded hover:bg-black/10 transition-colors\"\r\n aria-label={t(\"dismiss\", \"Dismiss\")}\r\n >\r\n <X className=\"h-4 w-4\" />\r\n </button>\r\n )}\r\n </div>\r\n </div>\r\n </motion.div>\r\n )}\r\n </AnimatePresence>\r\n );\r\n}\r\n"
23
+ },
24
+ {
25
+ "path": "announcement-bar/lang/en.json",
26
+ "type": "registry:lang",
27
+ "target": "$modules$/announcement-bar/lang/en.json",
28
+ "content": "{\r\n \"message\": \"Ask Promake to customize this announcement message based on your news or promotion.\",\r\n \"linkText\": \"Learn more\",\r\n \"dismiss\": \"Dismiss\"\r\n}\r\n"
29
+ },
30
+ {
31
+ "path": "announcement-bar/lang/tr.json",
32
+ "type": "registry:lang",
33
+ "target": "$modules$/announcement-bar/lang/tr.json",
34
+ "content": "{\r\n \"message\": \"Heyecan verici haber! En son özelliklerimize göz atın.\",\r\n \"linkText\": \"Daha fazla\",\r\n \"dismiss\": \"Kapat\"\r\n}\r\n"
35
+ }
36
+ ],
37
+ "exports": {
38
+ "types": [],
39
+ "variables": [
40
+ "AnnouncementBar"
41
+ ]
42
+ }
43
+ }
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "auth-core",
3
+ "type": "registry:module",
4
+ "title": "Auth Core",
5
+ "description": "Core authentication system with Zustand store, JWT token management with auto-refresh. No pages included - use separate page modules (login-page, register-page, etc.) for UI.",
6
+ "dependencies": [
7
+ "zustand"
8
+ ],
9
+ "registryDependencies": [
10
+ "api"
11
+ ],
12
+ "usage": "import { useAuth, useAuthStore } from '@/modules/auth-core';\n\n// Use the hook for auth operations\nconst {\n user,\n tokens,\n isAuthenticated,\n login,\n register,\n confirmEmail,\n forgotPassword,\n resetPassword,\n logout,\n api,\n} = useAuth();\n\n// Auth operations\nawait login(username, password);\nawait register(username, email, password);\nawait confirmEmail(username, code);\nawait forgotPassword(username);\nawait resetPassword(username, code, newPassword);\nlogout();\n\n// Direct store access for state management\nconst setAuth = useAuthStore((state) => state.setAuth);\nconst clearAuth = useAuthStore((state) => state.clearAuth);\n\n• Installed at: src/modules/auth-core/\n• Provides: useAuth hook, useAuthStore\n• Auto token refresh before expiry\n• 401 interceptor with automatic retry\n• Uses customerClient from api module internally",
13
+ "files": [
14
+ {
15
+ "path": "auth-core/index.ts",
16
+ "type": "registry:index",
17
+ "target": "$modules$/auth-core/index.ts",
18
+ "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"
19
+ },
20
+ {
21
+ "path": "auth-core/auth-store.ts",
22
+ "type": "registry:store",
23
+ "target": "$modules$/auth-core/auth-store.ts",
24
+ "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"
25
+ },
26
+ {
27
+ "path": "auth-core/use-auth.ts",
28
+ "type": "registry:hook",
29
+ "target": "$modules$/auth-core/use-auth.ts",
30
+ "content": "import { useCallback, useEffect, useRef } from \"react\";\r\nimport {\r\n useAuthStore,\r\n type User,\r\n type AuthTokens,\r\n} from \"@/modules/auth-core/auth-store\";\r\nimport { customerClient } from \"@/modules/api/customer-client\";\r\n\r\n// Refresh token 1 minute before expiry\r\nconst REFRESH_BUFFER_MS = 60 * 1000;\r\n\r\nexport function useAuth() {\r\n const {\r\n user,\r\n tokens,\r\n isAuthenticated,\r\n setAuth,\r\n updateTokens,\r\n clearAuth,\r\n isTokenExpired,\r\n getTimeUntilExpiry,\r\n } = useAuthStore();\r\n\r\n const refreshTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\r\n const isRefreshingRef = useRef(false);\r\n\r\n // Refresh token using the refresh token\r\n const refreshAccessToken = useCallback(async (): Promise<boolean> => {\r\n const currentTokens = useAuthStore.getState().tokens;\r\n\r\n // Don't refresh if no refresh token exists\r\n if (!currentTokens?.refreshToken || isRefreshingRef.current) {\r\n console.log(\"⚠️ No refresh token available, skipping refresh\");\r\n return false;\r\n }\r\n\r\n isRefreshingRef.current = true;\r\n\r\n try {\r\n // Make a refresh request using the axios instance directly\r\n const response = await customerClient.axios.post<{\r\n accessToken: string;\r\n refreshToken?: string;\r\n expiresIn?: number;\r\n }>(\"/auth/refresh\", {\r\n refreshToken: currentTokens.refreshToken,\r\n });\r\n\r\n const { accessToken, refreshToken, expiresIn } = response.data;\r\n\r\n // Validate response has required data\r\n if (!accessToken) {\r\n console.error(\"❌ Refresh response missing accessToken\");\r\n return false;\r\n }\r\n\r\n const newTokens: AuthTokens = {\r\n accessToken,\r\n refreshToken: refreshToken || currentTokens.refreshToken,\r\n idToken: currentTokens.idToken, // Preserve existing idToken\r\n encryptionKey: currentTokens.encryptionKey, // Preserve existing encryptionKey\r\n expiresAt: expiresIn ? Date.now() + expiresIn * 1000 : undefined,\r\n };\r\n\r\n customerClient.setToken(accessToken);\r\n updateTokens(newTokens);\r\n\r\n console.log(\"✅ Token refreshed successfully\");\r\n return true;\r\n } catch (error) {\r\n console.error(\"❌ Token refresh failed:\", error);\r\n // DON'T clear auth on refresh failure - just return false\r\n // User can still use their existing token until it expires\r\n return false;\r\n } finally {\r\n isRefreshingRef.current = false;\r\n }\r\n }, [updateTokens]);\r\n\r\n // Schedule automatic token refresh\r\n const scheduleTokenRefresh = useCallback(() => {\r\n // Clear any existing timeout\r\n if (refreshTimeoutRef.current) {\r\n clearTimeout(refreshTimeoutRef.current);\r\n refreshTimeoutRef.current = null;\r\n }\r\n\r\n const timeUntilExpiry = getTimeUntilExpiry();\r\n\r\n // Only schedule if we have an expiry time and a refresh token\r\n if (timeUntilExpiry === null || !tokens?.refreshToken) {\r\n return;\r\n }\r\n\r\n // Calculate when to refresh (REFRESH_BUFFER_MS before expiry)\r\n const refreshIn = Math.max(timeUntilExpiry - REFRESH_BUFFER_MS, 0);\r\n\r\n // Don't schedule if expiry is too far in the future (> 24 hours)\r\n if (refreshIn > 24 * 60 * 60 * 1000) {\r\n return;\r\n }\r\n\r\n refreshTimeoutRef.current = setTimeout(async () => {\r\n const success = await refreshAccessToken();\r\n if (success) {\r\n // Reschedule for the new token\r\n scheduleTokenRefresh();\r\n }\r\n }, refreshIn);\r\n }, [getTimeUntilExpiry, tokens?.refreshToken, refreshAccessToken]);\r\n\r\n // Sync token with API client and set up refresh on mount and token changes\r\n useEffect(() => {\r\n if (tokens?.accessToken) {\r\n console.log(\"🔑 Setting token in API client\");\r\n customerClient.setToken(tokens.accessToken);\r\n\r\n // Only try to refresh if we have a refresh token AND token is expired\r\n if (isTokenExpired() && tokens.refreshToken) {\r\n console.log(\"⏰ Token expired, attempting refresh...\");\r\n refreshAccessToken().then((success) => {\r\n if (success) {\r\n scheduleTokenRefresh();\r\n } else {\r\n console.log(\"⚠️ Refresh failed, but keeping existing token\");\r\n }\r\n });\r\n } else if (tokens.refreshToken) {\r\n // Only schedule refresh if we have a refresh token\r\n scheduleTokenRefresh();\r\n }\r\n } else if (tokens && Object.keys(tokens).length === 0) {\r\n // tokens is empty object {} - this shouldn't happen, log it\r\n console.warn(\"⚠️ Tokens object is empty, this may indicate a bug\");\r\n } else {\r\n customerClient.setToken(null);\r\n }\r\n\r\n // Cleanup timeout on unmount\r\n return () => {\r\n if (refreshTimeoutRef.current) {\r\n clearTimeout(refreshTimeoutRef.current);\r\n }\r\n };\r\n }, [\r\n tokens?.accessToken,\r\n tokens?.refreshToken,\r\n isTokenExpired,\r\n refreshAccessToken,\r\n scheduleTokenRefresh,\r\n ]);\r\n\r\n // Set up axios interceptor for 401 responses (token expired during request)\r\n useEffect(() => {\r\n const interceptorId = customerClient.axios.interceptors.response.use(\r\n (response) => response,\r\n async (error) => {\r\n const originalRequest = error.config;\r\n\r\n // Skip refresh for auth endpoints to prevent infinite loops\r\n const isAuthEndpoint = originalRequest?.url?.includes(\"/auth/\");\r\n\r\n // If we get a 401 and haven't retried yet, try to refresh\r\n if (\r\n error.response?.status === 401 &&\r\n !originalRequest._retry &&\r\n tokens?.refreshToken &&\r\n !isAuthEndpoint\r\n ) {\r\n originalRequest._retry = true;\r\n\r\n const success = await refreshAccessToken();\r\n if (success) {\r\n // Retry the original request with new token\r\n const newTokens = useAuthStore.getState().tokens;\r\n if (newTokens?.accessToken) {\r\n originalRequest.headers.Authorization = `Bearer ${newTokens.accessToken}`;\r\n return customerClient.axios(originalRequest);\r\n }\r\n }\r\n }\r\n\r\n return Promise.reject(error);\r\n },\r\n );\r\n\r\n return () => {\r\n customerClient.axios.interceptors.response.eject(interceptorId);\r\n };\r\n }, [tokens?.refreshToken, refreshAccessToken]);\r\n\r\n const login = useCallback(async (username: string, password: string) => {\r\n const response = await customerClient.auth.login({ username, password });\r\n\r\n console.log(\"🔐 Login response:\", response);\r\n console.log(\"🔐 accessToken:\", response.accessToken);\r\n console.log(\"🔐 refreshToken:\", response.refreshToken);\r\n console.log(\"🔐 encryptionKey:\", response.encryptionKey);\r\n\r\n const newTokens: AuthTokens = {\r\n accessToken: response.accessToken,\r\n refreshToken: response.refreshToken,\r\n idToken: response.idToken,\r\n encryptionKey: response.encryptionKey,\r\n expiresAt: response.expiresIn\r\n ? Date.now() + response.expiresIn * 1000\r\n : undefined,\r\n };\r\n\r\n console.log(\"🔐 newTokens object:\", newTokens);\r\n\r\n const newUser: User = {\r\n username,\r\n email: (response as any).email || (response as any).user?.email,\r\n };\r\n\r\n customerClient.setToken(newTokens.accessToken);\r\n setAuth(newUser, newTokens);\r\n\r\n console.log(\r\n \"🔐 Auth set complete, checking store:\",\r\n useAuthStore.getState().tokens,\r\n );\r\n }, []);\r\n\r\n const register = useCallback(\r\n async (username: string, email: string, password: string) => {\r\n await customerClient.auth.register({ username, email, password });\r\n },\r\n [],\r\n );\r\n\r\n const confirmEmail = useCallback(async (username: string, code: string) => {\r\n await customerClient.auth.confirm({ username, code });\r\n }, []);\r\n\r\n const forgotPassword = useCallback(async (username: string) => {\r\n await customerClient.auth.forgotPassword({ username });\r\n }, []);\r\n\r\n const resetPassword = useCallback(\r\n async (username: string, code: string, newPassword: string) => {\r\n await customerClient.auth.resetPassword({ username, code, newPassword });\r\n },\r\n [],\r\n );\r\n\r\n const logout = useCallback(() => {\r\n // Clear any scheduled refresh\r\n if (refreshTimeoutRef.current) {\r\n clearTimeout(refreshTimeoutRef.current);\r\n refreshTimeoutRef.current = null;\r\n }\r\n\r\n customerClient.setToken(null);\r\n clearAuth();\r\n }, [clearAuth]);\r\n\r\n return {\r\n user,\r\n token: tokens?.accessToken ?? null,\r\n tokens,\r\n isAuthenticated,\r\n api: customerClient,\r\n login,\r\n register,\r\n confirmEmail,\r\n forgotPassword,\r\n resetPassword,\r\n logout,\r\n refreshAccessToken,\r\n };\r\n}\r\n"
31
+ }
32
+ ],
33
+ "exports": {
34
+ "types": [
35
+ "AuthTokens",
36
+ "User"
37
+ ],
38
+ "variables": [
39
+ "useAuth",
40
+ "useAuthStore"
41
+ ]
42
+ }
43
+ }
@@ -14,7 +14,7 @@
14
14
  "path": "auth/index.ts",
15
15
  "type": "registry:index",
16
16
  "target": "$modules$/auth/index.ts",
17
- "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\r\n// Components\r\nexport { AuthHeaderMenu } from \"./auth-header-menu\";\r\nexport { LoginPage } from \"./login-page\";\r\nexport { RegisterPage } from \"./register-page\";\r\nexport { ForgotPasswordPage } from \"./forgot-password-page\";\r\n"
17
+ "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\r\n// Components\r\nexport { AuthHeaderMenu } from \"./auth-header-menu\";\r\nexport { default as LoginPage } from \"./login-page\";\r\nexport { default as RegisterPage } from \"./register-page\";\r\nexport { default as ForgotPasswordPage } from \"./forgot-password-page\";\r\n"
18
18
  },
19
19
  {
20
20
  "path": "auth/auth-store.ts",
@@ -5,7 +5,8 @@
5
5
  "description": "Blog listing page with category filter tabs, search functionality, and responsive post grid. Uses PostCard component for display with grid/list view toggle. Features loading states, empty state handling, pagination, and featured post highlight at top. Includes sidebar with popular posts, categories, and newsletter signup.",
6
6
  "registryDependencies": [
7
7
  "blog-core",
8
- "post-card"
8
+ "post-card",
9
+ "animations"
9
10
  ],
10
11
  "usage": "import { BlogListPage } from '@/modules/blog-list-page';\n\n<Route path=\"/blog\" element={<BlogListPage />} />\n\n• Uses usePosts() from blog-core (Zustand)\n• Features: category tabs, search, grid/list view\n• Sidebar: popular posts, categories, newsletter",
11
12
  "route": {
@@ -23,7 +24,7 @@
23
24
  "path": "blog-list-page/blog-list-page.tsx",
24
25
  "type": "registry:page",
25
26
  "target": "$modules$/blog-list-page/blog-list-page.tsx",
26
- "content": "import { useState, useEffect } from \"react\";\nimport { useSearchParams } from \"react-router\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { useTranslation } from \"react-i18next\";\nimport { Layout } from \"@/components/Layout\";\nimport { Search, Filter } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { FadeIn } from \"@/modules/animations\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n SheetTrigger,\n} from \"@/components/ui/sheet\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { PostCard } from \"@/modules/post-card/post-card\";\nimport { usePosts, useBlogCategories } from \"@/modules/blog-core\";\n\nexport function BlogListPage() {\n const { t } = useTranslation(\"blog-list-page\");\n usePageTitle({ title: t(\"title\") });\n\n const [searchParams, setSearchParams] = useSearchParams();\n const [searchTerm, setSearchTerm] = useState(\n searchParams.get(\"search\") || \"\"\n );\n const [selectedCategories, setSelectedCategories] = useState<string[]>(\n searchParams.get(\"categories\")?.split(\",\").filter(Boolean) || []\n );\n const [selectedTags, setSelectedTags] = useState<string[]>(\n searchParams.get(\"tags\")?.split(\",\").filter(Boolean) || []\n );\n const [sortBy, setSortBy] = useState(searchParams.get(\"sort\") || \"newest\");\n const [viewMode, _setViewMode] = useState<\"grid\" | \"list\">(\"grid\");\n\n const { posts, loading, error } = usePosts();\n const { categories } = useBlogCategories();\n\n const filteredPosts = posts.filter((post) => {\n if (searchTerm) {\n const searchLower = searchTerm.toLowerCase();\n if (\n !post.title.toLowerCase().includes(searchLower) &&\n !post.excerpt.toLowerCase().includes(searchLower) &&\n !post.content.toLowerCase().includes(searchLower)\n ) {\n return false;\n }\n }\n\n if (selectedCategories.length > 0) {\n const hasMatchingCategory = selectedCategories.some(\n (categorySlug) =>\n post.category === categorySlug ||\n post.categories?.some((cat) => cat.slug === categorySlug)\n );\n if (!hasMatchingCategory) return false;\n }\n\n if (selectedTags.length > 0) {\n const hasMatchingTag = selectedTags.some((tag) =>\n post.tags.includes(tag)\n );\n if (!hasMatchingTag) return false;\n }\n\n return true;\n });\n\n const sortedPosts = [...filteredPosts].sort((a, b) => {\n switch (sortBy) {\n case \"oldest\":\n return (\n new Date(a.published_at).getTime() -\n new Date(b.published_at).getTime()\n );\n case \"popular\":\n return (b.view_count || 0) - (a.view_count || 0);\n case \"reading-time\":\n return (a.read_time || 0) - (b.read_time || 0);\n case \"newest\":\n default:\n return (\n new Date(b.published_at).getTime() -\n new Date(a.published_at).getTime()\n );\n }\n });\n\n useEffect(() => {\n const params = new URLSearchParams();\n if (searchTerm) params.set(\"search\", searchTerm);\n if (selectedCategories.length)\n params.set(\"categories\", selectedCategories.join(\",\"));\n if (selectedTags.length) params.set(\"tags\", selectedTags.join(\",\"));\n if (sortBy !== \"newest\") params.set(\"sort\", sortBy);\n\n setSearchParams(params);\n }, [searchTerm, selectedCategories, selectedTags, sortBy, setSearchParams]);\n\n const handleCategoryChange = (categorySlug: string, checked: boolean) => {\n if (checked) {\n setSelectedCategories([...selectedCategories, categorySlug]);\n } else {\n setSelectedCategories(\n selectedCategories.filter((c) => c !== categorySlug)\n );\n }\n };\n\n const handleTagChange = (tag: string, checked: boolean) => {\n if (checked) {\n setSelectedTags([...selectedTags, tag]);\n } else {\n setSelectedTags(selectedTags.filter((t) => t !== tag));\n }\n };\n\n const allTags = Array.from(new Set(posts.flatMap((post) => post.tags)));\n\n const clearFilters = () => {\n setSearchTerm(\"\");\n setSelectedCategories([]);\n setSelectedTags([]);\n setSortBy(\"newest\");\n };\n\n const FilterSection = () => (\n <div className=\"space-y-6\">\n <div>\n <h3 className=\"font-semibold mb-3 flex items-center gap-2\">\n <Search className=\"h-4 w-4\" />\n {t(\"search\")}\n </h3>\n <Input\n placeholder={t(\"searchPlaceholder\")}\n value={searchTerm}\n onChange={(e) => setSearchTerm(e.target.value)}\n />\n </div>\n\n <div>\n <h3 className=\"font-semibold mb-3\">{t(\"categories\")}</h3>\n <div className=\"space-y-2\">\n {categories.map((category) => (\n <div key={category.slug} className=\"flex items-center space-x-2\">\n <Checkbox\n id={`category-${category.slug}`}\n checked={selectedCategories.includes(category.slug)}\n onCheckedChange={(checked) =>\n handleCategoryChange(category.slug, checked as boolean)\n }\n />\n <label\n htmlFor={`category-${category.slug}`}\n className=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer\"\n >\n {category.name}\n </label>\n </div>\n ))}\n </div>\n </div>\n\n {allTags.length > 0 && (\n <div>\n <h3 className=\"font-semibold mb-3\">{t(\"tags\")}</h3>\n <div className=\"space-y-2 max-h-48 overflow-y-auto\">\n {allTags.slice(0, 20).map((tag) => (\n <div key={tag} className=\"flex items-center space-x-2\">\n <Checkbox\n id={`tag-${tag}`}\n checked={selectedTags.includes(tag)}\n onCheckedChange={(checked) =>\n handleTagChange(tag, checked as boolean)\n }\n />\n <label\n htmlFor={`tag-${tag}`}\n className=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer\"\n >\n {tag}\n </label>\n </div>\n ))}\n </div>\n </div>\n )}\n\n {(searchTerm ||\n selectedCategories.length > 0 ||\n selectedTags.length > 0) && (\n <Button variant=\"outline\" onClick={clearFilters} className=\"w-full\">\n {t(\"clearFilters\")}\n </Button>\n )}\n </div>\n );\n\n if (loading) {\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\n <div className=\"animate-pulse space-y-4\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div key={i} className=\"h-48 bg-muted rounded-lg\"></div>\n ))}\n </div>\n </div>\n </Layout>\n );\n }\n\n if (error) {\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8 text-center\">\n <p className=\"text-destructive\">{t(\"error\")}</p>\n </div>\n </Layout>\n );\n }\n\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\n <FadeIn className=\"flex flex-col lg:flex-row justify-between items-start lg:items-center gap-4 mb-8\">\n <div>\n <h1 className=\"text-3xl font-bold mb-2\">{t(\"title\")}</h1>\n <p className=\"text-muted-foreground\">{t(\"subtitle\")}</p>\n </div>\n\n <div className=\"flex items-center gap-4\">\n <Select value={sortBy} onValueChange={setSortBy}>\n <SelectTrigger className=\"w-[180px]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"newest\">{t(\"sortNewest\")}</SelectItem>\n <SelectItem value=\"oldest\">{t(\"sortOldest\")}</SelectItem>\n <SelectItem value=\"popular\">{t(\"sortPopular\")}</SelectItem>\n <SelectItem value=\"reading-time\">\n {t(\"sortReadingTime\")}\n </SelectItem>\n </SelectContent>\n </Select>\n\n <Sheet>\n <SheetTrigger asChild>\n <Button variant=\"outline\" size=\"sm\" className=\"lg:hidden\">\n <Filter className=\"h-4 w-4 mr-2\" />\n {t(\"filters\")}\n </Button>\n </SheetTrigger>\n <SheetContent>\n <SheetHeader>\n <SheetTitle>{t(\"filters\")}</SheetTitle>\n <SheetDescription>{t(\"filterDescription\")}</SheetDescription>\n </SheetHeader>\n <div className=\"mt-6\">\n <FilterSection />\n </div>\n </SheetContent>\n </Sheet>\n </div>\n </FadeIn>\n\n <div className=\"flex flex-col lg:flex-row gap-8\">\n <div className=\"hidden lg:block w-64 flex-shrink-0\">\n <div className=\"sticky top-4\">\n <FilterSection />\n </div>\n </div>\n\n <div className=\"flex-1\">\n <div className=\"flex items-center justify-between mb-6\">\n <p className=\"text-sm text-muted-foreground\">\n {t(\"showing\")} {sortedPosts.length} {t(\"of\")} {posts.length}{\" \"}\n {t(\"posts\")}\n {searchTerm && (\n <span className=\"ml-1\">\n {t(\"for\")} \"<strong>{searchTerm}</strong>\"\n </span>\n )}\n </p>\n </div>\n\n {sortedPosts.length > 0 ? (\n <div\n className={`grid gap-6 ${\n viewMode === \"grid\"\n ? \"grid-cols-1 md:grid-cols-2 xl:grid-cols-3\"\n : \"grid-cols-1\"\n }`}\n >\n {sortedPosts.map((post) => (\n <PostCard key={post.id} post={post} layout={viewMode} />\n ))}\n </div>\n ) : (\n <div className=\"text-center py-12\">\n <p className=\"text-muted-foreground mb-4\">\n {t(\"noPostsFound\")}\n </p>\n <Button onClick={clearFilters} variant=\"outline\">\n {t(\"clearFilters\")}\n </Button>\n </div>\n )}\n </div>\n </div>\n </div>\n </Layout>\n );\n}\n"
27
+ "content": "import { useState, useEffect } from \"react\";\nimport { useSearchParams } from \"react-router\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { useTranslation } from \"react-i18next\";\nimport { Layout } from \"@/components/Layout\";\nimport { Search, Filter } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { FadeIn } from \"@/modules/animations\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n SheetTrigger,\n} from \"@/components/ui/sheet\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { PostCard } from \"@/modules/post-card/post-card\";\nimport { usePosts, useBlogCategories } from \"@/modules/blog-core\";\n\nexport function BlogListPage() {\n const { t } = useTranslation(\"blog-list-page\");\n usePageTitle({ title: t(\"title\") });\n\n const [searchParams, setSearchParams] = useSearchParams();\n const [searchTerm, setSearchTerm] = useState(\n searchParams.get(\"search\") || \"\"\n );\n const [selectedCategories, setSelectedCategories] = useState<string[]>(\n searchParams.get(\"categories\")?.split(\",\").filter(Boolean) || []\n );\n const [selectedTags, setSelectedTags] = useState<string[]>(\n searchParams.get(\"tags\")?.split(\",\").filter(Boolean) || []\n );\n const [sortBy, setSortBy] = useState(searchParams.get(\"sort\") || \"newest\");\n const [viewMode, _setViewMode] = useState<\"grid\" | \"list\">(\"grid\");\n\n const { posts, loading, error } = usePosts();\n const { categories } = useBlogCategories();\n\n const filteredPosts = posts.filter((post) => {\n if (searchTerm) {\n const searchLower = searchTerm.toLowerCase();\n if (\n !post.title.toLowerCase().includes(searchLower) &&\n !post.excerpt.toLowerCase().includes(searchLower) &&\n !post.content.toLowerCase().includes(searchLower)\n ) {\n return false;\n }\n }\n\n if (selectedCategories.length > 0) {\n const hasMatchingCategory = selectedCategories.some(\n (categorySlug) =>\n post.category === categorySlug ||\n post.categories?.some((cat) => cat.slug === categorySlug)\n );\n if (!hasMatchingCategory) return false;\n }\n\n if (selectedTags.length > 0) {\n const hasMatchingTag = selectedTags.some((tag) =>\n post.tags.includes(tag)\n );\n if (!hasMatchingTag) return false;\n }\n\n return true;\n });\n\n const sortedPosts = [...filteredPosts].sort((a, b) => {\n switch (sortBy) {\n case \"oldest\":\n return (\n new Date(a.published_at).getTime() -\n new Date(b.published_at).getTime()\n );\n case \"popular\":\n return (b.view_count || 0) - (a.view_count || 0);\n case \"reading-time\":\n return (a.read_time || 0) - (b.read_time || 0);\n case \"newest\":\n default:\n return (\n new Date(b.published_at).getTime() -\n new Date(a.published_at).getTime()\n );\n }\n });\n\n useEffect(() => {\n const params = new URLSearchParams();\n if (searchTerm) params.set(\"search\", searchTerm);\n if (selectedCategories.length)\n params.set(\"categories\", selectedCategories.join(\",\"));\n if (selectedTags.length) params.set(\"tags\", selectedTags.join(\",\"));\n if (sortBy !== \"newest\") params.set(\"sort\", sortBy);\n\n setSearchParams(params);\n }, [searchTerm, selectedCategories, selectedTags, sortBy, setSearchParams]);\n\n const handleCategoryChange = (categorySlug: string, checked: boolean) => {\n if (checked) {\n setSelectedCategories([...selectedCategories, categorySlug]);\n } else {\n setSelectedCategories(\n selectedCategories.filter((c) => c !== categorySlug)\n );\n }\n };\n\n const handleTagChange = (tag: string, checked: boolean) => {\n if (checked) {\n setSelectedTags([...selectedTags, tag]);\n } else {\n setSelectedTags(selectedTags.filter((t) => t !== tag));\n }\n };\n\n const allTags = Array.from(new Set(posts.flatMap((post) => post.tags)));\n\n const clearFilters = () => {\n setSearchTerm(\"\");\n setSelectedCategories([]);\n setSelectedTags([]);\n setSortBy(\"newest\");\n };\n\n const FilterSection = () => (\n <div className=\"space-y-6\">\n <div>\n <h3 className=\"font-semibold mb-3 flex items-center gap-2\">\n <Search className=\"h-4 w-4\" />\n {t(\"search\")}\n </h3>\n <Input\n placeholder={t(\"searchPlaceholder\")}\n value={searchTerm}\n onChange={(e) => setSearchTerm(e.target.value)}\n />\n </div>\n\n <div>\n <h3 className=\"font-semibold mb-3\">{t(\"categories\")}</h3>\n <div className=\"space-y-2\">\n {categories.map((category) => (\n <div key={category.slug} className=\"flex items-center space-x-2\">\n <Checkbox\n id={`category-${category.slug}`}\n checked={selectedCategories.includes(category.slug)}\n onCheckedChange={(checked) =>\n handleCategoryChange(category.slug, checked as boolean)\n }\n />\n <label\n htmlFor={`category-${category.slug}`}\n className=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer\"\n >\n {category.name}\n </label>\n </div>\n ))}\n </div>\n </div>\n\n {allTags.length > 0 && (\n <div>\n <h3 className=\"font-semibold mb-3\">{t(\"tags\")}</h3>\n <div className=\"space-y-2 max-h-48 overflow-y-auto\">\n {allTags.slice(0, 20).map((tag) => (\n <div key={tag} className=\"flex items-center space-x-2\">\n <Checkbox\n id={`tag-${tag}`}\n checked={selectedTags.includes(tag)}\n onCheckedChange={(checked) =>\n handleTagChange(tag, checked as boolean)\n }\n />\n <label\n htmlFor={`tag-${tag}`}\n className=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer\"\n >\n {tag}\n </label>\n </div>\n ))}\n </div>\n </div>\n )}\n\n {(searchTerm ||\n selectedCategories.length > 0 ||\n selectedTags.length > 0) && (\n <Button variant=\"outline\" onClick={clearFilters} className=\"w-full\">\n {t(\"clearFilters\")}\n </Button>\n )}\n </div>\n );\n\n if (loading) {\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\n <div className=\"animate-pulse space-y-4\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div key={i} className=\"h-48 bg-muted rounded-lg\"></div>\n ))}\n </div>\n </div>\n </Layout>\n );\n }\n\n if (error) {\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8 text-center\">\n <p className=\"text-destructive\">{t(\"error\")}</p>\n </div>\n </Layout>\n );\n }\n\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\n <FadeIn className=\"flex flex-col lg:flex-row justify-between items-start lg:items-center gap-4 mb-8\">\n <div>\n <h1 className=\"text-3xl font-bold mb-2\">{t(\"title\")}</h1>\n <p className=\"text-muted-foreground\">{t(\"subtitle\")}</p>\n </div>\n\n <div className=\"flex items-center gap-4\">\n <Select value={sortBy} onValueChange={setSortBy}>\n <SelectTrigger className=\"w-[180px]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"newest\">{t(\"sortNewest\")}</SelectItem>\n <SelectItem value=\"oldest\">{t(\"sortOldest\")}</SelectItem>\n <SelectItem value=\"popular\">{t(\"sortPopular\")}</SelectItem>\n <SelectItem value=\"reading-time\">\n {t(\"sortReadingTime\")}\n </SelectItem>\n </SelectContent>\n </Select>\n\n <Sheet>\n <SheetTrigger asChild>\n <Button variant=\"outline\" size=\"sm\" className=\"lg:hidden\">\n <Filter className=\"h-4 w-4 mr-2\" />\n {t(\"filters\")}\n </Button>\n </SheetTrigger>\n <SheetContent>\n <SheetHeader>\n <SheetTitle>{t(\"filters\")}</SheetTitle>\n <SheetDescription>{t(\"filterDescription\")}</SheetDescription>\n </SheetHeader>\n <div className=\"mt-6\">\n <FilterSection />\n </div>\n </SheetContent>\n </Sheet>\n </div>\n </FadeIn>\n\n <div className=\"flex flex-col lg:flex-row gap-8\">\n <div className=\"hidden lg:block w-64 flex-shrink-0\">\n <div className=\"sticky top-4\">\n <FilterSection />\n </div>\n </div>\n\n <div className=\"flex-1\">\n <div className=\"flex items-center justify-between mb-6\">\n <p className=\"text-sm text-muted-foreground\">\n {t(\"showing\")} {sortedPosts.length} {t(\"of\")} {posts.length}{\" \"}\n {t(\"posts\")}\n {searchTerm && (\n <span className=\"ml-1\">\n {t(\"for\")} \"<strong>{searchTerm}</strong>\"\n </span>\n )}\n </p>\n </div>\n\n {sortedPosts.length > 0 ? (\n <div\n className={`grid gap-6 ${\n viewMode === \"grid\"\n ? \"grid-cols-1 md:grid-cols-2 xl:grid-cols-3\"\n : \"grid-cols-1\"\n }`}\n >\n {sortedPosts.map((post) => (\n <PostCard key={post.id} post={post} layout={viewMode} />\n ))}\n </div>\n ) : (\n <div className=\"text-center py-12\">\n <p className=\"text-muted-foreground mb-4\">\n {t(\"noPostsFound\")}\n </p>\n <Button onClick={clearFilters} variant=\"outline\">\n {t(\"clearFilters\")}\n </Button>\n </div>\n )}\n </div>\n </div>\n </div>\n </Layout>\n );\n}\n\nexport default BlogListPage;\n"
27
28
  },
28
29
  {
29
30
  "path": "blog-list-page/lang/en.json",
@@ -25,7 +25,7 @@
25
25
  "path": "blog-section/lang/en.json",
26
26
  "type": "registry:lang",
27
27
  "target": "$modules$/blog-section/lang/en.json",
28
- "content": "{\r\n \"tagline\": \"Latest Updates\",\r\n \"title\": \"From Our Blog\",\r\n \"subtitle\": \"AI will customize this blog section subtitle based on your content strategy. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"viewAll\": \"View all articles\",\r\n \"readMore\": \"Read more\",\r\n \"post1Title\": \"AI will replace this blog post title\",\r\n \"post1Summary\": \"This is placeholder blog post summary text. AI will customize this based on your actual blog content. Lorem ipsum dolor sit amet.\",\r\n \"post1Category\": \"Tutorial\",\r\n \"post1Author\": \"Sarah Chen\",\r\n \"post1Date\": \"Jan 15, 2024\",\r\n \"post2Title\": \"Replace this with your second blog post title\",\r\n \"post2Summary\": \"Placeholder summary text. AI will generate appropriate blog post summaries based on your content. Sed do eiusmod tempor.\",\r\n \"post2Category\": \"Best Practices\",\r\n \"post2Author\": \"Michael Park\",\r\n \"post2Date\": \"Jan 10, 2024\",\r\n \"post3Title\": \"This blog post title will be customized by AI\",\r\n \"post3Summary\": \"AI will replace this summary with relevant content based on your blog posts. Ut enim ad minim veniam, quis nostrud exercitation.\",\r\n \"post3Category\": \"Design\",\r\n \"post3Author\": \"Emily Davis\",\r\n \"post3Date\": \"Jan 5, 2024\"\r\n}\r\n"
28
+ "content": "{\r\n \"tagline\": \"Latest Updates\",\r\n \"title\": \"From Our Blog\",\r\n \"subtitle\": \"Ask Promake to customize this blog section subtitle based on your content strategy. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"viewAll\": \"View all articles\",\r\n \"readMore\": \"Read more\",\r\n \"post1Title\": \"Ask Promake to replace this blog post title\",\r\n \"post1Summary\": \"This is placeholder blog post summary text. Ask Promake to customize this based on your actual blog content. Lorem ipsum dolor sit amet.\",\r\n \"post1Category\": \"Tutorial\",\r\n \"post1Author\": \"Sarah Chen\",\r\n \"post1Date\": \"Jan 15, 2024\",\r\n \"post2Title\": \"Replace this with your second blog post title\",\r\n \"post2Summary\": \"Placeholder summary text. Ask Promake to generate appropriate blog post summaries based on your content. Sed do eiusmod tempor.\",\r\n \"post2Category\": \"Best Practices\",\r\n \"post2Author\": \"Michael Park\",\r\n \"post2Date\": \"Jan 10, 2024\",\r\n \"post3Title\": \"This blog post title will be customized by Promake\",\r\n \"post3Summary\": \"Ask Promake to replace this summary with relevant content based on your blog posts. Ut enim ad minim veniam, quis nostrud exercitation.\",\r\n \"post3Category\": \"Design\",\r\n \"post3Author\": \"Emily Davis\",\r\n \"post3Date\": \"Jan 5, 2024\"\r\n}\r\n"
29
29
  },
30
30
  {
31
31
  "path": "blog-section/lang/tr.json",
@@ -23,19 +23,19 @@
23
23
  "path": "cart-page/cart-page.tsx",
24
24
  "type": "registry:page",
25
25
  "target": "$modules$/cart-page/cart-page.tsx",
26
- "content": "import { Link } from \"react-router\";\nimport { Trash2, Plus, Minus, ArrowLeft, ShoppingBag } from \"lucide-react\";\nimport { Layout } from \"@/components/Layout\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Input } from \"@/components/ui/input\";\nimport { useTranslation } from \"react-i18next\";\nimport { useCart, formatPrice } from \"@/modules/ecommerce-core\";\nimport constants from \"@/constants/constants.json\";\nimport { FadeIn } from \"@/modules/animations\";\n\nexport function CartPage() {\n const { t } = useTranslation(\"cart-page\");\n const { state, removeItem, updateQuantity } = useCart();\n const { items, total } = state;\n\n const currency = constants.site.currency || \"USD\";\n const shipping = 0;\n const tax = 0;\n const freeShippingThreshold = 100;\n\n const getProductPrice = (product: { price: number; sale_price?: number; on_sale?: boolean }) => {\n return product.on_sale && product.sale_price ? product.sale_price : product.price;\n };\n\n const handleQuantityChange = (productId: number | string, newQuantity: number) => {\n if (newQuantity <= 0) {\n removeItem(productId);\n } else {\n updateQuantity(productId, newQuantity);\n }\n };\n\n const handleQuantityInputChange = (productId: number | string, value: string) => {\n const quantity = parseInt(value) || 1;\n handleQuantityChange(productId, quantity);\n };\n\n const itemCount = items.reduce((sum, item) => sum + item.quantity, 0);\n const finalTotal = total + shipping + tax;\n\n if (items.length === 0) {\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\n <div className=\"max-w-2xl mx-auto text-center py-16\">\n <div className=\"mb-8\">\n <ShoppingBag className=\"h-24 w-24 mx-auto text-muted-foreground mb-4\" />\n <h1 className=\"text-3xl font-bold mb-4\">\n {t(\"empty\", \"Your Cart is Empty\")}\n </h1>\n <p className=\"text-muted-foreground mb-8\">\n {t(\"emptyDescription\", \"Looks like you haven't added any items to your cart yet.\")}\n </p>\n <Button asChild size=\"lg\">\n <Link to=\"/products\">\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\n {t(\"continueShopping\", \"Continue Shopping\")}\n </Link>\n </Button>\n </div>\n </div>\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 items-center gap-4 mb-8\">\n <Button variant=\"ghost\" size=\"icon\" asChild>\n <Link to=\"/products\">\n <ArrowLeft className=\"h-4 w-4\" />\n </Link>\n </Button>\n <div>\n <h1 className=\"text-3xl font-bold\">{t(\"title\", \"Shopping Cart\")}</h1>\n <p className=\"text-muted-foreground\">\n {itemCount} {t(\"itemsInCart\", \"items in your cart\")}\n </p>\n </div>\n </FadeIn>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-8\">\n <div className=\"lg:col-span-2 space-y-4\">\n {items.map((item) => (\n <Card key={item.id}>\n <CardContent className=\"p-6\">\n <div className=\"flex gap-4\">\n <div className=\"w-24 h-24 flex-shrink-0\">\n <img\n src={item.product.images[0] || \"/images/placeholder.png\"}\n alt={item.product.name}\n className=\"w-full h-full object-cover rounded-lg\"\n />\n </div>\n\n <div className=\"flex-1 space-y-2\">\n <div className=\"flex items-start justify-between\">\n <div>\n <h3 className=\"font-semibold\">{item.product.name}</h3>\n <p className=\"text-sm text-muted-foreground\">\n {item.product.category_name ||\n item.product.categories?.[0]?.name ||\n item.product.category}\n </p>\n </div>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => removeItem(item.product.id)}\n className=\"text-destructive hover:text-destructive\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n </div>\n\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <Button\n variant=\"outline\"\n size=\"icon\"\n className=\"h-8 w-8\"\n onClick={() => handleQuantityChange(item.product.id, item.quantity - 1)}\n >\n <Minus className=\"h-3 w-3\" />\n </Button>\n <Input\n type=\"number\"\n value={item.quantity}\n onChange={(e) => handleQuantityInputChange(item.product.id, e.target.value)}\n className=\"w-16 text-center\"\n min=\"1\"\n />\n <Button\n variant=\"outline\"\n size=\"icon\"\n className=\"h-8 w-8\"\n onClick={() => handleQuantityChange(item.product.id, item.quantity + 1)}\n >\n <Plus className=\"h-3 w-3\" />\n </Button>\n </div>\n\n <div className=\"text-right\">\n <p className=\"font-semibold\">\n {formatPrice(getProductPrice(item.product) * item.quantity, currency)}\n </p>\n {item.quantity > 1 && (\n <p className=\"text-sm text-muted-foreground\">\n {formatPrice(getProductPrice(item.product), currency)} {t(\"each\", \"each\")}\n </p>\n )}\n </div>\n </div>\n </div>\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>{t(\"orderSummary\", \"Order Summary\")}</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"flex justify-between\">\n <span>\n {t(\"subtotal\", \"Subtotal\")} ({itemCount} {t(\"items\", \"items\")})\n </span>\n <span>{formatPrice(total, currency)}</span>\n </div>\n\n <div className=\"flex justify-between\">\n <span>{t(\"shipping\", \"Shipping\")}</span>\n <span>\n {shipping === 0 ? t(\"free\", \"Free\") : formatPrice(shipping, currency)}\n </span>\n </div>\n\n <div className=\"flex justify-between\">\n <span>{t(\"tax\", \"Tax\")}</span>\n <span>{formatPrice(tax, currency)}</span>\n </div>\n\n <Separator />\n\n <div className=\"flex justify-between text-lg font-semibold\">\n <span>{t(\"total\", \"Total\")}</span>\n <span>{formatPrice(finalTotal, currency)}</span>\n </div>\n\n {shipping > 0 && freeShippingThreshold && freeShippingThreshold > total && (\n <div className=\"text-sm text-muted-foreground bg-muted/50 p-3 rounded-lg\">\n {t(\"freeShippingMessage\", \"Add {{amount}} more for free shipping!\").replace(\n \"{{amount}}\",\n formatPrice(freeShippingThreshold - total, currency)\n )}\n </div>\n )}\n\n <Button asChild className=\"w-full\" size=\"lg\">\n <Link to=\"/checkout\">{t(\"proceedToCheckout\", \"Proceed to Checkout\")}</Link>\n </Button>\n\n <Button variant=\"outline\" asChild className=\"w-full\">\n <Link to=\"/products\">{t(\"continueShopping\", \"Continue Shopping\")}</Link>\n </Button>\n </CardContent>\n </Card>\n\n <Card>\n <CardContent className=\"p-4\">\n <div className=\"text-center space-y-2\">\n <div className=\"text-sm text-muted-foreground\">\n {t(\"secureCheckout\", \"Secure Checkout\")}\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {t(\"secureCheckoutDescription\", \"Your payment information is encrypted and secure\")}\n </p>\n </div>\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n </Layout>\n );\n}\n"
26
+ "content": "import { Link } from \"react-router\";\nimport { Trash2, Plus, Minus, ArrowLeft, ShoppingBag } from \"lucide-react\";\nimport { Layout } from \"@/components/Layout\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Input } from \"@/components/ui/input\";\nimport { useTranslation } from \"react-i18next\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { useCart, formatPrice } from \"@/modules/ecommerce-core\";\nimport constants from \"@/constants/constants.json\";\nimport { FadeIn } from \"@/modules/animations\";\n\nexport function CartPage() {\n const { t } = useTranslation(\"cart-page\");\n usePageTitle({ title: t(\"pageTitle\", \"Shopping Cart\") });\n const { state, removeItem, updateQuantity } = useCart();\n const { items, total } = state;\n\n const currency = constants.site.currency || \"USD\";\n const shipping = 0;\n const tax = 0;\n const freeShippingThreshold = 100;\n\n const getProductPrice = (product: { price: number; sale_price?: number; on_sale?: boolean }) => {\n return product.on_sale && product.sale_price ? product.sale_price : product.price;\n };\n\n const handleQuantityChange = (productId: number | string, newQuantity: number) => {\n if (newQuantity <= 0) {\n removeItem(productId);\n } else {\n updateQuantity(productId, newQuantity);\n }\n };\n\n const handleQuantityInputChange = (productId: number | string, value: string) => {\n const quantity = parseInt(value) || 1;\n handleQuantityChange(productId, quantity);\n };\n\n const itemCount = items.reduce((sum, item) => sum + item.quantity, 0);\n const finalTotal = total + shipping + tax;\n\n if (items.length === 0) {\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\n <div className=\"max-w-2xl mx-auto text-center py-16\">\n <div className=\"mb-8\">\n <ShoppingBag className=\"h-24 w-24 mx-auto text-muted-foreground mb-4\" />\n <h1 className=\"text-3xl font-bold mb-4\">\n {t(\"empty\", \"Your Cart is Empty\")}\n </h1>\n <p className=\"text-muted-foreground mb-8\">\n {t(\"emptyDescription\", \"Looks like you haven't added any items to your cart yet.\")}\n </p>\n <Button asChild size=\"lg\">\n <Link to=\"/products\">\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\n {t(\"continueShopping\", \"Continue Shopping\")}\n </Link>\n </Button>\n </div>\n </div>\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 items-center gap-4 mb-8\">\n <Button variant=\"ghost\" size=\"icon\" asChild>\n <Link to=\"/products\">\n <ArrowLeft className=\"h-4 w-4\" />\n </Link>\n </Button>\n <div>\n <h1 className=\"text-3xl font-bold\">{t(\"title\", \"Shopping Cart\")}</h1>\n <p className=\"text-muted-foreground\">\n {itemCount} {t(\"itemsInCart\", \"items in your cart\")}\n </p>\n </div>\n </FadeIn>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-8\">\n <div className=\"lg:col-span-2 space-y-4\">\n {items.map((item) => (\n <Card key={item.id}>\n <CardContent className=\"p-6\">\n <div className=\"flex gap-4\">\n <div className=\"w-24 h-24 flex-shrink-0\">\n <img\n src={item.product.images[0] || \"/images/placeholder.png\"}\n alt={item.product.name}\n className=\"w-full h-full object-cover rounded-lg\"\n />\n </div>\n\n <div className=\"flex-1 space-y-2\">\n <div className=\"flex items-start justify-between\">\n <div>\n <h3 className=\"font-semibold\">{item.product.name}</h3>\n <p className=\"text-sm text-muted-foreground\">\n {item.product.category_name ||\n item.product.categories?.[0]?.name ||\n item.product.category}\n </p>\n </div>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => removeItem(item.product.id)}\n className=\"text-destructive hover:text-destructive\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n </div>\n\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <Button\n variant=\"outline\"\n size=\"icon\"\n className=\"h-8 w-8\"\n onClick={() => handleQuantityChange(item.product.id, item.quantity - 1)}\n >\n <Minus className=\"h-3 w-3\" />\n </Button>\n <Input\n type=\"number\"\n value={item.quantity}\n onChange={(e) => handleQuantityInputChange(item.product.id, e.target.value)}\n className=\"w-16 text-center\"\n min=\"1\"\n />\n <Button\n variant=\"outline\"\n size=\"icon\"\n className=\"h-8 w-8\"\n onClick={() => handleQuantityChange(item.product.id, item.quantity + 1)}\n >\n <Plus className=\"h-3 w-3\" />\n </Button>\n </div>\n\n <div className=\"text-right\">\n <p className=\"font-semibold\">\n {formatPrice(getProductPrice(item.product) * item.quantity, currency)}\n </p>\n {item.quantity > 1 && (\n <p className=\"text-sm text-muted-foreground\">\n {formatPrice(getProductPrice(item.product), currency)} {t(\"each\", \"each\")}\n </p>\n )}\n </div>\n </div>\n </div>\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>{t(\"orderSummary\", \"Order Summary\")}</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"flex justify-between\">\n <span>\n {t(\"subtotal\", \"Subtotal\")} ({itemCount} {t(\"items\", \"items\")})\n </span>\n <span>{formatPrice(total, currency)}</span>\n </div>\n\n <div className=\"flex justify-between\">\n <span>{t(\"shipping\", \"Shipping\")}</span>\n <span>\n {shipping === 0 ? t(\"free\", \"Free\") : formatPrice(shipping, currency)}\n </span>\n </div>\n\n <div className=\"flex justify-between\">\n <span>{t(\"tax\", \"Tax\")}</span>\n <span>{formatPrice(tax, currency)}</span>\n </div>\n\n <Separator />\n\n <div className=\"flex justify-between text-lg font-semibold\">\n <span>{t(\"total\", \"Total\")}</span>\n <span>{formatPrice(finalTotal, currency)}</span>\n </div>\n\n {shipping > 0 && freeShippingThreshold && freeShippingThreshold > total && (\n <div className=\"text-sm text-muted-foreground bg-muted/50 p-3 rounded-lg\">\n {t(\"freeShippingMessage\", \"Add {{amount}} more for free shipping!\").replace(\n \"{{amount}}\",\n formatPrice(freeShippingThreshold - total, currency)\n )}\n </div>\n )}\n\n <Button asChild className=\"w-full\" size=\"lg\">\n <Link to=\"/checkout\">{t(\"proceedToCheckout\", \"Proceed to Checkout\")}</Link>\n </Button>\n\n <Button variant=\"outline\" asChild className=\"w-full\">\n <Link to=\"/products\">{t(\"continueShopping\", \"Continue Shopping\")}</Link>\n </Button>\n </CardContent>\n </Card>\n\n <Card>\n <CardContent className=\"p-4\">\n <div className=\"text-center space-y-2\">\n <div className=\"text-sm text-muted-foreground\">\n {t(\"secureCheckout\", \"Secure Checkout\")}\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {t(\"secureCheckoutDescription\", \"Your payment information is encrypted and secure\")}\n </p>\n </div>\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n </Layout>\n );\n}\n\nexport default CartPage;\n"
27
27
  },
28
28
  {
29
29
  "path": "cart-page/lang/en.json",
30
30
  "type": "registry:lang",
31
31
  "target": "$modules$/cart-page/lang/en.json",
32
- "content": "{\r\n \"title\": \"Shopping Cart\",\r\n \"empty\": \"Your Cart is Empty\",\r\n \"emptyDescription\": \"Looks like you haven't added any items to your cart yet.\",\r\n \"addToCart\": \"Add to Cart\",\r\n \"removeItem\": \"Remove Item\",\r\n \"quantity\": \"Quantity\",\r\n \"subtotal\": \"Subtotal\",\r\n \"shipping\": \"Shipping\",\r\n \"tax\": \"Tax\",\r\n \"total\": \"Total\",\r\n \"proceedToCheckout\": \"Proceed to Checkout\",\r\n \"secureCheckout\": \"Secure Checkout\",\r\n \"orderSummary\": \"Order Summary\",\r\n \"itemsInCart\": \"items in your cart\",\r\n \"items\": \"items\",\r\n \"variant\": \"Variant\",\r\n \"each\": \"each\",\r\n \"freeShippingMessage\": \"Add {{amount}} more for free shipping!\",\r\n \"continueShopping\": \"Continue Shopping\",\r\n \"secureCheckoutDescription\": \"Your payment information is encrypted and secure\",\r\n \"free\": \"Free\"\r\n}\r\n"
32
+ "content": "{\r\n \"pageTitle\": \"Shopping Cart\",\r\n \"title\": \"Shopping Cart\",\r\n \"empty\": \"Your Cart is Empty\",\r\n \"emptyDescription\": \"Looks like you haven't added any items to your cart yet.\",\r\n \"addToCart\": \"Add to Cart\",\r\n \"removeItem\": \"Remove Item\",\r\n \"quantity\": \"Quantity\",\r\n \"subtotal\": \"Subtotal\",\r\n \"shipping\": \"Shipping\",\r\n \"tax\": \"Tax\",\r\n \"total\": \"Total\",\r\n \"proceedToCheckout\": \"Proceed to Checkout\",\r\n \"secureCheckout\": \"Secure Checkout\",\r\n \"orderSummary\": \"Order Summary\",\r\n \"itemsInCart\": \"items in your cart\",\r\n \"items\": \"items\",\r\n \"variant\": \"Variant\",\r\n \"each\": \"each\",\r\n \"freeShippingMessage\": \"Add {{amount}} more for free shipping!\",\r\n \"continueShopping\": \"Continue Shopping\",\r\n \"secureCheckoutDescription\": \"Your payment information is encrypted and secure\",\r\n \"free\": \"Free\"\r\n}\r\n"
33
33
  },
34
34
  {
35
35
  "path": "cart-page/lang/tr.json",
36
36
  "type": "registry:lang",
37
37
  "target": "$modules$/cart-page/lang/tr.json",
38
- "content": "{\r\n \"title\": \"Alışveriş Sepeti\",\r\n \"empty\": \"Sepetiniz Boş\",\r\n \"emptyDescription\": \"Henüz sepetinize hiç ürün eklememişsiniz.\",\r\n \"addToCart\": \"Sepete Ekle\",\r\n \"removeItem\": \"Ürünü Kaldır\",\r\n \"quantity\": \"Adet\",\r\n \"subtotal\": \"Ara Toplam\",\r\n \"shipping\": \"Kargo\",\r\n \"tax\": \"Vergi\",\r\n \"total\": \"Toplam\",\r\n \"proceedToCheckout\": \"Ödemeye Geç\",\r\n \"secureCheckout\": \"Güvenli Ödeme\",\r\n \"orderSummary\": \"Sipariş Özeti\",\r\n \"itemsInCart\": \"sepetinizdeki ürün\",\r\n \"items\": \"ürün\",\r\n \"variant\": \"Varyant\",\r\n \"each\": \"adet\",\r\n \"freeShippingMessage\": \"Ücretsiz kargo için {{amount}} daha ekleyin!\",\r\n \"continueShopping\": \"Alışverişe Devam Et\",\r\n \"secureCheckoutDescription\": \"Ödeme bilgileriniz şifreli ve güvenlidir\",\r\n \"free\": \"Ücretsiz\"\r\n}\r\n"
38
+ "content": "{\r\n \"pageTitle\": \"Alışveriş Sepeti\",\r\n \"title\": \"Alışveriş Sepeti\",\r\n \"empty\": \"Sepetiniz Boş\",\r\n \"emptyDescription\": \"Henüz sepetinize hiç ürün eklememişsiniz.\",\r\n \"addToCart\": \"Sepete Ekle\",\r\n \"removeItem\": \"Ürünü Kaldır\",\r\n \"quantity\": \"Adet\",\r\n \"subtotal\": \"Ara Toplam\",\r\n \"shipping\": \"Kargo\",\r\n \"tax\": \"Vergi\",\r\n \"total\": \"Toplam\",\r\n \"proceedToCheckout\": \"Ödemeye Geç\",\r\n \"secureCheckout\": \"Güvenli Ödeme\",\r\n \"orderSummary\": \"Sipariş Özeti\",\r\n \"itemsInCart\": \"sepetinizdeki ürün\",\r\n \"items\": \"ürün\",\r\n \"variant\": \"Varyant\",\r\n \"each\": \"adet\",\r\n \"freeShippingMessage\": \"Ücretsiz kargo için {{amount}} daha ekleyin!\",\r\n \"continueShopping\": \"Alışverişe Devam Et\",\r\n \"secureCheckoutDescription\": \"Ödeme bilgileriniz şifreli ve güvenlidir\",\r\n \"free\": \"Ücretsiz\"\r\n}\r\n"
39
39
  }
40
40
  ],
41
41
  "exports": {
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "case-study-page",
3
+ "type": "registry:component",
4
+ "title": "Case Study Page",
5
+ "description": "Detailed project case study with challenge/solution/results sections. Includes hero image, gallery, project details sidebar, and share buttons.",
6
+ "dependencies": [
7
+ "lucide-react"
8
+ ],
9
+ "registryDependencies": [
10
+ "button",
11
+ "animations",
12
+ "share-buttons"
13
+ ],
14
+ "usage": "import { CaseStudyPage } from '@/modules/case-study-page';\n\n<CaseStudyPage />\n\n• Installed at: src/modules/case-study-page/\n• Customize content: lang/en/case-study-page.json\n• Props: title, heroImage, gallery[], challenge, solution, results, details",
15
+ "files": [
16
+ {
17
+ "path": "case-study-page/case-study-page.tsx",
18
+ "type": "registry:component",
19
+ "target": "$modules$/case-study-page/case-study-page.tsx",
20
+ "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { Link } from \"react-router\";\nimport { cn } from \"@/lib/utils\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { Layout } from \"@/components/Layout\";\nimport { Button } from \"@/components/ui/button\";\nimport { ShareButtons } from \"@/modules/share-buttons\";\nimport { FadeIn } from \"@/modules/animations\";\nimport { ChevronLeft, ChevronRight } from \"lucide-react\";\n\ninterface ProjectDetails {\n client?: string;\n date?: string;\n category?: string;\n services?: string[];\n website?: string;\n}\n\ninterface CaseStudySection {\n title: string;\n content: string;\n}\n\ninterface CaseStudyPageProps {\n title?: string;\n subtitle?: string;\n heroImage?: string;\n gallery?: string[];\n challenge?: CaseStudySection;\n solution?: CaseStudySection;\n results?: CaseStudySection;\n details?: ProjectDetails;\n prevProject?: { title: string; link: string };\n nextProject?: { title: string; link: string };\n className?: string;\n}\n\nexport function CaseStudyPage({\n title,\n subtitle,\n heroImage,\n gallery,\n challenge,\n solution,\n results,\n details,\n prevProject,\n nextProject,\n className,\n}: CaseStudyPageProps) {\n const { t } = useTranslation(\"case-study-page\");\n usePageTitle({ title: t(\"pageTitle\") });\n const [selectedImage, setSelectedImage] = useState<string | null>(null);\n\n const displayTitle = title ?? t(\"title\");\n const displaySubtitle = subtitle ?? t(\"subtitle\");\n const displayHeroImage = heroImage ?? \"/images/placeholder.png\";\n const displayGallery = gallery ?? [\n \"/images/placeholder.png\",\n \"/images/placeholder.png\",\n \"/images/placeholder.png\",\n \"/images/placeholder.png\",\n ];\n\n const displayChallenge = challenge ?? {\n title: t(\"sections.challenge.title\"),\n content: t(\"sections.challenge.content\"),\n };\n\n const displaySolution = solution ?? {\n title: t(\"sections.solution.title\"),\n content: t(\"sections.solution.content\"),\n };\n\n const displayResults = results ?? {\n title: t(\"sections.results.title\"),\n content: t(\"sections.results.content\"),\n };\n\n const displayDetails = details ?? {\n client: t(\"details.client\"),\n date: t(\"details.date\"),\n category: t(\"details.category\"),\n services: [t(\"details.services.0\"), t(\"details.services.1\"), t(\"details.services.2\")],\n website: \"https://example.com\",\n };\n\n const displayPrevProject = prevProject ?? {\n title: t(\"navigation.prev\"),\n link: \"/portfolio\",\n };\n\n const displayNextProject = nextProject ?? {\n title: t(\"navigation.next\"),\n link: \"/portfolio\",\n };\n\n return (\n <Layout>\n <div className={cn(\"min-h-screen\", className)}>\n {/* Hero Section */}\n <div className=\"relative h-[50vh] md:h-[70vh] overflow-hidden\">\n <img\n src={displayHeroImage}\n alt={displayTitle}\n className=\"w-full h-full object-cover\"\n />\n <div className=\"absolute inset-0 bg-gradient-to-t from-background via-background/50 to-transparent\" />\n <div className=\"absolute bottom-0 left-0 right-0 p-8 md:p-16\">\n <div className=\"container max-w-[var(--container-max-width)] mx-auto\">\n <FadeIn>\n <h1 className=\"text-4xl md:text-5xl lg:text-6xl font-bold mb-4\">\n {displayTitle}\n </h1>\n <p className=\"text-lg md:text-xl text-muted-foreground max-w-2xl\">\n {displaySubtitle}\n </p>\n </FadeIn>\n </div>\n </div>\n </div>\n\n {/* Content */}\n <div className=\"container max-w-[var(--container-max-width)] mx-auto px-4 py-12 md:py-16\">\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-12\">\n {/* Main Content */}\n <div className=\"lg:col-span-2 space-y-12\">\n {/* Challenge */}\n <FadeIn>\n <section>\n <h2 className=\"text-2xl md:text-3xl font-bold mb-4\">\n {displayChallenge.title}\n </h2>\n <p className=\"text-muted-foreground leading-relaxed\">\n {displayChallenge.content}\n </p>\n </section>\n </FadeIn>\n\n {/* Solution */}\n <FadeIn delay={0.1}>\n <section>\n <h2 className=\"text-2xl md:text-3xl font-bold mb-4\">\n {displaySolution.title}\n </h2>\n <p className=\"text-muted-foreground leading-relaxed\">\n {displaySolution.content}\n </p>\n </section>\n </FadeIn>\n\n {/* Gallery */}\n <FadeIn delay={0.2}>\n <section>\n <h2 className=\"text-2xl md:text-3xl font-bold mb-6\">\n {t(\"gallery\")}\n </h2>\n <div className=\"grid grid-cols-2 gap-4\">\n {displayGallery.map((image, index) => (\n <button\n key={index}\n onClick={() => setSelectedImage(image)}\n className=\"aspect-[4/3] overflow-hidden rounded-xl group\"\n >\n <img\n src={image}\n alt={`Gallery ${index + 1}`}\n className=\"w-full h-full object-cover transition-transform duration-300 group-hover:scale-105\"\n />\n </button>\n ))}\n </div>\n </section>\n </FadeIn>\n\n {/* Results */}\n <FadeIn delay={0.3}>\n <section>\n <h2 className=\"text-2xl md:text-3xl font-bold mb-4\">\n {displayResults.title}\n </h2>\n <p className=\"text-muted-foreground leading-relaxed\">\n {displayResults.content}\n </p>\n </section>\n </FadeIn>\n </div>\n\n {/* Sidebar */}\n <div className=\"lg:col-span-1\">\n <FadeIn delay={0.2}>\n <div className=\"sticky top-24 space-y-8\">\n {/* Project Details */}\n <div className=\"bg-muted/50 rounded-xl p-6\">\n <h3 className=\"text-lg font-semibold mb-4\">{t(\"projectDetails\")}</h3>\n <dl className=\"space-y-4\">\n {displayDetails.client && (\n <div>\n <dt className=\"text-sm text-muted-foreground\">{t(\"labels.client\")}</dt>\n <dd className=\"font-medium\">{displayDetails.client}</dd>\n </div>\n )}\n {displayDetails.date && (\n <div>\n <dt className=\"text-sm text-muted-foreground\">{t(\"labels.date\")}</dt>\n <dd className=\"font-medium\">{displayDetails.date}</dd>\n </div>\n )}\n {displayDetails.category && (\n <div>\n <dt className=\"text-sm text-muted-foreground\">{t(\"labels.category\")}</dt>\n <dd className=\"font-medium\">{displayDetails.category}</dd>\n </div>\n )}\n {displayDetails.services && displayDetails.services.length > 0 && (\n <div>\n <dt className=\"text-sm text-muted-foreground\">{t(\"labels.services\")}</dt>\n <dd className=\"font-medium\">\n {displayDetails.services.join(\", \")}\n </dd>\n </div>\n )}\n {displayDetails.website && (\n <div>\n <dt className=\"text-sm text-muted-foreground\">{t(\"labels.website\")}</dt>\n <dd>\n <a\n href={displayDetails.website}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"font-medium text-primary hover:underline\"\n >\n {t(\"visitWebsite\")}\n </a>\n </dd>\n </div>\n )}\n </dl>\n </div>\n\n {/* Share */}\n <div>\n <h3 className=\"text-lg font-semibold mb-4\">{t(\"share\")}</h3>\n <ShareButtons />\n </div>\n </div>\n </FadeIn>\n </div>\n </div>\n\n {/* Navigation */}\n <FadeIn delay={0.4}>\n <div className=\"mt-16 pt-8 border-t border-border\">\n <div className=\"flex justify-between items-center\">\n <Link to={displayPrevProject.link}>\n <Button variant=\"ghost\" className=\"gap-2\">\n <ChevronLeft className=\"w-4 h-4\" />\n <span className=\"hidden sm:inline\">{displayPrevProject.title}</span>\n <span className=\"sm:hidden\">{t(\"prev\")}</span>\n </Button>\n </Link>\n <Link to=\"/portfolio\">\n <Button variant=\"outline\">{t(\"allProjects\")}</Button>\n </Link>\n <Link to={displayNextProject.link}>\n <Button variant=\"ghost\" className=\"gap-2\">\n <span className=\"hidden sm:inline\">{displayNextProject.title}</span>\n <span className=\"sm:hidden\">{t(\"next\")}</span>\n <ChevronRight className=\"w-4 h-4\" />\n </Button>\n </Link>\n </div>\n </div>\n </FadeIn>\n </div>\n\n {/* Lightbox */}\n {selectedImage && (\n <div\n className=\"fixed inset-0 z-50 bg-black/90 flex items-center justify-center p-4\"\n onClick={() => setSelectedImage(null)}\n >\n <button\n className=\"absolute top-4 right-4 text-white/80 hover:text-white\"\n onClick={() => setSelectedImage(null)}\n >\n <svg\n className=\"w-8 h-8\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M6 18L18 6M6 6l12 12\"\n />\n </svg>\n </button>\n <img\n src={selectedImage}\n alt=\"Gallery\"\n className=\"max-w-full max-h-[90vh] object-contain rounded-lg\"\n onClick={(e) => e.stopPropagation()}\n />\n </div>\n )}\n </div>\n </Layout>\n );\n}\n\nexport default CaseStudyPage;\n"
21
+ },
22
+ {
23
+ "path": "case-study-page/index.ts",
24
+ "type": "registry:index",
25
+ "target": "$modules$/case-study-page/index.ts",
26
+ "content": "export * from \"./case-study-page\";\r\nexport { CaseStudyPage as default } from \"./case-study-page\";\r\n"
27
+ },
28
+ {
29
+ "path": "case-study-page/lang/en.json",
30
+ "type": "registry:lang",
31
+ "target": "$modules$/case-study-page/lang/en.json",
32
+ "content": "{\r\n \"pageTitle\": \"Case Study\",\r\n \"title\": \"E-commerce Platform Redesign\",\r\n \"subtitle\": \"Ask Promake to customize this case study based on your actual project.\",\r\n \"sections\": {\r\n \"challenge\": {\r\n \"title\": \"The Challenge\",\r\n \"content\": \"The client needed a complete overhaul of their e-commerce platform to improve user experience, increase conversion rates, and modernize their brand presence. The existing platform was outdated, had poor mobile support, and suffered from slow load times that were affecting sales.\"\r\n },\r\n \"solution\": {\r\n \"title\": \"Our Solution\",\r\n \"content\": \"We designed and developed a modern, responsive e-commerce platform with a focus on performance and user experience. Key improvements included a streamlined checkout process, advanced product filtering, personalized recommendations, and a complete visual redesign that aligned with the client's evolved brand identity.\"\r\n },\r\n \"results\": {\r\n \"title\": \"The Results\",\r\n \"content\": \"After launch, the client saw a 45% increase in conversion rates, 60% improvement in page load times, and a 35% increase in mobile sales. Customer satisfaction scores improved by 28%, and the average order value increased by 22%.\"\r\n }\r\n },\r\n \"gallery\": \"Project Gallery\",\r\n \"projectDetails\": \"Project Details\",\r\n \"labels\": {\r\n \"client\": \"Client\",\r\n \"date\": \"Date\",\r\n \"category\": \"Category\",\r\n \"services\": \"Services\"\r\n },\r\n \"details\": {\r\n \"client\": \"Acme Corporation\",\r\n \"date\": \"January 2024\",\r\n \"category\": \"Web Design\",\r\n \"services\": [\"UX Design\", \"UI Design\", \"Development\"]\r\n },\r\n \"visitWebsite\": \"Visit Website\",\r\n \"share\": \"Share This Project\",\r\n \"navigation\": {\r\n \"prev\": \"Previous Project\",\r\n \"next\": \"Next Project\"\r\n },\r\n \"prev\": \"Prev\",\r\n \"next\": \"Next\",\r\n \"allProjects\": \"All Projects\"\r\n}\r\n"
33
+ },
34
+ {
35
+ "path": "case-study-page/lang/tr.json",
36
+ "type": "registry:lang",
37
+ "target": "$modules$/case-study-page/lang/tr.json",
38
+ "content": "{\r\n \"pageTitle\": \"Vaka Çalışması\",\r\n \"title\": \"E-ticaret Platformu Yenileme\",\r\n \"subtitle\": \"Bu vaka çalışmasını gerçek projenize göre özelleştirmek için Promake'e sorun.\",\r\n \"sections\": {\r\n \"challenge\": {\r\n \"title\": \"Zorluk\",\r\n \"content\": \"Müşteri, kullanıcı deneyimini iyileştirmek, dönüşüm oranlarını artırmak ve marka varlığını modernize etmek için e-ticaret platformunun tamamen yenilenmesini istedi. Mevcut platform eskimişti, mobil desteği zayıftı ve satışları etkileyen yavaş yükleme süreleri yaşıyordu.\"\r\n },\r\n \"solution\": {\r\n \"title\": \"Çözümümüz\",\r\n \"content\": \"Performans ve kullanıcı deneyimine odaklanan modern, duyarlı bir e-ticaret platformu tasarladık ve geliştirdik. Temel iyileştirmeler arasında basitleştirilmiş ödeme süreci, gelişmiş ürün filtreleme, kişiselleştirilmiş öneriler ve müşterinin gelişen marka kimliğiyle uyumlu tam bir görsel yenileme yer aldı.\"\r\n },\r\n \"results\": {\r\n \"title\": \"Sonuçlar\",\r\n \"content\": \"Lansmandan sonra müşteri, dönüşüm oranlarında %45 artış, sayfa yükleme sürelerinde %60 iyileşme ve mobil satışlarda %35 artış gördü. Müşteri memnuniyeti puanları %28 iyileşti ve ortalama sipariş değeri %22 arttı.\"\r\n }\r\n },\r\n \"gallery\": \"Proje Galerisi\",\r\n \"projectDetails\": \"Proje Detayları\",\r\n \"labels\": {\r\n \"client\": \"Müşteri\",\r\n \"date\": \"Tarih\",\r\n \"category\": \"Kategori\",\r\n \"services\": \"Hizmetler\"\r\n },\r\n \"details\": {\r\n \"client\": \"Acme Corporation\",\r\n \"date\": \"Ocak 2024\",\r\n \"category\": \"Web Tasarımı\",\r\n \"services\": [\"UX Tasarımı\", \"UI Tasarımı\", \"Geliştirme\"]\r\n },\r\n \"visitWebsite\": \"Web Sitesini Ziyaret Et\",\r\n \"share\": \"Bu Projeyi Paylaş\",\r\n \"navigation\": {\r\n \"prev\": \"Önceki Proje\",\r\n \"next\": \"Sonraki Proje\"\r\n },\r\n \"prev\": \"Önceki\",\r\n \"next\": \"Sonraki\",\r\n \"allProjects\": \"Tüm Projeler\"\r\n}\r\n"
39
+ }
40
+ ],
41
+ "exports": {
42
+ "types": [],
43
+ "variables": [
44
+ "CaseStudyPage",
45
+ "default"
46
+ ]
47
+ }
48
+ }