@promakeai/cli 0.9.8 → 0.9.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/README.md +111 -111
  2. package/dist/index.js +142 -142
  3. package/dist/registry/about-page.json +3 -3
  4. package/dist/registry/about-section.json +4 -4
  5. package/dist/registry/animations.json +2 -2
  6. package/dist/registry/announcement-bar.json +4 -4
  7. package/dist/registry/api.json +1 -1
  8. package/dist/registry/auth-core.json +2 -2
  9. package/dist/registry/bento-grid-section.json +4 -4
  10. package/dist/registry/blog-core.json +5 -5
  11. package/dist/registry/blog-list-page.json +4 -4
  12. package/dist/registry/blog-section.json +4 -4
  13. package/dist/registry/cards-carousel-section.json +4 -4
  14. package/dist/registry/cart-drawer.json +4 -4
  15. package/dist/registry/cart-page.json +4 -4
  16. package/dist/registry/case-study-page.json +3 -3
  17. package/dist/registry/category-section.json +4 -4
  18. package/dist/registry/checkout-page.json +4 -4
  19. package/dist/registry/coming-soon-page-minimal.json +4 -4
  20. package/dist/registry/coming-soon-page.json +4 -4
  21. package/dist/registry/contact-info-grid.json +4 -4
  22. package/dist/registry/contact-page-centered.json +4 -4
  23. package/dist/registry/contact-page-map-overlay.json +3 -3
  24. package/dist/registry/contact-page-map-split.json +3 -3
  25. package/dist/registry/contact-page-split.json +4 -4
  26. package/dist/registry/contact-page.json +4 -4
  27. package/dist/registry/content-section.json +4 -4
  28. package/dist/registry/cookie-consent.json +4 -4
  29. package/dist/registry/cookies-page.json +3 -3
  30. package/dist/registry/cta-section.json +3 -3
  31. package/dist/registry/ecommerce-core.json +8 -8
  32. package/dist/registry/empty-page.json +3 -3
  33. package/dist/registry/faq-categorized.json +4 -4
  34. package/dist/registry/faq-simple.json +4 -4
  35. package/dist/registry/favorites-blog-block.json +1 -1
  36. package/dist/registry/favorites-blog-page.json +4 -4
  37. package/dist/registry/favorites-ecommerce-block.json +1 -1
  38. package/dist/registry/favorites-ecommerce-page.json +4 -4
  39. package/dist/registry/feature-section.json +3 -3
  40. package/dist/registry/featured-products.json +4 -4
  41. package/dist/registry/footer-detailed.json +4 -4
  42. package/dist/registry/footer-minimal.json +3 -3
  43. package/dist/registry/footer.json +3 -3
  44. package/dist/registry/forgot-password-page-split.json +4 -4
  45. package/dist/registry/forgot-password-page.json +4 -4
  46. package/dist/registry/google-adsense.json +4 -4
  47. package/dist/registry/google-map.json +2 -2
  48. package/dist/registry/header-centered-pill.json +4 -4
  49. package/dist/registry/header-ecommerce.json +4 -4
  50. package/dist/registry/header-mega.json +4 -4
  51. package/dist/registry/header-minimal.json +4 -4
  52. package/dist/registry/header-simple.json +3 -3
  53. package/dist/registry/hero-carousel.json +3 -3
  54. package/dist/registry/hero-cta.json +4 -4
  55. package/dist/registry/hero-gradient.json +4 -4
  56. package/dist/registry/hero-grid.json +4 -4
  57. package/dist/registry/hero-profile.json +3 -3
  58. package/dist/registry/hero.json +3 -3
  59. package/dist/registry/index.json +103 -103
  60. package/dist/registry/landing-page-app.json +3 -3
  61. package/dist/registry/landing-page-saas.json +3 -3
  62. package/dist/registry/login-page-split.json +4 -4
  63. package/dist/registry/login-page.json +4 -4
  64. package/dist/registry/logo-cloud.json +4 -4
  65. package/dist/registry/masonry-grid.json +3 -3
  66. package/dist/registry/my-orders-page.json +4 -4
  67. package/dist/registry/newsletter-section.json +4 -4
  68. package/dist/registry/order-card-compact.json +3 -3
  69. package/dist/registry/order-confirmation-page.json +4 -4
  70. package/dist/registry/order-detail-block.json +1 -1
  71. package/dist/registry/orders-list-block.json +1 -1
  72. package/dist/registry/payment-success-block.json +2 -2
  73. package/dist/registry/portfolio-page.json +4 -4
  74. package/dist/registry/post-card.json +4 -4
  75. package/dist/registry/post-detail-block.json +4 -4
  76. package/dist/registry/post-detail-page.json +4 -4
  77. package/dist/registry/pricing-card.json +3 -3
  78. package/dist/registry/pricing-page.json +4 -4
  79. package/dist/registry/pricing-section.json +4 -4
  80. package/dist/registry/privacy-page.json +3 -3
  81. package/dist/registry/product-card-detailed.json +4 -4
  82. package/dist/registry/product-card-hover.json +4 -4
  83. package/dist/registry/product-card.json +4 -4
  84. package/dist/registry/product-detail-block.json +2 -2
  85. package/dist/registry/product-detail-page.json +4 -4
  86. package/dist/registry/product-detail-section.json +4 -4
  87. package/dist/registry/product-quick-view.json +4 -4
  88. package/dist/registry/products-page.json +4 -4
  89. package/dist/registry/reading-progress.json +4 -4
  90. package/dist/registry/register-page-split.json +4 -4
  91. package/dist/registry/register-page.json +4 -4
  92. package/dist/registry/related-posts-block.json +1 -1
  93. package/dist/registry/related-products-block.json +2 -2
  94. package/dist/registry/reset-password-page-split.json +4 -4
  95. package/dist/registry/reset-password-page.json +4 -4
  96. package/dist/registry/service-card.json +1 -1
  97. package/dist/registry/share-buttons.json +4 -4
  98. package/dist/registry/skill-card.json +1 -1
  99. package/dist/registry/team-page.json +4 -4
  100. package/dist/registry/terms-page.json +3 -3
  101. package/dist/registry/testimonials-carousel.json +4 -4
  102. package/dist/registry/testimonials-grid.json +4 -4
  103. package/dist/registry/timeline-section.json +4 -4
  104. package/dist/registry/verify-email-page.json +4 -4
  105. package/dist/registry/video-hero.json +4 -4
  106. package/dist/registry/youtube-embed.json +4 -4
  107. package/package.json +1 -1
  108. package/template/.env +5 -5
  109. package/template/README.md +54 -54
  110. package/template/eslint.config.js +41 -41
  111. package/template/index.html +237 -237
  112. package/template/package.json +96 -96
  113. package/template/public/_redirects +1 -1
  114. package/template/public/robots.txt +14 -14
  115. package/template/scripts/init-db.ts +18 -18
  116. package/template/src/App.tsx +21 -21
  117. package/template/src/components/FormField.tsx +48 -48
  118. package/template/src/components/FormFileInput.tsx +75 -75
  119. package/template/src/components/GoogleAnalytics.tsx +34 -34
  120. package/template/src/components/LanguageSwitcher.tsx +53 -53
  121. package/template/src/components/MetriaAnalytics.tsx +68 -68
  122. package/template/src/components/PasswordInput.tsx +60 -60
  123. package/template/src/components/ScriptInjector.tsx +62 -62
  124. package/template/src/components/Stack.tsx +39 -39
  125. package/template/src/constants/constants.json +71 -71
  126. package/template/src/db/index.ts +21 -21
  127. package/template/src/db/provider.tsx +106 -106
  128. package/template/src/db/schema.json +278 -278
  129. package/template/src/db/types.ts +195 -195
  130. package/template/src/hooks/use-debounced-value.ts +12 -12
  131. package/template/src/hooks/use-page-title.ts +55 -55
  132. package/template/src/lang/index.ts +90 -90
  133. package/template/src/lib/api.ts +345 -345
  134. package/template/src/lib/env.ts +19 -19
  135. package/template/src/router.tsx +14 -14
  136. package/template/src/vite-env.d.ts +1 -1
  137. package/template/vite.config.ts +194 -194
@@ -14,25 +14,25 @@
14
14
  "path": "cards-carousel-section/index.ts",
15
15
  "type": "registry:index",
16
16
  "target": "$modules$/cards-carousel-section/index.ts",
17
- "content": "export * from './cards-carousel-section';\r\n"
17
+ "content": "export * from './cards-carousel-section';\n"
18
18
  },
19
19
  {
20
20
  "path": "cards-carousel-section/cards-carousel-section.tsx",
21
21
  "type": "registry:component",
22
22
  "target": "$modules$/cards-carousel-section/cards-carousel-section.tsx",
23
- "content": "import { useEffect, useRef, useState, createContext, useContext } from \"react\";\r\nimport type { ReactNode } from \"react\";\r\nimport { motion, AnimatePresence } from \"motion/react\";\r\nimport { X, ChevronLeft, ChevronRight } from \"lucide-react\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\n// Context for outside click detection\r\nconst CarouselContext = createContext<{\r\n onCardClose: (index: number) => void;\r\n currentIndex: number;\r\n}>({\r\n onCardClose: () => {},\r\n currentIndex: 0,\r\n});\r\n\r\n// Card interface\r\ninterface CardData {\r\n category: string;\r\n title: string;\r\n src: string;\r\n content: ReactNode;\r\n}\r\n\r\n// Card Component\r\nexport function Card({\r\n card,\r\n index,\r\n layout = false,\r\n}: {\r\n card: CardData;\r\n index: number;\r\n layout?: boolean;\r\n}) {\r\n const [open, setOpen] = useState(false);\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const { onCardClose } = useContext(CarouselContext);\r\n\r\n const handleOpen = () => setOpen(true);\r\n const handleClose = () => {\r\n setOpen(false);\r\n onCardClose(index);\r\n };\r\n\r\n useEffect(() => {\r\n function onKeyDown(event: KeyboardEvent) {\r\n if (event.key === \"Escape\") {\r\n handleClose();\r\n }\r\n }\r\n\r\n if (open) {\r\n document.body.style.overflow = \"hidden\";\r\n window.addEventListener(\"keydown\", onKeyDown);\r\n } else {\r\n document.body.style.overflow = \"auto\";\r\n }\r\n\r\n return () => {\r\n window.removeEventListener(\"keydown\", onKeyDown);\r\n document.body.style.overflow = \"auto\";\r\n };\r\n }, [open, handleClose]);\r\n\r\n return (\r\n <>\r\n <AnimatePresence>\r\n {open && (\r\n <div className=\"fixed inset-0 h-screen z-50 overflow-auto\">\r\n <motion.div\r\n initial={{ opacity: 0 }}\r\n animate={{ opacity: 1 }}\r\n exit={{ opacity: 0 }}\r\n className=\"bg-black/80 backdrop-blur-lg h-full w-full fixed inset-0\"\r\n onClick={handleClose}\r\n />\r\n <motion.div\r\n initial={{ opacity: 0 }}\r\n animate={{ opacity: 1 }}\r\n exit={{ opacity: 0 }}\r\n ref={containerRef}\r\n layoutId={layout ? `card-${card.title}` : undefined}\r\n className=\"max-w-5xl mx-auto bg-white dark:bg-neutral-900 h-fit z-[60] my-10 p-4 md:p-10 rounded-3xl relative\"\r\n >\r\n <button\r\n className=\"sticky top-4 h-8 w-8 right-0 ml-auto bg-black dark:bg-white rounded-full flex items-center justify-center\"\r\n onClick={handleClose}\r\n >\r\n <X className=\"h-6 w-6 text-neutral-100 dark:text-neutral-900\" />\r\n </button>\r\n <motion.p\r\n layoutId={layout ? `category-${card.title}` : undefined}\r\n className=\"text-base font-medium text-black dark:text-white\"\r\n >\r\n {card.category}\r\n </motion.p>\r\n <motion.p\r\n layoutId={layout ? `title-${card.title}` : undefined}\r\n className=\"text-2xl md:text-5xl font-semibold text-neutral-700 mt-4 dark:text-white\"\r\n >\r\n {card.title}\r\n </motion.p>\r\n <div className=\"py-10\">{card.content}</div>\r\n </motion.div>\r\n </div>\r\n )}\r\n </AnimatePresence>\r\n <motion.button\r\n layoutId={layout ? `card-${card.title}` : undefined}\r\n onClick={handleOpen}\r\n className=\"rounded-3xl bg-gray-100 dark:bg-neutral-900 h-80 w-56 md:h-[40rem] md:w-96 overflow-hidden flex flex-col items-start justify-start relative z-10\"\r\n >\r\n <div className=\"absolute h-full top-0 inset-x-0 bg-gradient-to-b from-black/50 via-transparent to-transparent z-30 pointer-events-none\" />\r\n <div className=\"relative z-40 p-8\">\r\n <motion.p\r\n layoutId={layout ? `category-${card.title}` : undefined}\r\n className=\"text-white text-sm md:text-base font-medium text-left\"\r\n >\r\n {card.category}\r\n </motion.p>\r\n <motion.p\r\n layoutId={layout ? `title-${card.title}` : undefined}\r\n className=\"text-white text-xl md:text-3xl font-semibold max-w-xs text-left mt-2\"\r\n >\r\n {card.title}\r\n </motion.p>\r\n </div>\r\n <img\r\n src={card.src}\r\n alt={card.title}\r\n className=\"object-cover absolute z-10 inset-0 w-full h-full\"\r\n />\r\n </motion.button>\r\n </>\r\n );\r\n}\r\n\r\n// Carousel Component\r\nexport function Carousel({ items }: { items: ReactNode[] }) {\r\n const carouselRef = useRef<HTMLDivElement>(null);\r\n const [canScrollLeft, setCanScrollLeft] = useState(false);\r\n const [canScrollRight, setCanScrollRight] = useState(true);\r\n const [currentIndex, setCurrentIndex] = useState(0);\r\n\r\n const checkScrollability = () => {\r\n if (carouselRef.current) {\r\n const { scrollLeft, scrollWidth, clientWidth } = carouselRef.current;\r\n setCanScrollLeft(scrollLeft > 0);\r\n setCanScrollRight(scrollLeft < scrollWidth - clientWidth);\r\n }\r\n };\r\n\r\n useEffect(() => {\r\n checkScrollability();\r\n }, []);\r\n\r\n const scrollLeft = () => {\r\n if (carouselRef.current) {\r\n carouselRef.current.scrollBy({ left: -300, behavior: \"smooth\" });\r\n }\r\n };\r\n\r\n const scrollRight = () => {\r\n if (carouselRef.current) {\r\n carouselRef.current.scrollBy({ left: 300, behavior: \"smooth\" });\r\n }\r\n };\r\n\r\n const handleCardClose = (index: number) => {\r\n if (carouselRef.current) {\r\n const cardWidth = isMobile() ? 230 : 384;\r\n const gap = isMobile() ? 4 : 8;\r\n const scrollPosition = (cardWidth + gap) * (index + 1);\r\n carouselRef.current.scrollTo({\r\n left: scrollPosition,\r\n behavior: \"smooth\",\r\n });\r\n setCurrentIndex(index);\r\n }\r\n };\r\n\r\n const isMobile = () => {\r\n return window && window.innerWidth < 768;\r\n };\r\n\r\n return (\r\n <CarouselContext.Provider value={{ onCardClose: handleCardClose, currentIndex }}>\r\n <div className=\"relative w-full\">\r\n <div\r\n className=\"flex w-full overflow-x-scroll overscroll-x-auto py-10 md:py-20 scroll-smooth [scrollbar-width:none]\"\r\n ref={carouselRef}\r\n onScroll={checkScrollability}\r\n >\r\n <div className=\"absolute right-0 z-[1000] h-auto w-[5%] overflow-hidden bg-gradient-to-l from-background to-transparent pointer-events-none\" />\r\n <div className=\"flex flex-row justify-start gap-4 pl-4 max-w-7xl mx-auto\">\r\n {items.map((item, index) => (\r\n <motion.div\r\n initial={{ opacity: 0, y: 20 }}\r\n animate={{\r\n opacity: 1,\r\n y: 0,\r\n transition: {\r\n duration: 0.5,\r\n delay: 0.2 * index,\r\n ease: \"easeOut\",\r\n },\r\n }}\r\n key={\"card\" + index}\r\n className=\"last:pr-[5%] md:last:pr-[33%] rounded-3xl\"\r\n >\r\n {item}\r\n </motion.div>\r\n ))}\r\n </div>\r\n </div>\r\n <div className=\"flex justify-end gap-2 mr-10\">\r\n <button\r\n className=\"relative z-40 h-10 w-10 rounded-full bg-gray-100 flex items-center justify-center disabled:opacity-50\"\r\n onClick={scrollLeft}\r\n disabled={!canScrollLeft}\r\n >\r\n <ChevronLeft className=\"h-6 w-6 text-gray-500\" />\r\n </button>\r\n <button\r\n className=\"relative z-40 h-10 w-10 rounded-full bg-gray-100 flex items-center justify-center disabled:opacity-50\"\r\n onClick={scrollRight}\r\n disabled={!canScrollRight}\r\n >\r\n <ChevronRight className=\"h-6 w-6 text-gray-500\" />\r\n </button>\r\n </div>\r\n </div>\r\n </CarouselContext.Provider>\r\n );\r\n}\r\n\r\n// Section Component\r\ninterface CardsCarouselSectionProps {\r\n title?: string;\r\n items: CardData[];\r\n className?: string;\r\n}\r\n\r\nexport function CardsCarouselSection({\r\n title,\r\n items,\r\n className,\r\n}: CardsCarouselSectionProps) {\r\n const cards = items.map((card, index) => (\r\n <Card key={card.src} card={card} index={index} />\r\n ));\r\n\r\n return (\r\n <section className={cn(\"w-full py-16 md:py-20\", className)}>\r\n {title && (\r\n <h2 className=\"max-w-7xl pl-4 mx-auto text-xl md:text-5xl font-bold text-neutral-800 dark:text-neutral-200\">\r\n {title}\r\n </h2>\r\n )}\r\n <Carousel items={cards} />\r\n </section>\r\n );\r\n}\r\n"
23
+ "content": "import { useEffect, useRef, useState, createContext, useContext } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { motion, AnimatePresence } from \"motion/react\";\nimport { X, ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\n// Context for outside click detection\nconst CarouselContext = createContext<{\n onCardClose: (index: number) => void;\n currentIndex: number;\n}>({\n onCardClose: () => {},\n currentIndex: 0,\n});\n\n// Card interface\ninterface CardData {\n category: string;\n title: string;\n src: string;\n content: ReactNode;\n}\n\n// Card Component\nexport function Card({\n card,\n index,\n layout = false,\n}: {\n card: CardData;\n index: number;\n layout?: boolean;\n}) {\n const [open, setOpen] = useState(false);\n const containerRef = useRef<HTMLDivElement>(null);\n const { onCardClose } = useContext(CarouselContext);\n\n const handleOpen = () => setOpen(true);\n const handleClose = () => {\n setOpen(false);\n onCardClose(index);\n };\n\n useEffect(() => {\n function onKeyDown(event: KeyboardEvent) {\n if (event.key === \"Escape\") {\n handleClose();\n }\n }\n\n if (open) {\n document.body.style.overflow = \"hidden\";\n window.addEventListener(\"keydown\", onKeyDown);\n } else {\n document.body.style.overflow = \"auto\";\n }\n\n return () => {\n window.removeEventListener(\"keydown\", onKeyDown);\n document.body.style.overflow = \"auto\";\n };\n }, [open, handleClose]);\n\n return (\n <>\n <AnimatePresence>\n {open && (\n <div className=\"fixed inset-0 h-screen z-50 overflow-auto\">\n <motion.div\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n className=\"bg-black/80 backdrop-blur-lg h-full w-full fixed inset-0\"\n onClick={handleClose}\n />\n <motion.div\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n ref={containerRef}\n layoutId={layout ? `card-${card.title}` : undefined}\n className=\"max-w-5xl mx-auto bg-white dark:bg-neutral-900 h-fit z-[60] my-10 p-4 md:p-10 rounded-3xl relative\"\n >\n <button\n className=\"sticky top-4 h-8 w-8 right-0 ml-auto bg-black dark:bg-white rounded-full flex items-center justify-center\"\n onClick={handleClose}\n >\n <X className=\"h-6 w-6 text-neutral-100 dark:text-neutral-900\" />\n </button>\n <motion.p\n layoutId={layout ? `category-${card.title}` : undefined}\n className=\"text-base font-medium text-black dark:text-white\"\n >\n {card.category}\n </motion.p>\n <motion.p\n layoutId={layout ? `title-${card.title}` : undefined}\n className=\"text-2xl md:text-5xl font-semibold text-neutral-700 mt-4 dark:text-white\"\n >\n {card.title}\n </motion.p>\n <div className=\"py-10\">{card.content}</div>\n </motion.div>\n </div>\n )}\n </AnimatePresence>\n <motion.button\n layoutId={layout ? `card-${card.title}` : undefined}\n onClick={handleOpen}\n className=\"rounded-3xl bg-gray-100 dark:bg-neutral-900 h-80 w-56 md:h-[40rem] md:w-96 overflow-hidden flex flex-col items-start justify-start relative z-10\"\n >\n <div className=\"absolute h-full top-0 inset-x-0 bg-gradient-to-b from-black/50 via-transparent to-transparent z-30 pointer-events-none\" />\n <div className=\"relative z-40 p-8\">\n <motion.p\n layoutId={layout ? `category-${card.title}` : undefined}\n className=\"text-white text-sm md:text-base font-medium text-left\"\n >\n {card.category}\n </motion.p>\n <motion.p\n layoutId={layout ? `title-${card.title}` : undefined}\n className=\"text-white text-xl md:text-3xl font-semibold max-w-xs text-left mt-2\"\n >\n {card.title}\n </motion.p>\n </div>\n <img\n src={card.src}\n alt={card.title}\n className=\"object-cover absolute z-10 inset-0 w-full h-full\"\n />\n </motion.button>\n </>\n );\n}\n\n// Carousel Component\nexport function Carousel({ items }: { items: ReactNode[] }) {\n const carouselRef = useRef<HTMLDivElement>(null);\n const [canScrollLeft, setCanScrollLeft] = useState(false);\n const [canScrollRight, setCanScrollRight] = useState(true);\n const [currentIndex, setCurrentIndex] = useState(0);\n\n const checkScrollability = () => {\n if (carouselRef.current) {\n const { scrollLeft, scrollWidth, clientWidth } = carouselRef.current;\n setCanScrollLeft(scrollLeft > 0);\n setCanScrollRight(scrollLeft < scrollWidth - clientWidth);\n }\n };\n\n useEffect(() => {\n checkScrollability();\n }, []);\n\n const scrollLeft = () => {\n if (carouselRef.current) {\n carouselRef.current.scrollBy({ left: -300, behavior: \"smooth\" });\n }\n };\n\n const scrollRight = () => {\n if (carouselRef.current) {\n carouselRef.current.scrollBy({ left: 300, behavior: \"smooth\" });\n }\n };\n\n const handleCardClose = (index: number) => {\n if (carouselRef.current) {\n const cardWidth = isMobile() ? 230 : 384;\n const gap = isMobile() ? 4 : 8;\n const scrollPosition = (cardWidth + gap) * (index + 1);\n carouselRef.current.scrollTo({\n left: scrollPosition,\n behavior: \"smooth\",\n });\n setCurrentIndex(index);\n }\n };\n\n const isMobile = () => {\n return window && window.innerWidth < 768;\n };\n\n return (\n <CarouselContext.Provider value={{ onCardClose: handleCardClose, currentIndex }}>\n <div className=\"relative w-full\">\n <div\n className=\"flex w-full overflow-x-scroll overscroll-x-auto py-10 md:py-20 scroll-smooth [scrollbar-width:none]\"\n ref={carouselRef}\n onScroll={checkScrollability}\n >\n <div className=\"absolute right-0 z-[1000] h-auto w-[5%] overflow-hidden bg-gradient-to-l from-background to-transparent pointer-events-none\" />\n <div className=\"flex flex-row justify-start gap-4 pl-4 max-w-7xl mx-auto\">\n {items.map((item, index) => (\n <motion.div\n initial={{ opacity: 0, y: 20 }}\n animate={{\n opacity: 1,\n y: 0,\n transition: {\n duration: 0.5,\n delay: 0.2 * index,\n ease: \"easeOut\",\n },\n }}\n key={\"card\" + index}\n className=\"last:pr-[5%] md:last:pr-[33%] rounded-3xl\"\n >\n {item}\n </motion.div>\n ))}\n </div>\n </div>\n <div className=\"flex justify-end gap-2 mr-10\">\n <button\n className=\"relative z-40 h-10 w-10 rounded-full bg-gray-100 flex items-center justify-center disabled:opacity-50\"\n onClick={scrollLeft}\n disabled={!canScrollLeft}\n >\n <ChevronLeft className=\"h-6 w-6 text-gray-500\" />\n </button>\n <button\n className=\"relative z-40 h-10 w-10 rounded-full bg-gray-100 flex items-center justify-center disabled:opacity-50\"\n onClick={scrollRight}\n disabled={!canScrollRight}\n >\n <ChevronRight className=\"h-6 w-6 text-gray-500\" />\n </button>\n </div>\n </div>\n </CarouselContext.Provider>\n );\n}\n\n// Section Component\ninterface CardsCarouselSectionProps {\n title?: string;\n items: CardData[];\n className?: string;\n}\n\nexport function CardsCarouselSection({\n title,\n items,\n className,\n}: CardsCarouselSectionProps) {\n const cards = items.map((card, index) => (\n <Card key={card.src} card={card} index={index} />\n ));\n\n return (\n <section className={cn(\"w-full py-16 md:py-20\", className)}>\n {title && (\n <h2 className=\"max-w-7xl pl-4 mx-auto text-xl md:text-5xl font-bold text-neutral-800 dark:text-neutral-200\">\n {title}\n </h2>\n )}\n <Carousel items={cards} />\n </section>\n );\n}\n"
24
24
  },
25
25
  {
26
26
  "path": "cards-carousel-section/lang/en.json",
27
27
  "type": "registry:lang",
28
28
  "target": "$modules$/cards-carousel-section/lang/en.json",
29
- "content": "{\r\n \"title\": \"Explore\"\r\n}\r\n"
29
+ "content": "{\n \"title\": \"Explore\"\n}\n"
30
30
  },
31
31
  {
32
32
  "path": "cards-carousel-section/lang/tr.json",
33
33
  "type": "registry:lang",
34
34
  "target": "$modules$/cards-carousel-section/lang/tr.json",
35
- "content": "{\r\n \"title\": \"Keşfet\"\r\n}\r\n"
35
+ "content": "{\n \"title\": \"Keşfet\"\n}\n"
36
36
  }
37
37
  ],
38
38
  "exports": {
@@ -14,25 +14,25 @@
14
14
  "path": "cart-drawer/index.ts",
15
15
  "type": "registry:index",
16
16
  "target": "$modules$/cart-drawer/index.ts",
17
- "content": "export * from './cart-drawer';\r\n"
17
+ "content": "export * from './cart-drawer';\n"
18
18
  },
19
19
  {
20
20
  "path": "cart-drawer/cart-drawer.tsx",
21
21
  "type": "registry:component",
22
22
  "target": "$modules$/cart-drawer/cart-drawer.tsx",
23
- "content": "import { useMemo } from \"react\";\r\nimport { Link } from \"react-router\";\r\nimport { Minus, Plus } from \"lucide-react\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport {\r\n Sheet,\r\n SheetContent,\r\n SheetHeader,\r\n SheetTitle,\r\n} from \"@/components/ui/sheet\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { useCart, formatPrice } from \"@/modules/ecommerce-core\";\r\nimport type { Category } from \"@/modules/ecommerce-core/types\";\r\nimport { useDbList } from \"@/db\";\r\nimport constants from \"@/constants/constants.json\";\r\n\r\ninterface CartDrawerProps {\r\n checkoutHref?: string;\r\n className?: string;\r\n showTrigger?: boolean;\r\n}\r\n\r\nexport function CartDrawer({\r\n checkoutHref = \"/checkout\",\r\n className,\r\n showTrigger = true,\r\n}: CartDrawerProps) {\r\n const { t } = useTranslation(\"cart-drawer\");\r\n const {\r\n state,\r\n removeItem,\r\n updateQuantity,\r\n isDrawerOpen,\r\n setDrawerOpen,\r\n } = useCart();\r\n const { items, total } = state;\r\n const { data: productCategories = [] } = useDbList<Category>(\"product_categories\");\r\n const categoryMap = useMemo(() => new Map(productCategories.map(c => [c.id, c])), [productCategories]);\r\n const currency = (constants.site as any).currency || \"USD\";\r\n\r\n const getProductPrice = (product: {\r\n price: number;\r\n sale_price?: number;\r\n on_sale?: boolean;\r\n }) => {\r\n return product.on_sale && product.sale_price\r\n ? product.sale_price\r\n : product.price;\r\n };\r\n\r\n const handleQuantityChange = (id: string | number, newQuantity: number) => {\r\n if (newQuantity <= 0) {\r\n removeItem(id);\r\n } else {\r\n updateQuantity(id, newQuantity);\r\n }\r\n };\r\n\r\n return (\r\n <Sheet open={isDrawerOpen} onOpenChange={setDrawerOpen}>\r\n <SheetContent className=\"w-full sm:max-w-md flex flex-col px-6 pb-8\">\r\n <SheetHeader>\r\n <SheetTitle>{t(\"title\", \"Shopping cart\")}</SheetTitle>\r\n </SheetHeader>\r\n\r\n <div className=\"flex-1 overflow-y-auto mt-8\">\r\n {items.length === 0 ? (\r\n <p className=\"text-center text-muted-foreground py-8\">\r\n {t(\"empty\", \"Your cart is empty\")}\r\n </p>\r\n ) : (\r\n <ul className=\"-my-6 divide-y divide-border\">\r\n {items.map((item) => (\r\n <li key={item.id} className=\"flex py-6\">\r\n <div className=\"size-24 shrink-0 overflow-hidden rounded-md border border-border\">\r\n <img\r\n alt={item.product.name}\r\n src={item.product.images?.length ? item.product.images?.[0] : \"/images/placeholder.png\"}\r\n className=\"size-full object-cover\"\r\n />\r\n </div>\r\n\r\n <div className=\"ml-4 flex flex-1 flex-col\">\r\n <div>\r\n <div className=\"flex justify-between text-base font-medium\">\r\n <h3>\r\n <Link\r\n to={`/products/${item.product.slug}`}\r\n onClick={() => setDrawerOpen(false)}\r\n >\r\n {item.product.name}\r\n </Link>\r\n </h3>\r\n <p className=\"ml-4\">\r\n {formatPrice(getProductPrice(item.product), currency)}\r\n </p>\r\n </div>\r\n {categoryMap.get(item.product.categories?.[0] as number)?.name && (\r\n <p className=\"mt-1 text-sm text-muted-foreground\">\r\n {categoryMap.get(item.product.categories?.[0] as number)?.name}\r\n </p>\r\n )}\r\n </div>\r\n <div className=\"flex flex-1 items-end justify-between text-sm\">\r\n <div className=\"flex items-center gap-1\">\r\n <Button\r\n variant=\"outline\"\r\n size=\"icon\"\r\n className=\"h-6 w-6\"\r\n onClick={() =>\r\n handleQuantityChange(item.id, item.quantity - 1)\r\n }\r\n >\r\n <Minus className=\"h-3 w-3\" />\r\n </Button>\r\n <span className=\"w-8 text-center text-sm\">\r\n {item.quantity}\r\n </span>\r\n <Button\r\n variant=\"outline\"\r\n size=\"icon\"\r\n className=\"h-6 w-6\"\r\n onClick={() =>\r\n handleQuantityChange(item.id, item.quantity + 1)\r\n }\r\n >\r\n <Plus className=\"h-3 w-3\" />\r\n </Button>\r\n </div>\r\n\r\n <button\r\n type=\"button\"\r\n onClick={() => removeItem(item.id)}\r\n className=\"font-medium text-primary hover:text-primary/80\"\r\n >\r\n {t(\"remove\", \"Remove\")}\r\n </button>\r\n </div>\r\n </div>\r\n </li>\r\n ))}\r\n </ul>\r\n )}\r\n </div>\r\n\r\n <div className=\"border-t border-border pt-6 mt-6\">\r\n <div className=\"flex justify-between text-base font-medium\">\r\n <p>{t(\"subtotal\", \"Subtotal\")}</p>\r\n <p>{formatPrice(total, currency)}</p>\r\n </div>\r\n <p className=\"mt-0.5 text-sm text-muted-foreground\">\r\n {t(\"shippingNote\", \"Shipping and taxes calculated at checkout.\")}\r\n </p>\r\n <div className=\"mt-6\">\r\n <Button asChild className=\"w-full\" disabled={items.length === 0}>\r\n <Link to={checkoutHref} onClick={() => setDrawerOpen(false)}>\r\n {t(\"checkout\", \"Checkout\")}\r\n </Link>\r\n </Button>\r\n </div>\r\n </div>\r\n </SheetContent>\r\n </Sheet>\r\n );\r\n}\r\n"
23
+ "content": "import { useMemo } from \"react\";\nimport { Link } from \"react-router\";\nimport { Minus, Plus } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n Sheet,\n SheetContent,\n SheetHeader,\n SheetTitle,\n} from \"@/components/ui/sheet\";\nimport { useTranslation } from \"react-i18next\";\nimport { useCart, formatPrice } from \"@/modules/ecommerce-core\";\nimport type { Category } from \"@/modules/ecommerce-core/types\";\nimport { useDbList } from \"@/db\";\nimport constants from \"@/constants/constants.json\";\n\ninterface CartDrawerProps {\n checkoutHref?: string;\n className?: string;\n showTrigger?: boolean;\n}\n\nexport function CartDrawer({\n checkoutHref = \"/checkout\",\n className,\n showTrigger = true,\n}: CartDrawerProps) {\n const { t } = useTranslation(\"cart-drawer\");\n const {\n state,\n removeItem,\n updateQuantity,\n isDrawerOpen,\n setDrawerOpen,\n } = useCart();\n const { items, total } = state;\n const { data: productCategories = [] } = useDbList<Category>(\"product_categories\");\n const categoryMap = useMemo(() => new Map(productCategories.map(c => [c.id, c])), [productCategories]);\n const currency = (constants.site as any).currency || \"USD\";\n\n const getProductPrice = (product: {\n price: number;\n sale_price?: number;\n on_sale?: boolean;\n }) => {\n return product.on_sale && product.sale_price\n ? product.sale_price\n : product.price;\n };\n\n const handleQuantityChange = (id: string | number, newQuantity: number) => {\n if (newQuantity <= 0) {\n removeItem(id);\n } else {\n updateQuantity(id, newQuantity);\n }\n };\n\n return (\n <Sheet open={isDrawerOpen} onOpenChange={setDrawerOpen}>\n <SheetContent className=\"w-full sm:max-w-md flex flex-col px-6 pb-8\">\n <SheetHeader>\n <SheetTitle>{t(\"title\", \"Shopping cart\")}</SheetTitle>\n </SheetHeader>\n\n <div className=\"flex-1 overflow-y-auto mt-8\">\n {items.length === 0 ? (\n <p className=\"text-center text-muted-foreground py-8\">\n {t(\"empty\", \"Your cart is empty\")}\n </p>\n ) : (\n <ul className=\"-my-6 divide-y divide-border\">\n {items.map((item) => (\n <li key={item.id} className=\"flex py-6\">\n <div className=\"size-24 shrink-0 overflow-hidden rounded-md border border-border\">\n <img\n alt={item.product.name}\n src={item.product.images?.length ? item.product.images?.[0] : \"/images/placeholder.png\"}\n className=\"size-full object-cover\"\n />\n </div>\n\n <div className=\"ml-4 flex flex-1 flex-col\">\n <div>\n <div className=\"flex justify-between text-base font-medium\">\n <h3>\n <Link\n to={`/products/${item.product.slug}`}\n onClick={() => setDrawerOpen(false)}\n >\n {item.product.name}\n </Link>\n </h3>\n <p className=\"ml-4\">\n {formatPrice(getProductPrice(item.product), currency)}\n </p>\n </div>\n {categoryMap.get(item.product.categories?.[0] as number)?.name && (\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {categoryMap.get(item.product.categories?.[0] as number)?.name}\n </p>\n )}\n </div>\n <div className=\"flex flex-1 items-end justify-between text-sm\">\n <div className=\"flex items-center gap-1\">\n <Button\n variant=\"outline\"\n size=\"icon\"\n className=\"h-6 w-6\"\n onClick={() =>\n handleQuantityChange(item.id, item.quantity - 1)\n }\n >\n <Minus className=\"h-3 w-3\" />\n </Button>\n <span className=\"w-8 text-center text-sm\">\n {item.quantity}\n </span>\n <Button\n variant=\"outline\"\n size=\"icon\"\n className=\"h-6 w-6\"\n onClick={() =>\n handleQuantityChange(item.id, item.quantity + 1)\n }\n >\n <Plus className=\"h-3 w-3\" />\n </Button>\n </div>\n\n <button\n type=\"button\"\n onClick={() => removeItem(item.id)}\n className=\"font-medium text-primary hover:text-primary/80\"\n >\n {t(\"remove\", \"Remove\")}\n </button>\n </div>\n </div>\n </li>\n ))}\n </ul>\n )}\n </div>\n\n <div className=\"border-t border-border pt-6 mt-6\">\n <div className=\"flex justify-between text-base font-medium\">\n <p>{t(\"subtotal\", \"Subtotal\")}</p>\n <p>{formatPrice(total, currency)}</p>\n </div>\n <p className=\"mt-0.5 text-sm text-muted-foreground\">\n {t(\"shippingNote\", \"Shipping and taxes calculated at checkout.\")}\n </p>\n <div className=\"mt-6\">\n <Button asChild className=\"w-full\" disabled={items.length === 0}>\n <Link to={checkoutHref} onClick={() => setDrawerOpen(false)}>\n {t(\"checkout\", \"Checkout\")}\n </Link>\n </Button>\n </div>\n </div>\n </SheetContent>\n </Sheet>\n );\n}\n"
24
24
  },
25
25
  {
26
26
  "path": "cart-drawer/lang/en.json",
27
27
  "type": "registry:lang",
28
28
  "target": "$modules$/cart-drawer/lang/en.json",
29
- "content": "{\r\n \"title\": \"Shopping cart\",\r\n \"empty\": \"Your cart is empty\",\r\n \"qty\": \"Qty\",\r\n \"remove\": \"Remove\",\r\n \"subtotal\": \"Subtotal\",\r\n \"shippingNote\": \"Shipping and taxes calculated at checkout.\",\r\n \"checkout\": \"Checkout\",\r\n \"or\": \"or\",\r\n \"continueShopping\": \"Continue Shopping\"\r\n}\r\n"
29
+ "content": "{\n \"title\": \"Shopping cart\",\n \"empty\": \"Your cart is empty\",\n \"qty\": \"Qty\",\n \"remove\": \"Remove\",\n \"subtotal\": \"Subtotal\",\n \"shippingNote\": \"Shipping and taxes calculated at checkout.\",\n \"checkout\": \"Checkout\",\n \"or\": \"or\",\n \"continueShopping\": \"Continue Shopping\"\n}\n"
30
30
  },
31
31
  {
32
32
  "path": "cart-drawer/lang/tr.json",
33
33
  "type": "registry:lang",
34
34
  "target": "$modules$/cart-drawer/lang/tr.json",
35
- "content": "{\r\n \"title\": \"Sepet\",\r\n \"empty\": \"Sepetiniz boş\",\r\n \"qty\": \"Adet\",\r\n \"remove\": \"Kaldır\",\r\n \"subtotal\": \"Ara Toplam\",\r\n \"shippingNote\": \"Kargo ve vergiler ödeme sırasında hesaplanır.\",\r\n \"checkout\": \"Ödemeye Geç\",\r\n \"or\": \"veya\",\r\n \"continueShopping\": \"Alışverişe Devam Et\"\r\n}\r\n"
35
+ "content": "{\n \"title\": \"Sepet\",\n \"empty\": \"Sepetiniz boş\",\n \"qty\": \"Adet\",\n \"remove\": \"Kaldır\",\n \"subtotal\": \"Ara Toplam\",\n \"shippingNote\": \"Kargo ve vergiler ödeme sırasında hesaplanır.\",\n \"checkout\": \"Ödemeye Geç\",\n \"or\": \"veya\",\n \"continueShopping\": \"Alışverişe Devam Et\"\n}\n"
36
36
  }
37
37
  ],
38
38
  "exports": {
@@ -17,25 +17,25 @@
17
17
  "path": "cart-page/index.ts",
18
18
  "type": "registry:index",
19
19
  "target": "$modules$/cart-page/index.ts",
20
- "content": "export * from './cart-page';\r\nexport { CartPage as default } from './cart-page';\r\n"
20
+ "content": "export * from './cart-page';\nexport { CartPage as default } from './cart-page';\n"
21
21
  },
22
22
  {
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 { useMemo } from \"react\";\r\nimport { Link } from \"react-router\";\r\nimport { Trash2, Plus, Minus, ArrowLeft, ShoppingBag } from \"lucide-react\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\r\nimport { Separator } from \"@/components/ui/separator\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { useCart, formatPrice } from \"@/modules/ecommerce-core\";\r\nimport type { Category } from \"@/modules/ecommerce-core/types\";\r\nimport { useDbList } from \"@/db\";\r\nimport constants from \"@/constants/constants.json\";\r\nimport { FadeIn } from \"@/modules/animations\";\r\n\r\nexport function CartPage() {\r\n const { t } = useTranslation(\"cart-page\");\r\n usePageTitle({ title: t(\"pageTitle\", \"Shopping Cart\") });\r\n const { state, removeItem, updateQuantity } = useCart();\r\n const { items, total } = state;\r\n const { data: productCategories = [] } = useDbList<Category>(\"product_categories\");\r\n const categoryMap = useMemo(() => new Map(productCategories.map(c => [c.id, c])), [productCategories]);\r\n\r\n const currency = constants.site.currency || \"USD\";\r\n const shipping = 0;\r\n const tax = 0;\r\n const freeShippingThreshold = 100;\r\n\r\n const getProductPrice = (product: { price: number; sale_price?: number; on_sale?: boolean }) => {\r\n return product.on_sale && product.sale_price ? product.sale_price : product.price;\r\n };\r\n\r\n const handleQuantityChange = (productId: number | string, newQuantity: number) => {\r\n if (newQuantity <= 0) {\r\n removeItem(productId);\r\n } else {\r\n updateQuantity(productId, newQuantity);\r\n }\r\n };\r\n\r\n const handleQuantityInputChange = (productId: number | string, value: string) => {\r\n const quantity = parseInt(value) || 1;\r\n handleQuantityChange(productId, quantity);\r\n };\r\n\r\n const itemCount = items.reduce((sum, item) => sum + item.quantity, 0);\r\n const finalTotal = total + shipping + tax;\r\n\r\n if (items.length === 0) {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\r\n <div className=\"max-w-2xl mx-auto text-center py-16\">\r\n <div className=\"mb-8\">\r\n <ShoppingBag className=\"h-24 w-24 mx-auto text-muted-foreground mb-4\" />\r\n <h1 className=\"text-3xl font-bold mb-4\">\r\n {t(\"empty\", \"Your Cart is Empty\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-8\">\r\n {t(\"emptyDescription\", \"Looks like you haven't added any items to your cart yet.\")}\r\n </p>\r\n <Button asChild size=\"lg\">\r\n <Link to=\"/products\">\r\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\r\n {t(\"continueShopping\", \"Continue Shopping\")}\r\n </Link>\r\n </Button>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\r\n <FadeIn className=\"flex items-center gap-4 mb-8\">\r\n <Button variant=\"ghost\" size=\"icon\" asChild>\r\n <Link to=\"/products\">\r\n <ArrowLeft className=\"h-4 w-4\" />\r\n </Link>\r\n </Button>\r\n <div>\r\n <h1 className=\"text-3xl font-bold\">{t(\"title\", \"Shopping Cart\")}</h1>\r\n <p className=\"text-muted-foreground\">\r\n {itemCount} {t(\"itemsInCart\", \"items in your cart\")}\r\n </p>\r\n </div>\r\n </FadeIn>\r\n\r\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-8\">\r\n <div className=\"lg:col-span-2 space-y-4\">\r\n {items.map((item) => (\r\n <Card key={item.id}>\r\n <CardContent className=\"p-6\">\r\n <div className=\"flex gap-4\">\r\n <div className=\"w-24 h-24 flex-shrink-0\">\r\n <img\r\n src={item.product.images?.length ? item.product.images?.[0] : \"/images/placeholder.png\"}\r\n alt={item.product.name}\r\n className=\"w-full h-full object-cover rounded-lg\"\r\n />\r\n </div>\r\n\r\n <div className=\"flex-1 space-y-2\">\r\n <div className=\"flex items-start justify-between\">\r\n <div>\r\n <h3 className=\"font-semibold\">{item.product.name}</h3>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {categoryMap.get(item.product.categories?.[0] as number)?.name}\r\n </p>\r\n </div>\r\n <Button\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n onClick={() => removeItem(item.id)}\r\n className=\"text-destructive hover:text-destructive\"\r\n >\r\n <Trash2 className=\"h-4 w-4\" />\r\n </Button>\r\n </div>\r\n\r\n <div className=\"flex items-center justify-between\">\r\n <div className=\"flex items-center gap-2\">\r\n <Button\r\n variant=\"outline\"\r\n size=\"icon\"\r\n className=\"h-8 w-8\"\r\n onClick={() => handleQuantityChange(item.id, item.quantity - 1)}\r\n >\r\n <Minus className=\"h-3 w-3\" />\r\n </Button>\r\n <Input\r\n type=\"number\"\r\n value={item.quantity}\r\n onChange={(e) => handleQuantityInputChange(item.id, e.target.value)}\r\n className=\"w-16 text-center\"\r\n min=\"1\"\r\n />\r\n <Button\r\n variant=\"outline\"\r\n size=\"icon\"\r\n className=\"h-8 w-8\"\r\n onClick={() => handleQuantityChange(item.id, item.quantity + 1)}\r\n >\r\n <Plus className=\"h-3 w-3\" />\r\n </Button>\r\n </div>\r\n\r\n <div className=\"text-right\">\r\n <p className=\"font-semibold\">\r\n {formatPrice(getProductPrice(item.product) * item.quantity, currency)}\r\n </p>\r\n {item.quantity > 1 && (\r\n <p className=\"text-sm text-muted-foreground\">\r\n {formatPrice(getProductPrice(item.product), currency)} {t(\"each\", \"each\")}\r\n </p>\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n ))}\r\n </div>\r\n\r\n <div className=\"space-y-6\">\r\n <Card>\r\n <CardHeader>\r\n <CardTitle>{t(\"orderSummary\", \"Order Summary\")}</CardTitle>\r\n </CardHeader>\r\n <CardContent className=\"space-y-4\">\r\n <div className=\"flex justify-between\">\r\n <span>\r\n {t(\"subtotal\", \"Subtotal\")} ({itemCount} {t(\"items\", \"items\")})\r\n </span>\r\n <span>{formatPrice(total, currency)}</span>\r\n </div>\r\n\r\n <div className=\"flex justify-between\">\r\n <span>{t(\"shipping\", \"Shipping\")}</span>\r\n <span>\r\n {shipping === 0 ? t(\"free\", \"Free\") : formatPrice(shipping, currency)}\r\n </span>\r\n </div>\r\n\r\n <div className=\"flex justify-between\">\r\n <span>{t(\"tax\", \"Tax\")}</span>\r\n <span>{formatPrice(tax, currency)}</span>\r\n </div>\r\n\r\n <Separator />\r\n\r\n <div className=\"flex justify-between text-lg font-semibold\">\r\n <span>{t(\"total\", \"Total\")}</span>\r\n <span>{formatPrice(finalTotal, currency)}</span>\r\n </div>\r\n\r\n {shipping > 0 && freeShippingThreshold && freeShippingThreshold > total && (\r\n <div className=\"text-sm text-muted-foreground bg-muted/50 p-3 rounded-lg\">\r\n {t(\"freeShippingMessage\", \"Add {{amount}} more for free shipping!\").replace(\r\n \"{{amount}}\",\r\n formatPrice(freeShippingThreshold - total, currency)\r\n )}\r\n </div>\r\n )}\r\n\r\n <Button asChild className=\"w-full\" size=\"lg\">\r\n <Link to=\"/checkout\">{t(\"proceedToCheckout\", \"Proceed to Checkout\")}</Link>\r\n </Button>\r\n\r\n <Button variant=\"outline\" asChild className=\"w-full\">\r\n <Link to=\"/products\">{t(\"continueShopping\", \"Continue Shopping\")}</Link>\r\n </Button>\r\n </CardContent>\r\n </Card>\r\n\r\n <Card>\r\n <CardContent className=\"p-4\">\r\n <div className=\"text-center space-y-2\">\r\n <div className=\"text-sm text-muted-foreground\">\r\n {t(\"secureCheckout\", \"Secure Checkout\")}\r\n </div>\r\n <p className=\"text-xs text-muted-foreground\">\r\n {t(\"secureCheckoutDescription\", \"Your payment information is encrypted and secure\")}\r\n </p>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default CartPage;\r\n"
26
+ "content": "import { useMemo } from \"react\";\nimport { 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 type { Category } from \"@/modules/ecommerce-core/types\";\nimport { useDbList } from \"@/db\";\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 const { data: productCategories = [] } = useDbList<Category>(\"product_categories\");\n const categoryMap = useMemo(() => new Map(productCategories.map(c => [c.id, c])), [productCategories]);\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?.length ? 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 {categoryMap.get(item.product.categories?.[0] as number)?.name}\n </p>\n </div>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => removeItem(item.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.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.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.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 \"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"
32
+ "content": "{\n \"pageTitle\": \"Shopping Cart\",\n \"title\": \"Shopping Cart\",\n \"empty\": \"Your Cart is Empty\",\n \"emptyDescription\": \"Looks like you haven't added any items to your cart yet.\",\n \"addToCart\": \"Add to Cart\",\n \"removeItem\": \"Remove Item\",\n \"quantity\": \"Quantity\",\n \"subtotal\": \"Subtotal\",\n \"shipping\": \"Shipping\",\n \"tax\": \"Tax\",\n \"total\": \"Total\",\n \"proceedToCheckout\": \"Proceed to Checkout\",\n \"secureCheckout\": \"Secure Checkout\",\n \"orderSummary\": \"Order Summary\",\n \"itemsInCart\": \"items in your cart\",\n \"items\": \"items\",\n \"variant\": \"Variant\",\n \"each\": \"each\",\n \"freeShippingMessage\": \"Add {{amount}} more for free shipping!\",\n \"continueShopping\": \"Continue Shopping\",\n \"secureCheckoutDescription\": \"Your payment information is encrypted and secure\",\n \"free\": \"Free\"\n}\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 \"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"
38
+ "content": "{\n \"pageTitle\": \"Alışveriş Sepeti\",\n \"title\": \"Alışveriş Sepeti\",\n \"empty\": \"Sepetiniz Boş\",\n \"emptyDescription\": \"Henüz sepetinize hiç ürün eklememişsiniz.\",\n \"addToCart\": \"Sepete Ekle\",\n \"removeItem\": \"Ürünü Kaldır\",\n \"quantity\": \"Adet\",\n \"subtotal\": \"Ara Toplam\",\n \"shipping\": \"Kargo\",\n \"tax\": \"Vergi\",\n \"total\": \"Toplam\",\n \"proceedToCheckout\": \"Ödemeye Geç\",\n \"secureCheckout\": \"Güvenli Ödeme\",\n \"orderSummary\": \"Sipariş Özeti\",\n \"itemsInCart\": \"sepetinizdeki ürün\",\n \"items\": \"ürün\",\n \"variant\": \"Varyant\",\n \"each\": \"adet\",\n \"freeShippingMessage\": \"Ücretsiz kargo için {{amount}} daha ekleyin!\",\n \"continueShopping\": \"Alışverişe Devam Et\",\n \"secureCheckoutDescription\": \"Ödeme bilgileriniz şifreli ve güvenlidir\",\n \"free\": \"Ücretsiz\"\n}\n"
39
39
  }
40
40
  ],
41
41
  "exports": {
@@ -23,19 +23,19 @@
23
23
  "path": "case-study-page/index.ts",
24
24
  "type": "registry:index",
25
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"
26
+ "content": "export * from \"./case-study-page\";\nexport { CaseStudyPage as default } from \"./case-study-page\";\n"
27
27
  },
28
28
  {
29
29
  "path": "case-study-page/lang/en.json",
30
30
  "type": "registry:lang",
31
31
  "target": "$modules$/case-study-page/lang/en.json",
32
- "content": "{\r\n \"pageTitle\": \"Case Study\",\r\n \"title\": \"E-commerce Platform Redesign\",\r\n \"subtitle\": \"Let Promake tailor this case study to your actual project.\",\r\n \"sections\": {\r\n \"challenge\": {\r\n \"title\": \"The Challenge\",\r\n \"content\": \"The client needed a complete overhaul of their e-commerce platform to improve user experience, increase conversion rates, and modernize their brand presence. The existing platform was outdated, had poor mobile support, and suffered from slow load times that were affecting sales.\"\r\n },\r\n \"solution\": {\r\n \"title\": \"Our Solution\",\r\n \"content\": \"We designed and developed a modern, responsive e-commerce platform with a focus on performance and user experience. Key improvements included a streamlined checkout process, advanced product filtering, personalized recommendations, and a complete visual redesign that aligned with the client's evolved brand identity.\"\r\n },\r\n \"results\": {\r\n \"title\": \"The Results\",\r\n \"content\": \"After launch, the client saw a 45% increase in conversion rates, 60% improvement in page load times, and a 35% increase in mobile sales. Customer satisfaction scores improved by 28%, and the average order value increased by 22%.\"\r\n }\r\n },\r\n \"gallery\": \"Project Gallery\",\r\n \"projectDetails\": \"Project Details\",\r\n \"labels\": {\r\n \"client\": \"Client\",\r\n \"date\": \"Date\",\r\n \"category\": \"Category\",\r\n \"services\": \"Services\",\r\n \"website\": \"Website\"\r\n },\r\n \"details\": {\r\n \"client\": \"Acme Corporation\",\r\n \"date\": \"January 2024\",\r\n \"category\": \"Web Design\",\r\n \"services\": [\"UX Design\", \"UI Design\", \"Development\"]\r\n },\r\n \"visitWebsite\": \"Visit Website\",\r\n \"share\": \"Share This Project\",\r\n \"navigation\": {\r\n \"prev\": \"Previous Project\",\r\n \"next\": \"Next Project\"\r\n },\r\n \"prev\": \"Prev\",\r\n \"next\": \"Next\",\r\n \"allProjects\": \"All Projects\"\r\n}\r\n"
32
+ "content": "{\n \"pageTitle\": \"Case Study\",\n \"title\": \"E-commerce Platform Redesign\",\n \"subtitle\": \"Let Promake tailor this case study to your actual project.\",\n \"sections\": {\n \"challenge\": {\n \"title\": \"The Challenge\",\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.\"\n },\n \"solution\": {\n \"title\": \"Our Solution\",\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.\"\n },\n \"results\": {\n \"title\": \"The Results\",\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%.\"\n }\n },\n \"gallery\": \"Project Gallery\",\n \"projectDetails\": \"Project Details\",\n \"labels\": {\n \"client\": \"Client\",\n \"date\": \"Date\",\n \"category\": \"Category\",\n \"services\": \"Services\",\n \"website\": \"Website\"\n },\n \"details\": {\n \"client\": \"Acme Corporation\",\n \"date\": \"January 2024\",\n \"category\": \"Web Design\",\n \"services\": [\"UX Design\", \"UI Design\", \"Development\"]\n },\n \"visitWebsite\": \"Visit Website\",\n \"share\": \"Share This Project\",\n \"navigation\": {\n \"prev\": \"Previous Project\",\n \"next\": \"Next Project\"\n },\n \"prev\": \"Prev\",\n \"next\": \"Next\",\n \"allProjects\": \"All Projects\"\n}\n"
33
33
  },
34
34
  {
35
35
  "path": "case-study-page/lang/tr.json",
36
36
  "type": "registry:lang",
37
37
  "target": "$modules$/case-study-page/lang/tr.json",
38
- "content": "{\r\n \"pageTitle\": \"Vaka Çalışması\",\r\n \"title\": \"E-ticaret Platformu Yenileme\",\r\n \"subtitle\": \"Promake ile bu vaka çalışmasını gerçek projenize göre uyarlayın.\",\r\n \"sections\": {\r\n \"challenge\": {\r\n \"title\": \"Zorluk\",\r\n \"content\": \"Müşteri, kullanıcı deneyimini iyileştirmek, dönüşüm oranlarını artırmak ve marka varlığını modernize etmek için e-ticaret platformunun tamamen yenilenmesini istedi. Mevcut platform eskimişti, mobil desteği zayıftı ve satışları etkileyen yavaş yükleme süreleri yaşıyordu.\"\r\n },\r\n \"solution\": {\r\n \"title\": \"Çözümümüz\",\r\n \"content\": \"Performans ve kullanıcı deneyimine odaklanan modern, duyarlı bir e-ticaret platformu tasarladık ve geliştirdik. Temel iyileştirmeler arasında basitleştirilmiş ödeme süreci, gelişmiş ürün filtreleme, kişiselleştirilmiş öneriler ve müşterinin gelişen marka kimliğiyle uyumlu tam bir görsel yenileme yer aldı.\"\r\n },\r\n \"results\": {\r\n \"title\": \"Sonuçlar\",\r\n \"content\": \"Lansmandan sonra müşteri, dönüşüm oranlarında %45 artış, sayfa yükleme sürelerinde %60 iyileşme ve mobil satışlarda %35 artış gördü. Müşteri memnuniyeti puanları %28 iyileşti ve ortalama sipariş değeri %22 arttı.\"\r\n }\r\n },\r\n \"gallery\": \"Proje Galerisi\",\r\n \"projectDetails\": \"Proje Detayları\",\r\n \"labels\": {\r\n \"client\": \"Müşteri\",\r\n \"date\": \"Tarih\",\r\n \"category\": \"Kategori\",\r\n \"services\": \"Hizmetler\",\r\n \"website\": \"Web Sitesi\"\r\n },\r\n \"details\": {\r\n \"client\": \"Acme Corporation\",\r\n \"date\": \"Ocak 2024\",\r\n \"category\": \"Web Tasarımı\",\r\n \"services\": [\"UX Tasarımı\", \"UI Tasarımı\", \"Geliştirme\"]\r\n },\r\n \"visitWebsite\": \"Web Sitesini Ziyaret Et\",\r\n \"share\": \"Bu Projeyi Paylaş\",\r\n \"navigation\": {\r\n \"prev\": \"Önceki Proje\",\r\n \"next\": \"Sonraki Proje\"\r\n },\r\n \"prev\": \"Önceki\",\r\n \"next\": \"Sonraki\",\r\n \"allProjects\": \"Tüm Projeler\"\r\n}\r\n"
38
+ "content": "{\n \"pageTitle\": \"Vaka Çalışması\",\n \"title\": \"E-ticaret Platformu Yenileme\",\n \"subtitle\": \"Promake ile bu vaka çalışmasını gerçek projenize göre uyarlayın.\",\n \"sections\": {\n \"challenge\": {\n \"title\": \"Zorluk\",\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.\"\n },\n \"solution\": {\n \"title\": \"Çözümümüz\",\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ı.\"\n },\n \"results\": {\n \"title\": \"Sonuçlar\",\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ı.\"\n }\n },\n \"gallery\": \"Proje Galerisi\",\n \"projectDetails\": \"Proje Detayları\",\n \"labels\": {\n \"client\": \"Müşteri\",\n \"date\": \"Tarih\",\n \"category\": \"Kategori\",\n \"services\": \"Hizmetler\",\n \"website\": \"Web Sitesi\"\n },\n \"details\": {\n \"client\": \"Acme Corporation\",\n \"date\": \"Ocak 2024\",\n \"category\": \"Web Tasarımı\",\n \"services\": [\"UX Tasarımı\", \"UI Tasarımı\", \"Geliştirme\"]\n },\n \"visitWebsite\": \"Web Sitesini Ziyaret Et\",\n \"share\": \"Bu Projeyi Paylaş\",\n \"navigation\": {\n \"prev\": \"Önceki Proje\",\n \"next\": \"Sonraki Proje\"\n },\n \"prev\": \"Önceki\",\n \"next\": \"Sonraki\",\n \"allProjects\": \"Tüm Projeler\"\n}\n"
39
39
  }
40
40
  ],
41
41
  "exports": {
@@ -10,25 +10,25 @@
10
10
  "path": "category-section/index.ts",
11
11
  "type": "registry:index",
12
12
  "target": "$modules$/category-section/index.ts",
13
- "content": "export * from './category-section';\r\n"
13
+ "content": "export * from './category-section';\n"
14
14
  },
15
15
  {
16
16
  "path": "category-section/category-section.tsx",
17
17
  "type": "registry:component",
18
18
  "target": "$modules$/category-section/category-section.tsx",
19
- "content": "import { Link } from \"react-router\";\r\nimport { ArrowRight } from \"lucide-react\";\r\nimport { Card } from \"@/components/ui/card\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { useDbList, type DbProductCategory } from \"@/db\";\r\n\r\nexport interface CategoryItem {\r\n id: string | number;\r\n slug: string;\r\n name: string;\r\n description?: string;\r\n image: string;\r\n}\r\n\r\nexport interface CategorySectionProps {\r\n categories?: CategoryItem[];\r\n loading?: boolean;\r\n}\r\n\r\nexport function CategorySection({\r\n categories: propCategories,\r\n loading: propLoading,\r\n}: CategorySectionProps) {\r\n const { t } = useTranslation(\"category-section\");\r\n const { data: hookCategories = [], isLoading: hookLoading } = useDbList<DbProductCategory>(\"product_categories\");\r\n\r\n const categories = propCategories ?? hookCategories;\r\n const loading = propLoading ?? hookLoading;\r\n\r\n return (\r\n <section className=\"py-8 sm:py-12 md:py-16 lg:py-20 bg-gradient-to-b from-background to-muted/20 border-t border-border/20\">\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-3 sm:px-4 lg:px-8\">\r\n <div className=\"text-center mb-6 sm:mb-8 md:mb-12 lg:mb-16 px-2\">\r\n <h2 className=\"text-xl sm:text-2xl md:text-3xl lg:text-4xl xl:text-5xl font-bold mb-2 sm:mb-3 md:mb-4 bg-gradient-to-r from-primary to-primary/80 bg-clip-text text-transparent leading-normal pb-1\">\r\n {t(\"title\", \"Shop by Category\")}\r\n </h2>\r\n <div className=\"w-12 sm:w-16 h-1 bg-gradient-to-r from-primary/50 to-primary/20 mx-auto mb-3 sm:mb-4 md:mb-6 rounded-full\"></div>\r\n <p className=\"text-xs sm:text-sm md:text-base lg:text-lg text-muted-foreground max-w-3xl mx-auto leading-relaxed\">\r\n {t(\"subtitle\", \"Discover our carefully curated collections\")}\r\n </p>\r\n </div>\r\n\r\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 sm:gap-6 lg:gap-8\">\r\n {loading\r\n ? [...Array(4)].map((_, i) => (\r\n <div key={i} className=\"animate-pulse\">\r\n <div className=\"aspect-[3/2] bg-muted rounded-xl mb-4\"></div>\r\n <div className=\"h-5 bg-muted rounded w-3/4 mb-2\"></div>\r\n <div className=\"h-4 bg-muted rounded w-1/2\"></div>\r\n </div>\r\n ))\r\n : categories.map((category) => (\r\n <div key={category.id} className=\"contents\" data-db-table=\"product_categories\" data-db-id={category.id}>\r\n <Link\r\n to={`/products?category=${category.slug}`}\r\n className=\"group block\"\r\n >\r\n <Card className=\"overflow-hidden border-0 p-0 shadow-lg hover:shadow-2xl transition-all duration-500 group-hover:-translate-y-2 rounded-2xl\">\r\n <div className=\"aspect-[4/3] relative overflow-hidden\">\r\n <img\r\n src={category.image}\r\n alt={category.name}\r\n className=\"absolute inset-0 w-full h-full object-cover group-hover:scale-110 transition-transform duration-700\"\r\n />\r\n <div className=\"absolute inset-0 bg-gradient-to-t from-black/60 via-black/20 to-transparent group-hover:from-black/70 transition-all duration-300\"></div>\r\n\r\n <div className=\"absolute bottom-0 left-0 right-0 p-4 sm:p-6\">\r\n <h3 className=\"text-lg sm:text-xl font-bold text-white mb-1 sm:mb-2 group-hover:text-primary-foreground transition-colors\">\r\n {category.name}\r\n </h3>\r\n {category.description && (\r\n <p className=\"text-xs sm:text-sm text-white/90 line-clamp-2 group-hover:text-white transition-colors\">\r\n {category.description}\r\n </p>\r\n )}\r\n\r\n <div className=\"flex items-center mt-2 sm:mt-3 text-white/80 group-hover:text-white transition-all duration-300 transform group-hover:translate-x-1\">\r\n <span className=\"text-xs sm:text-sm font-medium mr-2\">\r\n {t(\"explore\", \"Explore\")}\r\n </span>\r\n <ArrowRight className=\"w-3 h-3 sm:w-4 sm:h-4\" />\r\n </div>\r\n </div>\r\n </div>\r\n </Card>\r\n </Link>\r\n </div>\r\n ))}\r\n </div>\r\n </div>\r\n </section>\r\n );\r\n}\r\n"
19
+ "content": "import { Link } from \"react-router\";\nimport { ArrowRight } from \"lucide-react\";\nimport { Card } from \"@/components/ui/card\";\nimport { useTranslation } from \"react-i18next\";\nimport { useDbList, type DbProductCategory } from \"@/db\";\n\nexport interface CategoryItem {\n id: string | number;\n slug: string;\n name: string;\n description?: string;\n image: string;\n}\n\nexport interface CategorySectionProps {\n categories?: CategoryItem[];\n loading?: boolean;\n}\n\nexport function CategorySection({\n categories: propCategories,\n loading: propLoading,\n}: CategorySectionProps) {\n const { t } = useTranslation(\"category-section\");\n const { data: hookCategories = [], isLoading: hookLoading } = useDbList<DbProductCategory>(\"product_categories\");\n\n const categories = propCategories ?? hookCategories;\n const loading = propLoading ?? hookLoading;\n\n return (\n <section className=\"py-8 sm:py-12 md:py-16 lg:py-20 bg-gradient-to-b from-background to-muted/20 border-t border-border/20\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-3 sm:px-4 lg:px-8\">\n <div className=\"text-center mb-6 sm:mb-8 md:mb-12 lg:mb-16 px-2\">\n <h2 className=\"text-xl sm:text-2xl md:text-3xl lg:text-4xl xl:text-5xl font-bold mb-2 sm:mb-3 md:mb-4 bg-gradient-to-r from-primary to-primary/80 bg-clip-text text-transparent leading-normal pb-1\">\n {t(\"title\", \"Shop by Category\")}\n </h2>\n <div className=\"w-12 sm:w-16 h-1 bg-gradient-to-r from-primary/50 to-primary/20 mx-auto mb-3 sm:mb-4 md:mb-6 rounded-full\"></div>\n <p className=\"text-xs sm:text-sm md:text-base lg:text-lg text-muted-foreground max-w-3xl mx-auto leading-relaxed\">\n {t(\"subtitle\", \"Discover our carefully curated collections\")}\n </p>\n </div>\n\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 sm:gap-6 lg:gap-8\">\n {loading\n ? [...Array(4)].map((_, i) => (\n <div key={i} className=\"animate-pulse\">\n <div className=\"aspect-[3/2] bg-muted rounded-xl mb-4\"></div>\n <div className=\"h-5 bg-muted rounded w-3/4 mb-2\"></div>\n <div className=\"h-4 bg-muted rounded w-1/2\"></div>\n </div>\n ))\n : categories.map((category) => (\n <div key={category.id} className=\"contents\" data-db-table=\"product_categories\" data-db-id={category.id}>\n <Link\n to={`/products?category=${category.slug}`}\n className=\"group block\"\n >\n <Card className=\"overflow-hidden border-0 p-0 shadow-lg hover:shadow-2xl transition-all duration-500 group-hover:-translate-y-2 rounded-2xl\">\n <div className=\"aspect-[4/3] relative overflow-hidden\">\n <img\n src={category.image}\n alt={category.name}\n className=\"absolute inset-0 w-full h-full object-cover group-hover:scale-110 transition-transform duration-700\"\n />\n <div className=\"absolute inset-0 bg-gradient-to-t from-black/60 via-black/20 to-transparent group-hover:from-black/70 transition-all duration-300\"></div>\n\n <div className=\"absolute bottom-0 left-0 right-0 p-4 sm:p-6\">\n <h3 className=\"text-lg sm:text-xl font-bold text-white mb-1 sm:mb-2 group-hover:text-primary-foreground transition-colors\">\n {category.name}\n </h3>\n {category.description && (\n <p className=\"text-xs sm:text-sm text-white/90 line-clamp-2 group-hover:text-white transition-colors\">\n {category.description}\n </p>\n )}\n\n <div className=\"flex items-center mt-2 sm:mt-3 text-white/80 group-hover:text-white transition-all duration-300 transform group-hover:translate-x-1\">\n <span className=\"text-xs sm:text-sm font-medium mr-2\">\n {t(\"explore\", \"Explore\")}\n </span>\n <ArrowRight className=\"w-3 h-3 sm:w-4 sm:h-4\" />\n </div>\n </div>\n </div>\n </Card>\n </Link>\n </div>\n ))}\n </div>\n </div>\n </section>\n );\n}\n"
20
20
  },
21
21
  {
22
22
  "path": "category-section/lang/en.json",
23
23
  "type": "registry:lang",
24
24
  "target": "$modules$/category-section/lang/en.json",
25
- "content": "{\r\n \"title\": \"Shop by Category\",\r\n \"subtitle\": \"Discover our carefully curated collections\",\r\n \"explore\": \"Explore\"\r\n}\r\n"
25
+ "content": "{\n \"title\": \"Shop by Category\",\n \"subtitle\": \"Discover our carefully curated collections\",\n \"explore\": \"Explore\"\n}\n"
26
26
  },
27
27
  {
28
28
  "path": "category-section/lang/tr.json",
29
29
  "type": "registry:lang",
30
30
  "target": "$modules$/category-section/lang/tr.json",
31
- "content": "{\r\n \"title\": \"Kategorilere Göz Atın\",\r\n \"subtitle\": \"Özenle seçilmiş koleksiyonlarımızı keşfedin\",\r\n \"explore\": \"Keşfet\"\r\n}\r\n"
31
+ "content": "{\n \"title\": \"Kategorilere Göz Atın\",\n \"subtitle\": \"Özenle seçilmiş koleksiyonlarımızı keşfedin\",\n \"explore\": \"Keşfet\"\n}\n"
32
32
  }
33
33
  ],
34
34
  "exports": {
@@ -18,25 +18,25 @@
18
18
  "path": "checkout-page/index.ts",
19
19
  "type": "registry:index",
20
20
  "target": "$modules$/checkout-page/index.ts",
21
- "content": "export * from './checkout-page';\r\nexport { CheckoutPage as default } from './checkout-page';\r\n"
21
+ "content": "export * from './checkout-page';\nexport { CheckoutPage as default } from './checkout-page';\n"
22
22
  },
23
23
  {
24
24
  "path": "checkout-page/checkout-page.tsx",
25
25
  "type": "registry:page",
26
26
  "target": "$modules$/checkout-page/checkout-page.tsx",
27
- "content": "import { useState, useEffect } from \"react\";\r\nimport { Link } from \"react-router\";\r\nimport { ArrowLeft, CreditCard, Banknote, Truck, Check } from \"lucide-react\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { Textarea } from \"@/components/ui/textarea\";\r\nimport { RadioGroup, RadioGroupItem } from \"@/components/ui/radio-group\";\r\nimport { Separator } from \"@/components/ui/separator\";\r\nimport { Checkbox } from \"@/components/ui/checkbox\";\r\nimport { Skeleton } from \"@/components/ui/skeleton\";\r\nimport {\r\n Select,\r\n SelectContent,\r\n SelectItem,\r\n SelectTrigger,\r\n SelectValue,\r\n} from \"@/components/ui/select\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { toast } from \"sonner\";\r\nimport {\r\n useCart,\r\n formatPrice,\r\n type PaymentMethod,\r\n type OnlinePaymentProvider,\r\n getFilteredPaymentMethodConfigs,\r\n getOnlinePaymentProviders,\r\n ONLINE_PROVIDER_CONFIGS,\r\n} from \"@/modules/ecommerce-core\";\r\nimport { customerClient, getErrorMessage } from \"@/modules/api\";\r\nimport constants from \"@/constants/constants.json\";\r\nimport { FadeIn } from \"@/modules/animations\";\r\nimport { FormField } from \"@/components/FormField\";\r\n\r\ninterface Country {\r\n value: string;\r\n label: string;\r\n}\r\n\r\ninterface CheckoutFormData {\r\n firstName: string;\r\n lastName: string;\r\n email: string;\r\n phone: string;\r\n address: string;\r\n city: string;\r\n postalCode: string;\r\n country: string;\r\n notes: string;\r\n}\r\n\r\ninterface BankTransferInfo {\r\n bank_name: string;\r\n bank_account_name: string;\r\n iban: string;\r\n}\r\n\r\nconst DEFAULT_COUNTRIES: Country[] = [\r\n { value: \"US\", label: \"United States\" },\r\n { value: \"GB\", label: \"United Kingdom\" },\r\n { value: \"CA\", label: \"Canada\" },\r\n { value: \"AU\", label: \"Australia\" },\r\n { value: \"DE\", label: \"Germany\" },\r\n { value: \"FR\", label: \"France\" },\r\n { value: \"IT\", label: \"Italy\" },\r\n { value: \"ES\", label: \"Spain\" },\r\n { value: \"NL\", label: \"Netherlands\" },\r\n { value: \"TR\", label: \"Turkey\" },\r\n { value: \"JP\", label: \"Japan\" },\r\n];\r\n\r\nexport function CheckoutPage() {\r\n const { t } = useTranslation(\"checkout-page\");\r\n usePageTitle({ title: t(\"pageTitle\", \"Checkout\") });\r\n const { state, clearCart } = useCart();\r\n const { items, total } = state;\r\n\r\n const currency = (constants as any).site?.currency || \"USD\";\r\n const taxRate = (constants as any).payments?.taxRate || 0;\r\n const freeShippingThreshold = (constants as any).payments?.freeShippingThreshold || 0;\r\n const shippingCost = (constants as any).shipping?.domesticShipping?.standard?.cost || 0;\r\n\r\n // Calculate shipping and tax\r\n const shipping = total >= freeShippingThreshold ? 0 : shippingCost;\r\n const tax = total * taxRate;\r\n\r\n const countries = DEFAULT_COUNTRIES;\r\n\r\n // Get available payment methods and providers from config\r\n const availablePaymentMethods = getFilteredPaymentMethodConfigs();\r\n const availableProviders = getOnlinePaymentProviders();\r\n\r\n const getProductPrice = (product: {\r\n price: number;\r\n sale_price?: number;\r\n on_sale?: boolean;\r\n }) => {\r\n return product.on_sale && product.sale_price\r\n ? product.sale_price\r\n : product.price;\r\n };\r\n\r\n const [paymentMethod, setPaymentMethod] = useState<PaymentMethod>(\r\n availablePaymentMethods[0]?.id || \"card\"\r\n );\r\n const [selectedProvider, setSelectedProvider] = useState<OnlinePaymentProvider>(\r\n availableProviders[0] || \"stripe\"\r\n );\r\n const [isSubmitting, setIsSubmitting] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const [formData, setFormData] = useState<CheckoutFormData>({\r\n firstName: \"\",\r\n lastName: \"\",\r\n email: \"\",\r\n phone: \"\",\r\n address: \"\",\r\n city: \"\",\r\n postalCode: \"\",\r\n country: \"\",\r\n notes: \"\",\r\n });\r\n const [agreedToTerms, setAgreedToTerms] = useState(false);\r\n\r\n // Bank transfer info state\r\n const [bankInfo, setBankInfo] = useState<BankTransferInfo | null>(null);\r\n const [isBankInfoLoading, setIsBankInfoLoading] = useState(false);\r\n const [bankInfoError, setBankInfoError] = useState<string | null>(null);\r\n\r\n const finalTotal = total + shipping + tax;\r\n\r\n // Fetch bank info when transfer is selected\r\n useEffect(() => {\r\n if (paymentMethod === \"transfer\") {\r\n const fetchBankInfo = async () => {\r\n setIsBankInfoLoading(true);\r\n setBankInfoError(null);\r\n try {\r\n const info = await customerClient.payment.getBankTransferInfo();\r\n setBankInfo(info);\r\n } catch (err: any) {\r\n setBankInfoError(\r\n err.message || t(\"bankInfoError\", \"Failed to load bank information\")\r\n );\r\n } finally {\r\n setIsBankInfoLoading(false);\r\n }\r\n };\r\n fetchBankInfo();\r\n }\r\n }, [paymentMethod, t]);\r\n\r\n const handleInputChange = (\r\n e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>\r\n ) => {\r\n const { name, value } = e.target;\r\n setFormData((prev) => ({ ...prev, [name]: value }));\r\n };\r\n\r\n const handleSubmit = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n if (!agreedToTerms) {\r\n toast.error(t(\"agreeToTermsError\", \"Please agree to the terms and conditions\"));\r\n return;\r\n }\r\n\r\n setIsSubmitting(true);\r\n setError(null);\r\n\r\n try {\r\n // Determine payment type based on selection\r\n let paymentType: \"stripe\" | \"iyzico\" | \"bank_transfer\" | \"cash_on_delivery\";\r\n\r\n if (paymentMethod === \"card\") {\r\n paymentType = selectedProvider;\r\n } else if (paymentMethod === \"transfer\") {\r\n paymentType = \"bank_transfer\";\r\n } else {\r\n paymentType = \"cash_on_delivery\";\r\n }\r\n\r\n // Save checkout data to localStorage for success page\r\n const checkoutData = {\r\n items: items,\r\n total: finalTotal,\r\n customerInfo: formData,\r\n paymentMethod,\r\n paymentProvider: paymentType,\r\n };\r\n localStorage.setItem(\"pending_checkout\", JSON.stringify(checkoutData));\r\n\r\n // Build product data for checkout\r\n const productData = items.map((item) => {\r\n const price = getProductPrice(item.product);\r\n const qty = item.quantity || 1;\r\n\r\n return {\r\n quantity: qty,\r\n name: item.product.name || \"Product\",\r\n description: item.product.description || item.product.name || \"Product\",\r\n amount: Math.round(price * 100), // Convert to cents\r\n img: item.product.images?.[0] || \"/images/placeholder.png\",\r\n optionals: {\r\n productId: item.product.id,\r\n },\r\n };\r\n });\r\n\r\n // Tax amount in cents\r\n const taxAmountInCents = tax && !isNaN(tax) ? Math.round(tax * 100) : undefined;\r\n\r\n // Create checkout session\r\n const response = await customerClient.payment.createCheckout({\r\n currency: currency.toLowerCase(),\r\n taxAmount: taxAmountInCents,\r\n paymentType: paymentType,\r\n db: \"local\",\r\n productData,\r\n contactData: {\r\n firstname: formData.firstName,\r\n lastname: formData.lastName,\r\n email: formData.email,\r\n phone: formData.phone,\r\n },\r\n shippingData: {\r\n address: formData.address,\r\n country: formData.country,\r\n city: formData.city,\r\n zip: formData.postalCode,\r\n },\r\n });\r\n\r\n // Clear cart and redirect to payment URL or confirmation page\r\n clearCart();\r\n if (response.url) {\r\n window.location.href = response.url;\r\n } else {\r\n window.location.href = `/order-confirmation?session_id=${response.sessionId}`;\r\n }\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(err, t(\"orderError\", \"Failed to place order. Please try again.\"));\r\n setError(errorMessage);\r\n toast.error(t(\"orderErrorTitle\", \"Order Failed\"), {\r\n description: errorMessage,\r\n });\r\n } finally {\r\n setIsSubmitting(false);\r\n }\r\n };\r\n\r\n // Get icon component based on payment method\r\n const getPaymentIcon = (iconName: string) => {\r\n switch (iconName) {\r\n case \"CreditCard\":\r\n return CreditCard;\r\n case \"Banknote\":\r\n return Banknote;\r\n case \"Truck\":\r\n return Truck;\r\n default:\r\n return CreditCard;\r\n }\r\n };\r\n\r\n // Get icon color based on payment method\r\n const getIconColor = (methodId: string) => {\r\n switch (methodId) {\r\n case \"card\":\r\n return \"text-blue-600\";\r\n case \"transfer\":\r\n return \"text-primary\";\r\n case \"cash\":\r\n return \"text-green-600 dark:text-green-400\";\r\n default:\r\n return \"text-primary\";\r\n }\r\n };\r\n\r\n if (items.length === 0) {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\r\n <div className=\"max-w-2xl mx-auto text-center\">\r\n <h1 className=\"text-3xl font-bold mb-4\">\r\n {t(\"cartEmpty\", \"Your cart is empty\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-8\">\r\n {t(\r\n \"cartEmptyDescription\",\r\n \"Please add items to your cart before proceeding to checkout.\"\r\n )}\r\n </p>\r\n <Button asChild>\r\n <Link to=\"/products\">\r\n {t(\"continueShopping\", \"Continue Shopping\")}\r\n </Link>\r\n </Button>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\r\n <FadeIn className=\"flex items-center gap-4 mb-8\">\r\n <Button variant=\"ghost\" size=\"icon\" asChild>\r\n <Link to=\"/cart\">\r\n <ArrowLeft className=\"h-4 w-4\" />\r\n </Link>\r\n </Button>\r\n <div>\r\n <h1 className=\"text-3xl font-bold\">{t(\"title\", \"Checkout\")}</h1>\r\n <p className=\"text-muted-foreground\">\r\n {t(\"completeOrder\", \"Complete your order\")}\r\n </p>\r\n </div>\r\n </FadeIn>\r\n\r\n <form onSubmit={handleSubmit}>\r\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-8\">\r\n <div className=\"lg:col-span-2 space-y-6\">\r\n {/* Contact Information */}\r\n <FadeIn delay={0.1}>\r\n <Card>\r\n <CardHeader>\r\n <CardTitle>\r\n {t(\"contactInformation\", \"Contact Information\")}\r\n </CardTitle>\r\n </CardHeader>\r\n <CardContent className=\"space-y-4\">\r\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\r\n <FormField label={t(\"firstName\", \"First Name\")} htmlFor=\"firstName\" required>\r\n <Input\r\n id=\"firstName\"\r\n name=\"firstName\"\r\n value={formData.firstName}\r\n onChange={handleInputChange}\r\n required\r\n />\r\n </FormField>\r\n <FormField label={t(\"lastName\", \"Last Name\")} htmlFor=\"lastName\" required>\r\n <Input\r\n id=\"lastName\"\r\n name=\"lastName\"\r\n value={formData.lastName}\r\n onChange={handleInputChange}\r\n required\r\n />\r\n </FormField>\r\n </div>\r\n <FormField label={t(\"email\", \"Email Address\")} htmlFor=\"email\" required>\r\n <Input\r\n id=\"email\"\r\n name=\"email\"\r\n type=\"email\"\r\n value={formData.email}\r\n onChange={handleInputChange}\r\n required\r\n />\r\n </FormField>\r\n <FormField label={t(\"phone\", \"Phone Number\")} htmlFor=\"phone\" required>\r\n <Input\r\n id=\"phone\"\r\n name=\"phone\"\r\n type=\"tel\"\r\n value={formData.phone}\r\n onChange={handleInputChange}\r\n required\r\n />\r\n </FormField>\r\n </CardContent>\r\n </Card>\r\n </FadeIn>\r\n\r\n {/* Shipping Address */}\r\n <FadeIn delay={0.2}>\r\n <Card>\r\n <CardHeader>\r\n <CardTitle>\r\n {t(\"shippingAddress\", \"Shipping Address\")}\r\n </CardTitle>\r\n </CardHeader>\r\n <CardContent className=\"space-y-4\">\r\n <div className=\"space-y-2\">\r\n <Label htmlFor=\"address\">{t(\"address\", \"Address\")} *</Label>\r\n <Textarea\r\n id=\"address\"\r\n name=\"address\"\r\n value={formData.address}\r\n onChange={handleInputChange}\r\n placeholder={t(\r\n \"addressPlaceholder\",\r\n \"Street address, apartment, suite, etc.\"\r\n )}\r\n required\r\n />\r\n </div>\r\n <FormField label={t(\"country\", \"Country\")} htmlFor=\"country\" required>\r\n <Select\r\n value={formData.country}\r\n onValueChange={(value) =>\r\n setFormData((prev) => ({ ...prev, country: value }))\r\n }\r\n required\r\n >\r\n <SelectTrigger id=\"country\">\r\n <SelectValue\r\n placeholder={t(\"selectCountry\", \"Select a country\")}\r\n />\r\n </SelectTrigger>\r\n <SelectContent>\r\n {countries.map((country) => (\r\n <SelectItem key={country.value} value={country.value}>\r\n {country.label}\r\n </SelectItem>\r\n ))}\r\n </SelectContent>\r\n </Select>\r\n </FormField>\r\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\r\n <FormField label={t(\"city\", \"City\")} htmlFor=\"city\" required>\r\n <Input\r\n id=\"city\"\r\n name=\"city\"\r\n value={formData.city}\r\n onChange={handleInputChange}\r\n required\r\n />\r\n </FormField>\r\n <FormField label={t(\"postalCode\", \"Postal Code\")} htmlFor=\"postalCode\" required>\r\n <Input\r\n id=\"postalCode\"\r\n name=\"postalCode\"\r\n value={formData.postalCode}\r\n onChange={handleInputChange}\r\n required\r\n />\r\n </FormField>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n </FadeIn>\r\n\r\n {/* Payment Method */}\r\n <FadeIn delay={0.3}>\r\n <Card>\r\n <CardHeader>\r\n <CardTitle>{t(\"paymentMethod\", \"Payment Method\")}</CardTitle>\r\n </CardHeader>\r\n <CardContent>\r\n <RadioGroup\r\n value={paymentMethod}\r\n onValueChange={(value) =>\r\n setPaymentMethod(value as PaymentMethod)\r\n }\r\n className=\"space-y-4\"\r\n >\r\n {availablePaymentMethods.map((method) => {\r\n const IconComponent = getPaymentIcon(method.icon);\r\n const iconColor = getIconColor(method.id);\r\n\r\n return (\r\n <div\r\n key={method.id}\r\n className=\"flex items-center space-x-2 p-4 border rounded-lg\"\r\n >\r\n <RadioGroupItem value={method.id} id={method.id} />\r\n <Label\r\n htmlFor={method.id}\r\n className=\"flex-1 cursor-pointer\"\r\n >\r\n <div className=\"flex items-center gap-3\">\r\n <IconComponent\r\n className={`h-5 w-5 ${iconColor}`}\r\n />\r\n <div>\r\n <div className=\"font-medium\">\r\n {t(method.id, method.label)}\r\n </div>\r\n <div className=\"text-sm text-muted-foreground\">\r\n {t(`${method.id}Description`, method.description)}\r\n </div>\r\n </div>\r\n </div>\r\n </Label>\r\n </div>\r\n );\r\n })}\r\n </RadioGroup>\r\n\r\n {/* Bank Transfer Details */}\r\n {paymentMethod === \"transfer\" && (\r\n <div className=\"mt-4 p-4 bg-primary/10 rounded-lg border border-primary/20\">\r\n <h4 className=\"font-medium mb-2\">\r\n {t(\"bankTransferDetailsTitle\", \"Bank Transfer Details\")}:\r\n </h4>\r\n {isBankInfoLoading ? (\r\n <div className=\"text-sm space-y-2\">\r\n <Skeleton className=\"h-4 w-full\" />\r\n <Skeleton className=\"h-4 w-3/4\" />\r\n <Skeleton className=\"h-4 w-full\" />\r\n </div>\r\n ) : bankInfoError ? (\r\n <p className=\"text-sm text-red-600\">{bankInfoError}</p>\r\n ) : bankInfo ? (\r\n <div className=\"text-sm space-y-1\">\r\n <p>\r\n <strong>{t(\"bank\", \"Bank\")}:</strong> {bankInfo.bank_name}\r\n </p>\r\n <p>\r\n <strong>{t(\"accountName\", \"Account Name\")}:</strong>{\" \"}\r\n {bankInfo.bank_account_name}\r\n </p>\r\n <p>\r\n <strong>IBAN:</strong> {bankInfo.iban}\r\n </p>\r\n </div>\r\n ) : (\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"bankInfoNotAvailable\", \"Bank account information not available\")}\r\n </p>\r\n )}\r\n </div>\r\n )}\r\n\r\n {/* Card Payment - Provider Selection */}\r\n {paymentMethod === \"card\" && availableProviders.length > 1 && (\r\n <div className=\"mt-4 space-y-4\">\r\n <div className=\"p-4 bg-blue-50 dark:bg-blue-950/30 rounded-lg border border-blue-200 dark:border-blue-800\">\r\n <h4 className=\"font-medium text-blue-900 dark:text-blue-100 mb-3\">\r\n {t(\"selectPaymentProvider\", \"Select Payment Provider\")}\r\n </h4>\r\n <RadioGroup\r\n value={selectedProvider}\r\n onValueChange={(value) =>\r\n setSelectedProvider(value as OnlinePaymentProvider)\r\n }\r\n className=\"space-y-2\"\r\n >\r\n {availableProviders.map((provider) => (\r\n <div\r\n key={provider}\r\n className=\"flex items-center space-x-2 p-3 bg-background rounded border\"\r\n >\r\n <RadioGroupItem\r\n value={provider}\r\n id={`provider-${provider}`}\r\n />\r\n <Label\r\n htmlFor={`provider-${provider}`}\r\n className=\"flex-1 cursor-pointer\"\r\n >\r\n <div className=\"font-medium\">\r\n {t(`provider_${provider}_label`, ONLINE_PROVIDER_CONFIGS[provider].label)}\r\n </div>\r\n <div className=\"text-xs text-muted-foreground\">\r\n {t(`provider_${provider}_description`, ONLINE_PROVIDER_CONFIGS[provider].description)}\r\n </div>\r\n </Label>\r\n </div>\r\n ))}\r\n </RadioGroup>\r\n <p className=\"text-sm text-blue-700 dark:text-blue-300 mt-3\">\r\n {t(\r\n \"creditCardRedirectNote\",\r\n \"You will be redirected to the secure payment page to complete your purchase.\"\r\n )}\r\n </p>\r\n </div>\r\n </div>\r\n )}\r\n </CardContent>\r\n </Card>\r\n </FadeIn>\r\n\r\n {/* Order Notes */}\r\n <FadeIn delay={0.4}>\r\n <Card>\r\n <CardHeader>\r\n <CardTitle>\r\n {t(\"orderNotesOptional\", \"Order Notes (Optional)\")}\r\n </CardTitle>\r\n </CardHeader>\r\n <CardContent>\r\n <Textarea\r\n name=\"notes\"\r\n value={formData.notes}\r\n onChange={handleInputChange}\r\n placeholder={t(\r\n \"orderNotesPlaceholder\",\r\n \"Special instructions for your order...\"\r\n )}\r\n rows={3}\r\n />\r\n </CardContent>\r\n </Card>\r\n </FadeIn>\r\n </div>\r\n\r\n {/* Order Summary */}\r\n <FadeIn delay={0.2} className=\"lg:col-span-1\">\r\n <Card className=\"sticky top-24\">\r\n <CardHeader>\r\n <CardTitle>{t(\"orderSummary\", \"Order Summary\")}</CardTitle>\r\n </CardHeader>\r\n <CardContent className=\"space-y-4\">\r\n <div className=\"space-y-3\">\r\n {items.map((item) => (\r\n <div key={item.id} className=\"flex gap-3\">\r\n <img\r\n src={\r\n item.product.images?.[0] ||\r\n \"/images/placeholder.png\"\r\n }\r\n alt={item.product.name}\r\n className=\"w-12 h-12 object-cover rounded\"\r\n />\r\n <div className=\"flex-1 space-y-1\">\r\n <h4 className=\"text-sm font-medium leading-normal\">\r\n {item.product.name}\r\n </h4>\r\n <div className=\"flex justify-between text-sm\">\r\n <span className=\"text-muted-foreground\">\r\n {t(\"qty\", \"Qty\")}: {item.quantity}\r\n </span>\r\n <span>\r\n {formatPrice(\r\n getProductPrice(item.product) * item.quantity,\r\n currency\r\n )}\r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n ))}\r\n </div>\r\n\r\n <Separator />\r\n\r\n <div className=\"space-y-2\">\r\n <div className=\"flex justify-between\">\r\n <span>{t(\"subtotal\", \"Subtotal\")}</span>\r\n <span>{formatPrice(total, currency)}</span>\r\n </div>\r\n <div className=\"flex justify-between\">\r\n <span>{t(\"shipping\", \"Shipping\")}</span>\r\n <span>\r\n {shipping === 0\r\n ? t(\"free\", \"Free\")\r\n : formatPrice(shipping, currency)}\r\n </span>\r\n </div>\r\n <div className=\"flex justify-between\">\r\n <span>{t(\"tax\", \"Tax\")}</span>\r\n <span>{formatPrice(tax, currency)}</span>\r\n </div>\r\n </div>\r\n\r\n <Separator />\r\n\r\n <div className=\"flex justify-between text-lg font-semibold\">\r\n <span>{t(\"total\", \"Total\")}</span>\r\n <span>{formatPrice(finalTotal, currency)}</span>\r\n </div>\r\n\r\n {error && (\r\n <div className=\"p-4 bg-red-50 dark:bg-red-950 border border-red-200 dark:border-red-800 rounded-lg\">\r\n <p className=\"text-red-800 dark:text-red-200 text-sm font-medium\">\r\n {error}\r\n </p>\r\n </div>\r\n )}\r\n\r\n <div className=\"flex items-center gap-2\">\r\n <Checkbox\r\n id=\"terms\"\r\n checked={agreedToTerms}\r\n onCheckedChange={(checked) =>\r\n setAgreedToTerms(checked as boolean)\r\n }\r\n />\r\n <span className=\"text-sm\">\r\n {t(\"agreeToTermsTextBefore\", \"I agree to the\")}{\" \"}\r\n <Link\r\n to=\"/terms\"\r\n className=\"text-primary hover:underline\"\r\n >\r\n {t(\"termsOfService\", \"Terms of Service\")}\r\n </Link>{\" \"}\r\n {t(\"and\", \"and\")}{\" \"}\r\n <Link\r\n to=\"/privacy\"\r\n className=\"text-primary hover:underline\"\r\n >\r\n {t(\"privacyPolicy\", \"Privacy Policy\")}\r\n </Link>\r\n </span>\r\n </div>\r\n\r\n <Button\r\n type=\"submit\"\r\n className=\"w-full\"\r\n size=\"lg\"\r\n disabled={!agreedToTerms || isSubmitting}\r\n >\r\n {isSubmitting ? (\r\n <>\r\n <div className=\"w-4 h-4 border-2 border-primary-foreground border-t-transparent rounded-full animate-spin mr-2\" />\r\n {t(\"processing\", \"Processing...\")}\r\n </>\r\n ) : (\r\n <>\r\n <Check className=\"w-4 h-4 mr-2\" />\r\n {paymentMethod === \"card\"\r\n ? t(\"proceedToPayment\", \"Proceed to Payment\")\r\n : t(\"placeOrder\", \"Place Order\")}\r\n </>\r\n )}\r\n </Button>\r\n </CardContent>\r\n </Card>\r\n </FadeIn>\r\n </div>\r\n </form>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default CheckoutPage;\r\n"
27
+ "content": "import { useState, useEffect } from \"react\";\nimport { Link } from \"react-router\";\nimport { ArrowLeft, CreditCard, Banknote, Truck, Check } from \"lucide-react\";\nimport { Layout } from \"@/components/Layout\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { RadioGroup, RadioGroupItem } from \"@/components/ui/radio-group\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport { useTranslation } from \"react-i18next\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { toast } from \"sonner\";\nimport {\n useCart,\n formatPrice,\n type PaymentMethod,\n type OnlinePaymentProvider,\n getFilteredPaymentMethodConfigs,\n getOnlinePaymentProviders,\n ONLINE_PROVIDER_CONFIGS,\n} from \"@/modules/ecommerce-core\";\nimport { customerClient, getErrorMessage } from \"@/modules/api\";\nimport constants from \"@/constants/constants.json\";\nimport { FadeIn } from \"@/modules/animations\";\nimport { FormField } from \"@/components/FormField\";\n\ninterface Country {\n value: string;\n label: string;\n}\n\ninterface CheckoutFormData {\n firstName: string;\n lastName: string;\n email: string;\n phone: string;\n address: string;\n city: string;\n postalCode: string;\n country: string;\n notes: string;\n}\n\ninterface BankTransferInfo {\n bank_name: string;\n bank_account_name: string;\n iban: string;\n}\n\nconst DEFAULT_COUNTRIES: Country[] = [\n { value: \"US\", label: \"United States\" },\n { value: \"GB\", label: \"United Kingdom\" },\n { value: \"CA\", label: \"Canada\" },\n { value: \"AU\", label: \"Australia\" },\n { value: \"DE\", label: \"Germany\" },\n { value: \"FR\", label: \"France\" },\n { value: \"IT\", label: \"Italy\" },\n { value: \"ES\", label: \"Spain\" },\n { value: \"NL\", label: \"Netherlands\" },\n { value: \"TR\", label: \"Turkey\" },\n { value: \"JP\", label: \"Japan\" },\n];\n\nexport function CheckoutPage() {\n const { t } = useTranslation(\"checkout-page\");\n usePageTitle({ title: t(\"pageTitle\", \"Checkout\") });\n const { state, clearCart } = useCart();\n const { items, total } = state;\n\n const currency = (constants as any).site?.currency || \"USD\";\n const taxRate = (constants as any).payments?.taxRate || 0;\n const freeShippingThreshold = (constants as any).payments?.freeShippingThreshold || 0;\n const shippingCost = (constants as any).shipping?.domesticShipping?.standard?.cost || 0;\n\n // Calculate shipping and tax\n const shipping = total >= freeShippingThreshold ? 0 : shippingCost;\n const tax = total * taxRate;\n\n const countries = DEFAULT_COUNTRIES;\n\n // Get available payment methods and providers from config\n const availablePaymentMethods = getFilteredPaymentMethodConfigs();\n const availableProviders = getOnlinePaymentProviders();\n\n const getProductPrice = (product: {\n price: number;\n sale_price?: number;\n on_sale?: boolean;\n }) => {\n return product.on_sale && product.sale_price\n ? product.sale_price\n : product.price;\n };\n\n const [paymentMethod, setPaymentMethod] = useState<PaymentMethod>(\n availablePaymentMethods[0]?.id || \"card\"\n );\n const [selectedProvider, setSelectedProvider] = useState<OnlinePaymentProvider>(\n availableProviders[0] || \"stripe\"\n );\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [formData, setFormData] = useState<CheckoutFormData>({\n firstName: \"\",\n lastName: \"\",\n email: \"\",\n phone: \"\",\n address: \"\",\n city: \"\",\n postalCode: \"\",\n country: \"\",\n notes: \"\",\n });\n const [agreedToTerms, setAgreedToTerms] = useState(false);\n\n // Bank transfer info state\n const [bankInfo, setBankInfo] = useState<BankTransferInfo | null>(null);\n const [isBankInfoLoading, setIsBankInfoLoading] = useState(false);\n const [bankInfoError, setBankInfoError] = useState<string | null>(null);\n\n const finalTotal = total + shipping + tax;\n\n // Fetch bank info when transfer is selected\n useEffect(() => {\n if (paymentMethod === \"transfer\") {\n const fetchBankInfo = async () => {\n setIsBankInfoLoading(true);\n setBankInfoError(null);\n try {\n const info = await customerClient.payment.getBankTransferInfo();\n setBankInfo(info);\n } catch (err: any) {\n setBankInfoError(\n err.message || t(\"bankInfoError\", \"Failed to load bank information\")\n );\n } finally {\n setIsBankInfoLoading(false);\n }\n };\n fetchBankInfo();\n }\n }, [paymentMethod, t]);\n\n const handleInputChange = (\n e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>\n ) => {\n const { name, value } = e.target;\n setFormData((prev) => ({ ...prev, [name]: value }));\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!agreedToTerms) {\n toast.error(t(\"agreeToTermsError\", \"Please agree to the terms and conditions\"));\n return;\n }\n\n setIsSubmitting(true);\n setError(null);\n\n try {\n // Determine payment type based on selection\n let paymentType: \"stripe\" | \"iyzico\" | \"bank_transfer\" | \"cash_on_delivery\";\n\n if (paymentMethod === \"card\") {\n paymentType = selectedProvider;\n } else if (paymentMethod === \"transfer\") {\n paymentType = \"bank_transfer\";\n } else {\n paymentType = \"cash_on_delivery\";\n }\n\n // Save checkout data to localStorage for success page\n const checkoutData = {\n items: items,\n total: finalTotal,\n customerInfo: formData,\n paymentMethod,\n paymentProvider: paymentType,\n };\n localStorage.setItem(\"pending_checkout\", JSON.stringify(checkoutData));\n\n // Build product data for checkout\n const productData = items.map((item) => {\n const price = getProductPrice(item.product);\n const qty = item.quantity || 1;\n\n return {\n quantity: qty,\n name: item.product.name || \"Product\",\n description: item.product.description || item.product.name || \"Product\",\n amount: Math.round(price * 100), // Convert to cents\n img: item.product.images?.[0] || \"/images/placeholder.png\",\n optionals: {\n productId: item.product.id,\n },\n };\n });\n\n // Tax amount in cents\n const taxAmountInCents = tax && !isNaN(tax) ? Math.round(tax * 100) : undefined;\n\n // Create checkout session\n const response = await customerClient.payment.createCheckout({\n currency: currency.toLowerCase(),\n taxAmount: taxAmountInCents,\n paymentType: paymentType,\n db: \"local\",\n productData,\n contactData: {\n firstname: formData.firstName,\n lastname: formData.lastName,\n email: formData.email,\n phone: formData.phone,\n },\n shippingData: {\n address: formData.address,\n country: formData.country,\n city: formData.city,\n zip: formData.postalCode,\n },\n });\n\n // Clear cart and redirect to payment URL or confirmation page\n clearCart();\n if (response.url) {\n window.location.href = response.url;\n } else {\n window.location.href = `/order-confirmation?session_id=${response.sessionId}`;\n }\n } catch (err) {\n const errorMessage = getErrorMessage(err, t(\"orderError\", \"Failed to place order. Please try again.\"));\n setError(errorMessage);\n toast.error(t(\"orderErrorTitle\", \"Order Failed\"), {\n description: errorMessage,\n });\n } finally {\n setIsSubmitting(false);\n }\n };\n\n // Get icon component based on payment method\n const getPaymentIcon = (iconName: string) => {\n switch (iconName) {\n case \"CreditCard\":\n return CreditCard;\n case \"Banknote\":\n return Banknote;\n case \"Truck\":\n return Truck;\n default:\n return CreditCard;\n }\n };\n\n // Get icon color based on payment method\n const getIconColor = (methodId: string) => {\n switch (methodId) {\n case \"card\":\n return \"text-blue-600\";\n case \"transfer\":\n return \"text-primary\";\n case \"cash\":\n return \"text-green-600 dark:text-green-400\";\n default:\n return \"text-primary\";\n }\n };\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\">\n <h1 className=\"text-3xl font-bold mb-4\">\n {t(\"cartEmpty\", \"Your cart is empty\")}\n </h1>\n <p className=\"text-muted-foreground mb-8\">\n {t(\n \"cartEmptyDescription\",\n \"Please add items to your cart before proceeding to checkout.\"\n )}\n </p>\n <Button asChild>\n <Link to=\"/products\">\n {t(\"continueShopping\", \"Continue Shopping\")}\n </Link>\n </Button>\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=\"/cart\">\n <ArrowLeft className=\"h-4 w-4\" />\n </Link>\n </Button>\n <div>\n <h1 className=\"text-3xl font-bold\">{t(\"title\", \"Checkout\")}</h1>\n <p className=\"text-muted-foreground\">\n {t(\"completeOrder\", \"Complete your order\")}\n </p>\n </div>\n </FadeIn>\n\n <form onSubmit={handleSubmit}>\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-8\">\n <div className=\"lg:col-span-2 space-y-6\">\n {/* Contact Information */}\n <FadeIn delay={0.1}>\n <Card>\n <CardHeader>\n <CardTitle>\n {t(\"contactInformation\", \"Contact Information\")}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <FormField label={t(\"firstName\", \"First Name\")} htmlFor=\"firstName\" required>\n <Input\n id=\"firstName\"\n name=\"firstName\"\n value={formData.firstName}\n onChange={handleInputChange}\n required\n />\n </FormField>\n <FormField label={t(\"lastName\", \"Last Name\")} htmlFor=\"lastName\" required>\n <Input\n id=\"lastName\"\n name=\"lastName\"\n value={formData.lastName}\n onChange={handleInputChange}\n required\n />\n </FormField>\n </div>\n <FormField label={t(\"email\", \"Email Address\")} htmlFor=\"email\" required>\n <Input\n id=\"email\"\n name=\"email\"\n type=\"email\"\n value={formData.email}\n onChange={handleInputChange}\n required\n />\n </FormField>\n <FormField label={t(\"phone\", \"Phone Number\")} htmlFor=\"phone\" required>\n <Input\n id=\"phone\"\n name=\"phone\"\n type=\"tel\"\n value={formData.phone}\n onChange={handleInputChange}\n required\n />\n </FormField>\n </CardContent>\n </Card>\n </FadeIn>\n\n {/* Shipping Address */}\n <FadeIn delay={0.2}>\n <Card>\n <CardHeader>\n <CardTitle>\n {t(\"shippingAddress\", \"Shipping Address\")}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"address\">{t(\"address\", \"Address\")} *</Label>\n <Textarea\n id=\"address\"\n name=\"address\"\n value={formData.address}\n onChange={handleInputChange}\n placeholder={t(\n \"addressPlaceholder\",\n \"Street address, apartment, suite, etc.\"\n )}\n required\n />\n </div>\n <FormField label={t(\"country\", \"Country\")} htmlFor=\"country\" required>\n <Select\n value={formData.country}\n onValueChange={(value) =>\n setFormData((prev) => ({ ...prev, country: value }))\n }\n required\n >\n <SelectTrigger id=\"country\">\n <SelectValue\n placeholder={t(\"selectCountry\", \"Select a country\")}\n />\n </SelectTrigger>\n <SelectContent>\n {countries.map((country) => (\n <SelectItem key={country.value} value={country.value}>\n {country.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </FormField>\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <FormField label={t(\"city\", \"City\")} htmlFor=\"city\" required>\n <Input\n id=\"city\"\n name=\"city\"\n value={formData.city}\n onChange={handleInputChange}\n required\n />\n </FormField>\n <FormField label={t(\"postalCode\", \"Postal Code\")} htmlFor=\"postalCode\" required>\n <Input\n id=\"postalCode\"\n name=\"postalCode\"\n value={formData.postalCode}\n onChange={handleInputChange}\n required\n />\n </FormField>\n </div>\n </CardContent>\n </Card>\n </FadeIn>\n\n {/* Payment Method */}\n <FadeIn delay={0.3}>\n <Card>\n <CardHeader>\n <CardTitle>{t(\"paymentMethod\", \"Payment Method\")}</CardTitle>\n </CardHeader>\n <CardContent>\n <RadioGroup\n value={paymentMethod}\n onValueChange={(value) =>\n setPaymentMethod(value as PaymentMethod)\n }\n className=\"space-y-4\"\n >\n {availablePaymentMethods.map((method) => {\n const IconComponent = getPaymentIcon(method.icon);\n const iconColor = getIconColor(method.id);\n\n return (\n <div\n key={method.id}\n className=\"flex items-center space-x-2 p-4 border rounded-lg\"\n >\n <RadioGroupItem value={method.id} id={method.id} />\n <Label\n htmlFor={method.id}\n className=\"flex-1 cursor-pointer\"\n >\n <div className=\"flex items-center gap-3\">\n <IconComponent\n className={`h-5 w-5 ${iconColor}`}\n />\n <div>\n <div className=\"font-medium\">\n {t(method.id, method.label)}\n </div>\n <div className=\"text-sm text-muted-foreground\">\n {t(`${method.id}Description`, method.description)}\n </div>\n </div>\n </div>\n </Label>\n </div>\n );\n })}\n </RadioGroup>\n\n {/* Bank Transfer Details */}\n {paymentMethod === \"transfer\" && (\n <div className=\"mt-4 p-4 bg-primary/10 rounded-lg border border-primary/20\">\n <h4 className=\"font-medium mb-2\">\n {t(\"bankTransferDetailsTitle\", \"Bank Transfer Details\")}:\n </h4>\n {isBankInfoLoading ? (\n <div className=\"text-sm space-y-2\">\n <Skeleton className=\"h-4 w-full\" />\n <Skeleton className=\"h-4 w-3/4\" />\n <Skeleton className=\"h-4 w-full\" />\n </div>\n ) : bankInfoError ? (\n <p className=\"text-sm text-red-600\">{bankInfoError}</p>\n ) : bankInfo ? (\n <div className=\"text-sm space-y-1\">\n <p>\n <strong>{t(\"bank\", \"Bank\")}:</strong> {bankInfo.bank_name}\n </p>\n <p>\n <strong>{t(\"accountName\", \"Account Name\")}:</strong>{\" \"}\n {bankInfo.bank_account_name}\n </p>\n <p>\n <strong>IBAN:</strong> {bankInfo.iban}\n </p>\n </div>\n ) : (\n <p className=\"text-sm text-muted-foreground\">\n {t(\"bankInfoNotAvailable\", \"Bank account information not available\")}\n </p>\n )}\n </div>\n )}\n\n {/* Card Payment - Provider Selection */}\n {paymentMethod === \"card\" && availableProviders.length > 1 && (\n <div className=\"mt-4 space-y-4\">\n <div className=\"p-4 bg-blue-50 dark:bg-blue-950/30 rounded-lg border border-blue-200 dark:border-blue-800\">\n <h4 className=\"font-medium text-blue-900 dark:text-blue-100 mb-3\">\n {t(\"selectPaymentProvider\", \"Select Payment Provider\")}\n </h4>\n <RadioGroup\n value={selectedProvider}\n onValueChange={(value) =>\n setSelectedProvider(value as OnlinePaymentProvider)\n }\n className=\"space-y-2\"\n >\n {availableProviders.map((provider) => (\n <div\n key={provider}\n className=\"flex items-center space-x-2 p-3 bg-background rounded border\"\n >\n <RadioGroupItem\n value={provider}\n id={`provider-${provider}`}\n />\n <Label\n htmlFor={`provider-${provider}`}\n className=\"flex-1 cursor-pointer\"\n >\n <div className=\"font-medium\">\n {t(`provider_${provider}_label`, ONLINE_PROVIDER_CONFIGS[provider].label)}\n </div>\n <div className=\"text-xs text-muted-foreground\">\n {t(`provider_${provider}_description`, ONLINE_PROVIDER_CONFIGS[provider].description)}\n </div>\n </Label>\n </div>\n ))}\n </RadioGroup>\n <p className=\"text-sm text-blue-700 dark:text-blue-300 mt-3\">\n {t(\n \"creditCardRedirectNote\",\n \"You will be redirected to the secure payment page to complete your purchase.\"\n )}\n </p>\n </div>\n </div>\n )}\n </CardContent>\n </Card>\n </FadeIn>\n\n {/* Order Notes */}\n <FadeIn delay={0.4}>\n <Card>\n <CardHeader>\n <CardTitle>\n {t(\"orderNotesOptional\", \"Order Notes (Optional)\")}\n </CardTitle>\n </CardHeader>\n <CardContent>\n <Textarea\n name=\"notes\"\n value={formData.notes}\n onChange={handleInputChange}\n placeholder={t(\n \"orderNotesPlaceholder\",\n \"Special instructions for your order...\"\n )}\n rows={3}\n />\n </CardContent>\n </Card>\n </FadeIn>\n </div>\n\n {/* Order Summary */}\n <FadeIn delay={0.2} className=\"lg:col-span-1\">\n <Card className=\"sticky top-24\">\n <CardHeader>\n <CardTitle>{t(\"orderSummary\", \"Order Summary\")}</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"space-y-3\">\n {items.map((item) => (\n <div key={item.id} className=\"flex gap-3\">\n <img\n src={\n item.product.images?.[0] ||\n \"/images/placeholder.png\"\n }\n alt={item.product.name}\n className=\"w-12 h-12 object-cover rounded\"\n />\n <div className=\"flex-1 space-y-1\">\n <h4 className=\"text-sm font-medium leading-normal\">\n {item.product.name}\n </h4>\n <div className=\"flex justify-between text-sm\">\n <span className=\"text-muted-foreground\">\n {t(\"qty\", \"Qty\")}: {item.quantity}\n </span>\n <span>\n {formatPrice(\n getProductPrice(item.product) * item.quantity,\n currency\n )}\n </span>\n </div>\n </div>\n </div>\n ))}\n </div>\n\n <Separator />\n\n <div className=\"space-y-2\">\n <div className=\"flex justify-between\">\n <span>{t(\"subtotal\", \"Subtotal\")}</span>\n <span>{formatPrice(total, currency)}</span>\n </div>\n <div className=\"flex justify-between\">\n <span>{t(\"shipping\", \"Shipping\")}</span>\n <span>\n {shipping === 0\n ? t(\"free\", \"Free\")\n : formatPrice(shipping, currency)}\n </span>\n </div>\n <div className=\"flex justify-between\">\n <span>{t(\"tax\", \"Tax\")}</span>\n <span>{formatPrice(tax, currency)}</span>\n </div>\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 {error && (\n <div className=\"p-4 bg-red-50 dark:bg-red-950 border border-red-200 dark:border-red-800 rounded-lg\">\n <p className=\"text-red-800 dark:text-red-200 text-sm font-medium\">\n {error}\n </p>\n </div>\n )}\n\n <div className=\"flex items-center gap-2\">\n <Checkbox\n id=\"terms\"\n checked={agreedToTerms}\n onCheckedChange={(checked) =>\n setAgreedToTerms(checked as boolean)\n }\n />\n <span className=\"text-sm\">\n {t(\"agreeToTermsTextBefore\", \"I agree to the\")}{\" \"}\n <Link\n to=\"/terms\"\n className=\"text-primary hover:underline\"\n >\n {t(\"termsOfService\", \"Terms of Service\")}\n </Link>{\" \"}\n {t(\"and\", \"and\")}{\" \"}\n <Link\n to=\"/privacy\"\n className=\"text-primary hover:underline\"\n >\n {t(\"privacyPolicy\", \"Privacy Policy\")}\n </Link>\n </span>\n </div>\n\n <Button\n type=\"submit\"\n className=\"w-full\"\n size=\"lg\"\n disabled={!agreedToTerms || isSubmitting}\n >\n {isSubmitting ? (\n <>\n <div className=\"w-4 h-4 border-2 border-primary-foreground border-t-transparent rounded-full animate-spin mr-2\" />\n {t(\"processing\", \"Processing...\")}\n </>\n ) : (\n <>\n <Check className=\"w-4 h-4 mr-2\" />\n {paymentMethod === \"card\"\n ? t(\"proceedToPayment\", \"Proceed to Payment\")\n : t(\"placeOrder\", \"Place Order\")}\n </>\n )}\n </Button>\n </CardContent>\n </Card>\n </FadeIn>\n </div>\n </form>\n </div>\n </Layout>\n );\n}\n\nexport default CheckoutPage;\n"
28
28
  },
29
29
  {
30
30
  "path": "checkout-page/lang/en.json",
31
31
  "type": "registry:lang",
32
32
  "target": "$modules$/checkout-page/lang/en.json",
33
- "content": "{\r\n \"pageTitle\": \"Checkout\",\r\n \"title\": \"Checkout\",\r\n \"completeOrder\": \"Complete your order\",\r\n \"contactInformation\": \"Contact Information\",\r\n \"shippingAddress\": \"Shipping Address\",\r\n \"paymentMethod\": \"Payment Method\",\r\n \"orderSummary\": \"Order Summary\",\r\n \"placeOrder\": \"Place Order\",\r\n \"processing\": \"Processing...\",\r\n \"firstName\": \"First Name\",\r\n \"lastName\": \"Last Name\",\r\n \"email\": \"Email Address\",\r\n \"phone\": \"Phone Number\",\r\n \"address\": \"Address\",\r\n \"city\": \"City\",\r\n \"postalCode\": \"Postal Code\",\r\n \"country\": \"Country\",\r\n \"selectCountry\": \"Select a country\",\r\n \"qty\": \"Qty\",\r\n \"subtotal\": \"Subtotal\",\r\n \"shipping\": \"Shipping\",\r\n \"free\": \"Free\",\r\n \"tax\": \"Tax\",\r\n \"total\": \"Total\",\r\n \"and\": \"and\",\r\n \"termsOfService\": \"Terms of Service\",\r\n \"privacyPolicy\": \"Privacy Policy\",\r\n \"addressPlaceholder\": \"Street address, apartment, suite, etc.\",\r\n \"orderNotesOptional\": \"Order Notes (Optional)\",\r\n \"orderNotesPlaceholder\": \"Special instructions for your order...\",\r\n \"agreeToTermsTextBefore\": \"I agree to the\",\r\n \"agreeToTermsError\": \"Please agree to the terms and conditions\",\r\n \"card\": \"Credit/Debit Card\",\r\n \"transfer\": \"Bank Transfer\",\r\n \"cash\": \"Cash on Delivery\",\r\n \"cardDescription\": \"Pay securely with your credit or debit card\",\r\n \"transferDescription\": \"Transfer payment to our bank account\",\r\n \"cashDescription\": \"Pay when your order arrives at your doorstep\",\r\n \"proceedToPayment\": \"Proceed to Payment\",\r\n \"cartEmpty\": \"Your cart is empty\",\r\n \"cartEmptyDescription\": \"Please add items to your cart before proceeding to checkout.\",\r\n \"continueShopping\": \"Continue Shopping\",\r\n \"bankInfoError\": \"Failed to load bank information\",\r\n \"orderError\": \"Failed to place order. Please try again.\",\r\n \"orderErrorTitle\": \"Order Failed\",\r\n \"bankTransferDetailsTitle\": \"Bank Transfer Details\",\r\n \"bank\": \"Bank\",\r\n \"accountName\": \"Account Name\",\r\n \"bankInfoNotAvailable\": \"Bank account information not available\",\r\n \"selectPaymentProvider\": \"Select Payment Provider\",\r\n \"creditCardRedirectNote\": \"You will be redirected to the secure payment page to complete your purchase.\"\r\n}\r\n"
33
+ "content": "{\n \"pageTitle\": \"Checkout\",\n \"title\": \"Checkout\",\n \"completeOrder\": \"Complete your order\",\n \"contactInformation\": \"Contact Information\",\n \"shippingAddress\": \"Shipping Address\",\n \"paymentMethod\": \"Payment Method\",\n \"orderSummary\": \"Order Summary\",\n \"placeOrder\": \"Place Order\",\n \"processing\": \"Processing...\",\n \"firstName\": \"First Name\",\n \"lastName\": \"Last Name\",\n \"email\": \"Email Address\",\n \"phone\": \"Phone Number\",\n \"address\": \"Address\",\n \"city\": \"City\",\n \"postalCode\": \"Postal Code\",\n \"country\": \"Country\",\n \"selectCountry\": \"Select a country\",\n \"qty\": \"Qty\",\n \"subtotal\": \"Subtotal\",\n \"shipping\": \"Shipping\",\n \"free\": \"Free\",\n \"tax\": \"Tax\",\n \"total\": \"Total\",\n \"and\": \"and\",\n \"termsOfService\": \"Terms of Service\",\n \"privacyPolicy\": \"Privacy Policy\",\n \"addressPlaceholder\": \"Street address, apartment, suite, etc.\",\n \"orderNotesOptional\": \"Order Notes (Optional)\",\n \"orderNotesPlaceholder\": \"Special instructions for your order...\",\n \"agreeToTermsTextBefore\": \"I agree to the\",\n \"agreeToTermsError\": \"Please agree to the terms and conditions\",\n \"card\": \"Credit/Debit Card\",\n \"transfer\": \"Bank Transfer\",\n \"cash\": \"Cash on Delivery\",\n \"cardDescription\": \"Pay securely with your credit or debit card\",\n \"transferDescription\": \"Transfer payment to our bank account\",\n \"cashDescription\": \"Pay when your order arrives at your doorstep\",\n \"proceedToPayment\": \"Proceed to Payment\",\n \"cartEmpty\": \"Your cart is empty\",\n \"cartEmptyDescription\": \"Please add items to your cart before proceeding to checkout.\",\n \"continueShopping\": \"Continue Shopping\",\n \"bankInfoError\": \"Failed to load bank information\",\n \"orderError\": \"Failed to place order. Please try again.\",\n \"orderErrorTitle\": \"Order Failed\",\n \"bankTransferDetailsTitle\": \"Bank Transfer Details\",\n \"bank\": \"Bank\",\n \"accountName\": \"Account Name\",\n \"bankInfoNotAvailable\": \"Bank account information not available\",\n \"selectPaymentProvider\": \"Select Payment Provider\",\n \"creditCardRedirectNote\": \"You will be redirected to the secure payment page to complete your purchase.\"\n}\n"
34
34
  },
35
35
  {
36
36
  "path": "checkout-page/lang/tr.json",
37
37
  "type": "registry:lang",
38
38
  "target": "$modules$/checkout-page/lang/tr.json",
39
- "content": "{\r\n \"pageTitle\": \"Ödeme\",\r\n \"title\": \"Ödeme\",\r\n \"completeOrder\": \"Siparişinizi tamamlayın\",\r\n \"contactInformation\": \"İletişim Bilgileri\",\r\n \"shippingAddress\": \"Teslimat Adresi\",\r\n \"paymentMethod\": \"Ödeme Yöntemi\",\r\n \"orderSummary\": \"Sipariş Özeti\",\r\n \"placeOrder\": \"Siparişi Tamamla\",\r\n \"processing\": \"İşleniyor...\",\r\n \"firstName\": \"Ad\",\r\n \"lastName\": \"Soyad\",\r\n \"email\": \"E-posta Adresi\",\r\n \"phone\": \"Telefon Numarası\",\r\n \"address\": \"Adres\",\r\n \"city\": \"Şehir\",\r\n \"postalCode\": \"Posta Kodu\",\r\n \"country\": \"Ülke\",\r\n \"selectCountry\": \"Ülke seçin\",\r\n \"qty\": \"Adet\",\r\n \"subtotal\": \"Ara Toplam\",\r\n \"shipping\": \"Kargo\",\r\n \"free\": \"Ücretsiz\",\r\n \"tax\": \"Vergi\",\r\n \"total\": \"Toplam\",\r\n \"and\": \"ve\",\r\n \"termsOfService\": \"Hizmet Koşulları\",\r\n \"privacyPolicy\": \"Gizlilik Politikası\",\r\n \"addressPlaceholder\": \"Sokak adresi, daire, suit, vb.\",\r\n \"orderNotesOptional\": \"Sipariş Notları (Opsiyonel)\",\r\n \"orderNotesPlaceholder\": \"Siparişiniz için özel talimatlar...\",\r\n \"agreeToTermsTextBefore\": \"Kabul ediyorum:\",\r\n \"agreeToTermsError\": \"Lütfen hizmet koşullarını kabul edin\",\r\n \"card\": \"Kredi/Banka Kartı\",\r\n \"transfer\": \"Banka Havalesi\",\r\n \"cash\": \"Kapıda Ödeme\",\r\n \"cardDescription\": \"Kredi veya banka kartınızla güvenli ödeme\",\r\n \"transferDescription\": \"Banka hesabımıza ödeme transferi\",\r\n \"cashDescription\": \"Siparişiniz kapınıza geldiğinde ödeme\",\r\n \"proceedToPayment\": \"Ödemeye Geç\",\r\n \"cartEmpty\": \"Sepetiniz boş\",\r\n \"cartEmptyDescription\": \"Ödemeye geçmeden önce lütfen sepetinize ürün ekleyin.\",\r\n \"continueShopping\": \"Alışverişe Devam Et\",\r\n \"bankInfoError\": \"Banka bilgileri yüklenemedi\",\r\n \"orderError\": \"Sipariş verilemedi. Lütfen tekrar deneyin.\",\r\n \"orderErrorTitle\": \"Sipariş Başarısız\",\r\n \"bankTransferDetailsTitle\": \"Banka Havale Bilgileri\",\r\n \"bank\": \"Banka\",\r\n \"accountName\": \"Hesap Adı\",\r\n \"bankInfoNotAvailable\": \"Banka hesap bilgileri mevcut değil\",\r\n \"selectPaymentProvider\": \"Ödeme Sağlayıcısı Seçin\",\r\n \"creditCardRedirectNote\": \"Satın alma işleminizi tamamlamak için güvenli ödeme sayfasına yönlendirileceksiniz.\"\r\n}\r\n"
39
+ "content": "{\n \"pageTitle\": \"Ödeme\",\n \"title\": \"Ödeme\",\n \"completeOrder\": \"Siparişinizi tamamlayın\",\n \"contactInformation\": \"İletişim Bilgileri\",\n \"shippingAddress\": \"Teslimat Adresi\",\n \"paymentMethod\": \"Ödeme Yöntemi\",\n \"orderSummary\": \"Sipariş Özeti\",\n \"placeOrder\": \"Siparişi Tamamla\",\n \"processing\": \"İşleniyor...\",\n \"firstName\": \"Ad\",\n \"lastName\": \"Soyad\",\n \"email\": \"E-posta Adresi\",\n \"phone\": \"Telefon Numarası\",\n \"address\": \"Adres\",\n \"city\": \"Şehir\",\n \"postalCode\": \"Posta Kodu\",\n \"country\": \"Ülke\",\n \"selectCountry\": \"Ülke seçin\",\n \"qty\": \"Adet\",\n \"subtotal\": \"Ara Toplam\",\n \"shipping\": \"Kargo\",\n \"free\": \"Ücretsiz\",\n \"tax\": \"Vergi\",\n \"total\": \"Toplam\",\n \"and\": \"ve\",\n \"termsOfService\": \"Hizmet Koşulları\",\n \"privacyPolicy\": \"Gizlilik Politikası\",\n \"addressPlaceholder\": \"Sokak adresi, daire, suit, vb.\",\n \"orderNotesOptional\": \"Sipariş Notları (Opsiyonel)\",\n \"orderNotesPlaceholder\": \"Siparişiniz için özel talimatlar...\",\n \"agreeToTermsTextBefore\": \"Kabul ediyorum:\",\n \"agreeToTermsError\": \"Lütfen hizmet koşullarını kabul edin\",\n \"card\": \"Kredi/Banka Kartı\",\n \"transfer\": \"Banka Havalesi\",\n \"cash\": \"Kapıda Ödeme\",\n \"cardDescription\": \"Kredi veya banka kartınızla güvenli ödeme\",\n \"transferDescription\": \"Banka hesabımıza ödeme transferi\",\n \"cashDescription\": \"Siparişiniz kapınıza geldiğinde ödeme\",\n \"proceedToPayment\": \"Ödemeye Geç\",\n \"cartEmpty\": \"Sepetiniz boş\",\n \"cartEmptyDescription\": \"Ödemeye geçmeden önce lütfen sepetinize ürün ekleyin.\",\n \"continueShopping\": \"Alışverişe Devam Et\",\n \"bankInfoError\": \"Banka bilgileri yüklenemedi\",\n \"orderError\": \"Sipariş verilemedi. Lütfen tekrar deneyin.\",\n \"orderErrorTitle\": \"Sipariş Başarısız\",\n \"bankTransferDetailsTitle\": \"Banka Havale Bilgileri\",\n \"bank\": \"Banka\",\n \"accountName\": \"Hesap Adı\",\n \"bankInfoNotAvailable\": \"Banka hesap bilgileri mevcut değil\",\n \"selectPaymentProvider\": \"Ödeme Sağlayıcısı Seçin\",\n \"creditCardRedirectNote\": \"Satın alma işleminizi tamamlamak için güvenli ödeme sayfasına yönlendirileceksiniz.\"\n}\n"
40
40
  }
41
41
  ],
42
42
  "exports": {