@promakeai/cli 0.3.9 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/index.js +3 -3
  2. package/dist/registry/auth-core.json +1 -1
  3. package/dist/registry/blog-core.json +16 -16
  4. package/dist/registry/blog-list-page.json +2 -2
  5. package/dist/registry/blog-section.json +1 -1
  6. package/dist/registry/cards-carousel-section.json +2 -2
  7. package/dist/registry/cart-drawer.json +1 -1
  8. package/dist/registry/category-section.json +1 -1
  9. package/dist/registry/checkout-page.json +1 -1
  10. package/dist/registry/docs/blog-core.md +6 -6
  11. package/dist/registry/docs/blog-list-page.md +1 -1
  12. package/dist/registry/docs/ecommerce-core.md +6 -6
  13. package/dist/registry/docs/featured-products.md +1 -1
  14. package/dist/registry/docs/post-detail-page.md +2 -2
  15. package/dist/registry/docs/product-detail-page.md +2 -2
  16. package/dist/registry/docs/products-page.md +1 -1
  17. package/dist/registry/docs/reset-password-page.md +15 -9
  18. package/dist/registry/ecommerce-core.json +15 -15
  19. package/dist/registry/favorites-blog-block.json +1 -1
  20. package/dist/registry/favorites-blog-page.json +1 -1
  21. package/dist/registry/favorites-ecommerce-block.json +1 -1
  22. package/dist/registry/favorites-ecommerce-page.json +1 -1
  23. package/dist/registry/featured-products.json +2 -2
  24. package/dist/registry/header-ecommerce.json +1 -1
  25. package/dist/registry/my-orders-page.json +1 -1
  26. package/dist/registry/order-confirmation-page.json +1 -1
  27. package/dist/registry/post-card.json +1 -1
  28. package/dist/registry/post-detail-page.json +3 -3
  29. package/dist/registry/product-detail-page.json +3 -3
  30. package/dist/registry/products-page.json +2 -2
  31. package/dist/registry/related-posts-block.json +1 -1
  32. package/dist/registry/related-products-block.json +1 -1
  33. package/dist/registry/reset-password-page.json +19 -10
  34. package/package.json +1 -1
  35. package/template/bun.lock +2 -2
  36. package/template/package.json +2 -2
@@ -16,7 +16,7 @@
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\";\nimport { ArrowRight } from \"lucide-react\";\nimport { Card } from \"@/components/ui/card\";\nimport { useTranslation } from \"react-i18next\";\nimport { useCategories } from \"@/modules/ecommerce-core\";\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 { categories: hookCategories, loading: hookLoading } = useCategories();\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 <Link\n key={category.id}\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 ))}\n </div>\n </div>\n </section>\n );\n}\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 { useDbCategories } from \"@/modules/ecommerce-core\";\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 { categories: hookCategories, loading: hookLoading } = useDbCategories();\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",
@@ -24,7 +24,7 @@
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\";\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\";\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 as any).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 as any).payment.createCheckout({\n currency: currency.toLowerCase(),\n taxAmount: taxAmountInCents,\n paymentType: paymentType,\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\n clearCart();\n window.location.href = response.url;\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 <div className=\"space-y-2\">\n <Label htmlFor=\"firstName\">\n {t(\"firstName\", \"First Name\")} *\n </Label>\n <Input\n id=\"firstName\"\n name=\"firstName\"\n value={formData.firstName}\n onChange={handleInputChange}\n required\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"lastName\">\n {t(\"lastName\", \"Last Name\")} *\n </Label>\n <Input\n id=\"lastName\"\n name=\"lastName\"\n value={formData.lastName}\n onChange={handleInputChange}\n required\n />\n </div>\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"email\">\n {t(\"email\", \"Email Address\")} *\n </Label>\n <Input\n id=\"email\"\n name=\"email\"\n type=\"email\"\n value={formData.email}\n onChange={handleInputChange}\n required\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"phone\">\n {t(\"phone\", \"Phone Number\")} *\n </Label>\n <Input\n id=\"phone\"\n name=\"phone\"\n type=\"tel\"\n value={formData.phone}\n onChange={handleInputChange}\n required\n />\n </div>\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 <div className=\"space-y-2\">\n <Label htmlFor=\"country\">{t(\"country\", \"Country\")} *</Label>\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 </div>\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"city\">{t(\"city\", \"City\")} *</Label>\n <Input\n id=\"city\"\n name=\"city\"\n value={formData.city}\n onChange={handleInputChange}\n required\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"postalCode\">\n {t(\"postalCode\", \"Postal Code\")} *\n </Label>\n <Input\n id=\"postalCode\"\n name=\"postalCode\"\n value={formData.postalCode}\n onChange={handleInputChange}\n required\n />\n </div>\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"
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\";\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 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 <div className=\"space-y-2\">\n <Label htmlFor=\"firstName\">\n {t(\"firstName\", \"First Name\")} *\n </Label>\n <Input\n id=\"firstName\"\n name=\"firstName\"\n value={formData.firstName}\n onChange={handleInputChange}\n required\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"lastName\">\n {t(\"lastName\", \"Last Name\")} *\n </Label>\n <Input\n id=\"lastName\"\n name=\"lastName\"\n value={formData.lastName}\n onChange={handleInputChange}\n required\n />\n </div>\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"email\">\n {t(\"email\", \"Email Address\")} *\n </Label>\n <Input\n id=\"email\"\n name=\"email\"\n type=\"email\"\n value={formData.email}\n onChange={handleInputChange}\n required\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"phone\">\n {t(\"phone\", \"Phone Number\")} *\n </Label>\n <Input\n id=\"phone\"\n name=\"phone\"\n type=\"tel\"\n value={formData.phone}\n onChange={handleInputChange}\n required\n />\n </div>\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 <div className=\"space-y-2\">\n <Label htmlFor=\"country\">{t(\"country\", \"Country\")} *</Label>\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 </div>\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"city\">{t(\"city\", \"City\")} *</Label>\n <Input\n id=\"city\"\n name=\"city\"\n value={formData.city}\n onChange={handleInputChange}\n required\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"postalCode\">\n {t(\"postalCode\", \"Postal Code\")} *\n </Label>\n <Input\n id=\"postalCode\"\n name=\"postalCode\"\n value={formData.postalCode}\n onChange={handleInputChange}\n required\n />\n </div>\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",
@@ -1,6 +1,6 @@
1
1
  # Blog Core
2
2
 
3
- Complete blog state management with Zustand. Includes useBlogStore for saved/favorite posts functionality, usePosts hook for fetching posts with category filtering, search, and pagination. TypeScript types for Post, Category, and Author. No provider wrapping needed.
3
+ Complete blog state management with Zustand. Includes useBlogStore for saved/favorite posts functionality, useDbPosts hook for fetching posts with category filtering, search, and pagination. TypeScript types for Post, Category, and Author. No provider wrapping needed.
4
4
 
5
5
  ## Files
6
6
 
@@ -9,7 +9,7 @@ Complete blog state management with Zustand. Includes useBlogStore for saved/fav
9
9
  | `$modules$/blog-core/index.ts` | index |
10
10
  | `$modules$/blog-core/types.ts` | type |
11
11
  | `$modules$/blog-core/stores/blog-store.ts` | store |
12
- | `$modules$/blog-core/usePosts.ts` | hook |
12
+ | `$modules$/blog-core/useDbPosts.ts` | hook |
13
13
  | `$modules$/blog-core/lang/en.json` | lang |
14
14
  | `$modules$/blog-core/lang/tr.json` | lang |
15
15
 
@@ -17,20 +17,20 @@ Complete blog state management with Zustand. Includes useBlogStore for saved/fav
17
17
 
18
18
  **Types:** `Author`, `BlogCategory`, `BlogContextType`, `BlogSettings`, `Comment`, `Post`, `PostCategory`
19
19
 
20
- **Components/Functions:** `useBlog`, `useBlogCategories`, `useBlogStore`, `useFeaturedPosts`, `usePopularPosts`, `usePostBySlug`, `usePostSearch`, `usePostStats`, `usePosts`, `usePostsByCategory`, `usePostsByTag`, `useRecentPosts`
20
+ **Components/Functions:** `useBlog`, `useBlogStore`, `useDbBlogCategories`, `useDbFeaturedPosts`, `useDbPopularPosts`, `useDbPostBySlug`, `useDbPostSearch`, `useDbPostStats`, `useDbPosts`, `useDbPostsByCategory`, `useDbPostsByTag`, `useDbRecentPosts`
21
21
 
22
22
  ```typescript
23
- import { useBlog, useBlogCategories, useBlogStore, ... } from '@/modules/blog-core';
23
+ import { useBlog, useBlogStore, useDbBlogCategories, ... } from '@/modules/blog-core';
24
24
  ```
25
25
 
26
26
  ## Usage
27
27
 
28
28
  ```
29
- import { useBlog, usePosts, usePostBySlug } from '@/modules/blog-core';
29
+ import { useBlog, useDbPosts, useDbPostBySlug } from '@/modules/blog-core';
30
30
 
31
31
  // No provider needed - just use the hooks:
32
32
  const { favorites, addToFavorites, isFavorite } = useBlog();
33
- const { posts, loading } = usePosts();
33
+ const { posts, loading } = useDbPosts();
34
34
 
35
35
  // Or use store directly with selectors:
36
36
  const favorites = useBlogStore((s) => s.favorites);
@@ -26,7 +26,7 @@ import { BlogListPage } from '@/modules/blog-list-page';
26
26
 
27
27
  <Route path="/blog" element={<BlogListPage />} />
28
28
 
29
- • Uses usePosts() from blog-core (Zustand)
29
+ • Uses useDbPosts() from blog-core (Zustand)
30
30
  • Features: category tabs, search, grid/list view
31
31
  • Sidebar: popular posts, categories, newsletter
32
32
  ```
@@ -1,6 +1,6 @@
1
1
  # E-commerce Core
2
2
 
3
- Complete e-commerce state management with Zustand. Includes useCartStore for shopping cart operations (add/remove/update items, totals), useFavoritesStore for wishlist, useProducts hook for product fetching with filtering/sorting/pagination, and useSearch hook. No provider wrapping needed.
3
+ Complete e-commerce state management with Zustand. Includes useCartStore for shopping cart operations (add/remove/update items, totals), useFavoritesStore for wishlist, useDbProducts hook for product fetching with filtering/sorting/pagination, and useDbSearch hook. No provider wrapping needed.
4
4
 
5
5
  ## Files
6
6
 
@@ -10,8 +10,8 @@ Complete e-commerce state management with Zustand. Includes useCartStore for sho
10
10
  | `$modules$/ecommerce-core/types.ts` | type |
11
11
  | `$modules$/ecommerce-core/stores/cart-store.ts` | store |
12
12
  | `$modules$/ecommerce-core/stores/favorites-store.ts` | store |
13
- | `$modules$/ecommerce-core/useProducts.ts` | hook |
14
- | `$modules$/ecommerce-core/useSearch.ts` | hook |
13
+ | `$modules$/ecommerce-core/useDbProducts.ts` | hook |
14
+ | `$modules$/ecommerce-core/useDbSearch.ts` | hook |
15
15
  | `$modules$/ecommerce-core/format-price.ts` | lib |
16
16
  | `$modules$/ecommerce-core/payment-config.ts` | lib |
17
17
  | `$modules$/ecommerce-core/lang/en.json` | lang |
@@ -21,7 +21,7 @@ Complete e-commerce state management with Zustand. Includes useCartStore for sho
21
21
 
22
22
  **Types:** `Address`, `CartContextType`, `CartItem`, `CartState`, `Category`, `FavoritesContextType`, `OnlinePaymentProvider`, `Order`, `OrderItem`, `PaymentMethod`, `PaymentMethodConfig`, `Product`, `ProductCategory`, `ProductVariant`, `User`
23
23
 
24
- **Components/Functions:** `ONLINE_PROVIDER_CONFIGS`, `PAYMENT_METHOD_CONFIGS`, `formatPrice`, `getAvailablePaymentMethods`, `getFilteredPaymentMethodConfigs`, `getOnlinePaymentProviders`, `isOnlineProviderAvailable`, `isPaymentMethodAvailable`, `useCart`, `useCartStore`, `useCategories`, `useFavorites`, `useFavoritesStore`, `useFeaturedProducts`, `useProductBySlug`, `useProducts`, `useSearch`
24
+ **Components/Functions:** `ONLINE_PROVIDER_CONFIGS`, `PAYMENT_METHOD_CONFIGS`, `formatPrice`, `getAvailablePaymentMethods`, `getFilteredPaymentMethodConfigs`, `getOnlinePaymentProviders`, `isOnlineProviderAvailable`, `isPaymentMethodAvailable`, `useCart`, `useCartStore`, `useDbCategories`, `useDbFeaturedProducts`, `useDbProductBySlug`, `useDbProducts`, `useDbSearch`, `useFavorites`, `useFavoritesStore`
25
25
 
26
26
  ```typescript
27
27
  import { ONLINE_PROVIDER_CONFIGS, PAYMENT_METHOD_CONFIGS, formatPrice, ... } from '@/modules/ecommerce-core';
@@ -30,12 +30,12 @@ import { ONLINE_PROVIDER_CONFIGS, PAYMENT_METHOD_CONFIGS, formatPrice, ... } fro
30
30
  ## Usage
31
31
 
32
32
  ```
33
- import { useCart, useFavorites, useProducts } from '@/modules/ecommerce-core';
33
+ import { useCart, useFavorites, useDbProducts } from '@/modules/ecommerce-core';
34
34
 
35
35
  // No provider needed - just use the hooks:
36
36
  const { addItem, removeItem, state, itemCount } = useCart();
37
37
  const { addToFavorites, isFavorite } = useFavorites();
38
- const { products, loading } = useProducts();
38
+ const { products, loading } = useDbProducts();
39
39
 
40
40
  // Or use stores directly with selectors:
41
41
  const itemCount = useCartStore((s) => s.itemCount);
@@ -28,7 +28,7 @@ import { FeaturedProducts } from '@/modules/featured-products';
28
28
 
29
29
  • Installed at: src/modules/featured-products/
30
30
  • Customize content: src/modules/featured-products/lang/*.json
31
- • Products auto-loaded via useProducts hook
31
+ • Products auto-loaded via useDbProducts hook
32
32
  ```
33
33
 
34
34
  ## Dependencies
@@ -1,6 +1,6 @@
1
1
  # Post Detail Page
2
2
 
3
- Blog post detail page that fetches post data by slug from URL params. Uses usePostBySlug hook from blog-core and renders PostDetailBlock. Includes loading skeleton, error handling for not found posts, and automatic page title.
3
+ Blog post detail page that fetches post data by slug from URL params. Uses useDbPostBySlug hook from blog-core and renders PostDetailBlock. Includes loading skeleton, error handling for not found posts, and automatic page title.
4
4
 
5
5
  ## Files
6
6
 
@@ -26,7 +26,7 @@ import { PostDetailPage } from '@/modules/post-detail-page';
26
26
 
27
27
  <Route path="/blog/:slug" element={<PostDetailPage />} />
28
28
 
29
- • Uses usePostBySlug() from blog-core
29
+ • Uses useDbPostBySlug() from blog-core
30
30
  • Fetches post by slug from URL params
31
31
  • Shows loading skeleton while fetching
32
32
  • Handles post not found state
@@ -1,6 +1,6 @@
1
1
  # Product Detail Page
2
2
 
3
- Product detail page that fetches product data by slug from URL params. Uses useProductBySlug hook from ecommerce-core and renders ProductDetailBlock. Includes loading skeleton, error handling for not found products, and automatic page title.
3
+ Product detail page that fetches product data by slug from URL params. Uses useDbProductBySlug hook from ecommerce-core and renders ProductDetailBlock. Includes loading skeleton, error handling for not found products, and automatic page title.
4
4
 
5
5
  ## Files
6
6
 
@@ -26,7 +26,7 @@ import { ProductDetailPage } from '@/modules/product-detail-page';
26
26
 
27
27
  <Route path="/products/:slug" element={<ProductDetailPage />} />
28
28
 
29
- • Uses useProductBySlug() from ecommerce-core
29
+ • Uses useDbProductBySlug() from ecommerce-core
30
30
  • Fetches product by slug from URL params
31
31
  • Shows loading skeleton while fetching
32
32
  • Handles product not found state
@@ -29,7 +29,7 @@ import ProductsPage from '@/modules/products-page';
29
29
  • Installed at: src/modules/products-page/
30
30
  • Add link: <Link to="/products">Browse Products</Link>
31
31
  • Supports filters, sorting, grid/list view, pagination
32
- • Uses useProducts hook for data fetching
32
+ • Uses useDbProducts hook for data fetching
33
33
  ```
34
34
 
35
35
  ## Dependencies
@@ -1,6 +1,6 @@
1
1
  # Reset Password Page
2
2
 
3
- Split-screen password reset page with form on the left and full-height image on the right. Features new password input with confirmation, validates reset code from URL. Clean minimal design with API integration and responsive layout.
3
+ Centered card password reset page with Layout wrapper. Validates reset code and username from URL parameters (?code=&username=). Features new password input with confirmation and show/hide toggle. Uses useAuth hook from auth-core for password reset.
4
4
 
5
5
  ## Files
6
6
 
@@ -11,26 +11,32 @@ Split-screen password reset page with form on the left and full-height image on
11
11
  | `$modules$/reset-password-page/lang/en.json` | lang |
12
12
  | `$modules$/reset-password-page/lang/tr.json` | lang |
13
13
 
14
+ ## Exports
15
+
16
+ **Components/Functions:** `ResetPasswordPage`, `default`
17
+
18
+ ```typescript
19
+ import { ResetPasswordPage, default } from '@/modules/reset-password-page';
20
+ ```
21
+
14
22
  ## Usage
15
23
 
16
24
  ```
17
25
  import ResetPasswordPage from '@/modules/reset-password-page';
18
26
 
19
- <ResetPasswordPage
20
- image="/images/reset-bg.jpg"
21
- />
27
+ <ResetPasswordPage />
22
28
 
23
29
  • Installed at: src/modules/reset-password-page/
24
30
  • Customize text: src/modules/reset-password-page/lang/*.json
25
- • Uses customerClient.auth.resetPassword() for API
26
- Expects ?code= URL parameter from email link
31
+ • Uses useAuth() hook from auth-core:
32
+ const { resetPassword } = useAuth();
33
+ await resetPassword(username, code, newPassword);
34
+ • Expects ?code= and ?username= URL parameters from email link
27
35
  • Add to your router as a page component
28
36
  ```
29
37
 
30
38
  ## Dependencies
31
39
 
32
40
  This component requires:
33
- - `button`
34
- - `input`
35
- - `auth`
41
+ - `auth-core`
36
42
  - `api`
@@ -2,18 +2,18 @@
2
2
  "name": "ecommerce-core",
3
3
  "type": "registry:module",
4
4
  "title": "E-commerce Core",
5
- "description": "Complete e-commerce state management with Zustand. Includes useCartStore for shopping cart operations (add/remove/update items, totals), useFavoritesStore for wishlist, useProducts hook for product fetching with filtering/sorting/pagination, and useSearch hook. No provider wrapping needed.",
5
+ "description": "Complete e-commerce state management with Zustand. Includes useCartStore for shopping cart operations (add/remove/update items, totals), useFavoritesStore for wishlist, useDbProducts hook for product fetching with filtering/sorting/pagination, and useDbSearch hook. No provider wrapping needed.",
6
6
  "dependencies": [
7
7
  "zustand"
8
8
  ],
9
9
  "registryDependencies": [],
10
- "usage": "import { useCart, useFavorites, useProducts } from '@/modules/ecommerce-core';\n\n// No provider needed - just use the hooks:\nconst { addItem, removeItem, state, itemCount } = useCart();\nconst { addToFavorites, isFavorite } = useFavorites();\nconst { products, loading } = useProducts();\n\n// Or use stores directly with selectors:\nconst itemCount = useCartStore((s) => s.itemCount);",
10
+ "usage": "import { useCart, useFavorites, useDbProducts } from '@/modules/ecommerce-core';\n\n// No provider needed - just use the hooks:\nconst { addItem, removeItem, state, itemCount } = useCart();\nconst { addToFavorites, isFavorite } = useFavorites();\nconst { products, loading } = useDbProducts();\n\n// Or use stores directly with selectors:\nconst itemCount = useCartStore((s) => s.itemCount);",
11
11
  "files": [
12
12
  {
13
13
  "path": "ecommerce-core/index.ts",
14
14
  "type": "registry:index",
15
15
  "target": "$modules$/ecommerce-core/index.ts",
16
- "content": "// Types\r\nexport * from './types';\r\n\r\n// Stores (Zustand)\r\nexport { useCartStore, useCart } from './stores/cart-store';\r\nexport { useFavoritesStore, useFavorites } from './stores/favorites-store';\r\n\r\n// Hooks\r\nexport { useProducts, useProductBySlug, useFeaturedProducts, useCategories } from './useProducts';\r\nexport { useSearch } from './useSearch';\r\n\r\n// Utilities\r\nexport { formatPrice } from './format-price';\r\n\r\n// Payment Config\r\nexport {\r\n type PaymentMethod,\r\n type OnlinePaymentProvider,\r\n type PaymentMethodConfig,\r\n PAYMENT_METHOD_CONFIGS,\r\n ONLINE_PROVIDER_CONFIGS,\r\n getAvailablePaymentMethods,\r\n getOnlinePaymentProviders,\r\n getFilteredPaymentMethodConfigs,\r\n isPaymentMethodAvailable,\r\n isOnlineProviderAvailable,\r\n} from './payment-config';\r\n"
16
+ "content": "// Types\r\nexport * from './types';\r\n\r\n// Stores (Zustand)\r\nexport { useCartStore, useCart } from './stores/cart-store';\r\nexport { useFavoritesStore, useFavorites } from './stores/favorites-store';\r\n\r\n// Hooks\r\nexport { useDbProducts, useDbProductBySlug, useDbFeaturedProducts, useDbCategories } from './useDbProducts';\r\nexport { useDbSearch } from './useDbSearch';\r\n\r\n// Utilities\r\nexport { formatPrice } from './format-price';\r\n\r\n// Payment Config\r\nexport {\r\n type PaymentMethod,\r\n type OnlinePaymentProvider,\r\n type PaymentMethodConfig,\r\n PAYMENT_METHOD_CONFIGS,\r\n ONLINE_PROVIDER_CONFIGS,\r\n getAvailablePaymentMethods,\r\n getOnlinePaymentProviders,\r\n getFilteredPaymentMethodConfigs,\r\n isPaymentMethodAvailable,\r\n isOnlineProviderAvailable,\r\n} from './payment-config';\r\n"
17
17
  },
18
18
  {
19
19
  "path": "ecommerce-core/types.ts",
@@ -34,16 +34,16 @@
34
34
  "content": "import { create } from \"zustand\";\r\nimport { persist } from \"zustand/middleware\";\r\nimport type { Product, FavoritesContextType } from \"../types\";\r\n\r\ninterface FavoritesStore {\r\n favorites: Product[];\r\n favoriteCount: number;\r\n addToFavorites: (product: Product) => void;\r\n removeFromFavorites: (productId: string | number) => void;\r\n isFavorite: (productId: string | number) => boolean;\r\n clearFavorites: () => void;\r\n}\r\n\r\nexport const useFavoritesStore = create<FavoritesStore>()(\r\n persist(\r\n (set, get) => ({\r\n favorites: [],\r\n favoriteCount: 0,\r\n\r\n addToFavorites: (product) =>\r\n set((state) => {\r\n if (state.favorites.some((fav) => fav.id === product.id)) {\r\n return state;\r\n }\r\n const favorites = [...state.favorites, product];\r\n return { favorites, favoriteCount: favorites.length };\r\n }),\r\n\r\n removeFromFavorites: (productId) =>\r\n set((state) => {\r\n const favorites = state.favorites.filter((fav) => fav.id !== productId);\r\n return { favorites, favoriteCount: favorites.length };\r\n }),\r\n\r\n isFavorite: (productId) => {\r\n return get().favorites.some((fav) => fav.id === productId);\r\n },\r\n\r\n clearFavorites: () => set({ favorites: [], favoriteCount: 0 }),\r\n }),\r\n { name: \"ecommerce_favorites\" }\r\n )\r\n);\r\n\r\n// Backward compatible hook - matches FavoritesContextType\r\nexport const useFavorites = (): FavoritesContextType => {\r\n const store = useFavoritesStore();\r\n return {\r\n favorites: store.favorites,\r\n addToFavorites: store.addToFavorites,\r\n removeFromFavorites: store.removeFromFavorites,\r\n isFavorite: store.isFavorite,\r\n favoriteCount: store.favoriteCount,\r\n clearFavorites: store.clearFavorites,\r\n };\r\n};\r\n"
35
35
  },
36
36
  {
37
- "path": "ecommerce-core/useProducts.ts",
37
+ "path": "ecommerce-core/useDbProducts.ts",
38
38
  "type": "registry:hook",
39
- "target": "$modules$/ecommerce-core/useProducts.ts",
40
- "content": "import { useMemo } from 'react';\r\nimport type { Product, Category, ProductCategory } from './types';\r\nimport {\r\n useRepositoryQuery,\r\n useRawQuery,\r\n useRawQueryOne,\r\n parseStringToArray,\r\n parseJSONString,\r\n parseSQLiteBoolean,\r\n parseNumberSafe\r\n} from '@/modules/db';\r\n\r\nconst transformProduct = (row: any): Product => {\r\n const categoryNames = row.category_names ? row.category_names.split(',') : [];\r\n const categorySlugs = row.category_slugs ? row.category_slugs.split(',') : [];\r\n const categoryIds = row.category_ids ? row.category_ids.split(',').map(Number) : [];\r\n\r\n const categories: ProductCategory[] = categoryIds.map((id: number, index: number) => ({\r\n id,\r\n name: categoryNames[index] || '',\r\n slug: categorySlugs[index] || '',\r\n is_primary: index === 0\r\n }));\r\n\r\n const primaryCategory = categories.length > 0 ? categories[0] : null;\r\n\r\n return {\r\n id: parseNumberSafe(row.id),\r\n name: String(row.name || ''),\r\n slug: String(row.slug || ''),\r\n description: row.description || '',\r\n price: parseNumberSafe(row.price),\r\n sale_price: row.sale_price ? parseNumberSafe(row.sale_price) : undefined,\r\n on_sale: parseSQLiteBoolean(row.on_sale),\r\n images: parseStringToArray(row.images),\r\n brand: row.brand || '',\r\n sku: row.sku || '',\r\n stock: parseNumberSafe(row.stock),\r\n tags: parseJSONString(row.tags, []) || [],\r\n rating: parseNumberSafe(row.rating),\r\n review_count: parseNumberSafe(row.review_count),\r\n featured: parseSQLiteBoolean(row.featured),\r\n is_new: parseSQLiteBoolean(row.is_new),\r\n published: parseSQLiteBoolean(row.published),\r\n specifications: parseJSONString(row.specifications, {}) || {},\r\n variants: parseJSONString(row.variants, []) || [],\r\n created_at: row.created_at || new Date().toISOString(),\r\n updated_at: row.updated_at || new Date().toISOString(),\r\n meta_description: row.meta_description || '',\r\n meta_keywords: row.meta_keywords || '',\r\n category: primaryCategory?.slug || '',\r\n category_name: primaryCategory?.name || '',\r\n categories\r\n };\r\n};\r\n\r\nconst PRODUCTS_WITH_CATEGORIES_SQL = `\r\n SELECT p.*,\r\n GROUP_CONCAT(c.name) as category_names,\r\n GROUP_CONCAT(c.slug) as category_slugs,\r\n GROUP_CONCAT(c.id) as category_ids\r\n FROM products p\r\n LEFT JOIN product_category_relations pcr ON p.id = pcr.product_id\r\n LEFT JOIN product_categories c ON pcr.category_id = c.id\r\n WHERE p.published = 1\r\n GROUP BY p.id\r\n`;\r\n\r\nexport function useCategories() {\r\n const { data, isLoading: loading, error } = useRepositoryQuery<Category>('product_categories', {\r\n orderBy: [{ field: 'name', direction: 'ASC' }]\r\n });\r\n\r\n return {\r\n categories: data ?? [],\r\n loading,\r\n error: error?.message ?? null\r\n };\r\n}\r\n\r\nexport function useProducts() {\r\n const sql = `${PRODUCTS_WITH_CATEGORIES_SQL} ORDER BY p.name`;\r\n\r\n const { data, isLoading: loading, error } = useRawQuery<any>(\r\n ['products', 'all'],\r\n sql\r\n );\r\n\r\n const products = useMemo(() => {\r\n if (!data) return [];\r\n return data.map(transformProduct);\r\n }, [data]);\r\n\r\n return { products, loading, error: error?.message ?? null };\r\n}\r\n\r\nexport function useProductBySlug(slug: string) {\r\n const sql = `\r\n SELECT p.*,\r\n GROUP_CONCAT(c.name) as category_names,\r\n GROUP_CONCAT(c.slug) as category_slugs,\r\n GROUP_CONCAT(c.id) as category_ids\r\n FROM products p\r\n LEFT JOIN product_category_relations pcr ON p.id = pcr.product_id\r\n LEFT JOIN product_categories c ON pcr.category_id = c.id\r\n WHERE p.slug = ? AND p.published = 1\r\n GROUP BY p.id\r\n `;\r\n\r\n const { data, isLoading: loading, error } = useRawQueryOne<any>(\r\n ['products', 'slug', slug],\r\n sql,\r\n [slug],\r\n { enabled: !!slug }\r\n );\r\n\r\n const product = useMemo(() => {\r\n if (!data) return null;\r\n return transformProduct(data);\r\n }, [data]);\r\n\r\n return {\r\n product,\r\n loading,\r\n error: !data && !loading && slug ? 'Product not found' : (error?.message ?? null)\r\n };\r\n}\r\n\r\nexport function useFeaturedProducts() {\r\n const sql = `\r\n SELECT p.*,\r\n GROUP_CONCAT(c.name) as category_names,\r\n GROUP_CONCAT(c.slug) as category_slugs,\r\n GROUP_CONCAT(c.id) as category_ids\r\n FROM products p\r\n LEFT JOIN product_category_relations pcr ON p.id = pcr.product_id\r\n LEFT JOIN product_categories c ON pcr.category_id = c.id\r\n WHERE p.published = 1 AND p.featured = 1\r\n GROUP BY p.id\r\n ORDER BY p.created_at DESC LIMIT 8\r\n `;\r\n\r\n const { data, isLoading: loading, error } = useRawQuery<any>(\r\n ['products', 'featured'],\r\n sql\r\n );\r\n\r\n const products = useMemo(() => {\r\n if (!data) return [];\r\n return data.map(transformProduct);\r\n }, [data]);\r\n\r\n return { products, loading, error: error?.message ?? null };\r\n}\r\n"
39
+ "target": "$modules$/ecommerce-core/useDbProducts.ts",
40
+ "content": "import { useMemo } from 'react';\r\nimport type { Product, Category, ProductCategory } from './types';\r\nimport {\r\n useRepositoryQuery,\r\n useRawQuery,\r\n useRawQueryOne,\r\n parseStringToArray,\r\n parseJSONString,\r\n parseSQLiteBoolean,\r\n parseNumberSafe\r\n} from '@/modules/db';\r\n\r\nconst transformProduct = (row: any): Product => {\r\n const categoryNames = row.category_names ? row.category_names.split(',') : [];\r\n const categorySlugs = row.category_slugs ? row.category_slugs.split(',') : [];\r\n const categoryIds = row.category_ids ? row.category_ids.split(',').map(Number) : [];\r\n\r\n const categories: ProductCategory[] = categoryIds.map((id: number, index: number) => ({\r\n id,\r\n name: categoryNames[index] || '',\r\n slug: categorySlugs[index] || '',\r\n is_primary: index === 0\r\n }));\r\n\r\n const primaryCategory = categories.length > 0 ? categories[0] : null;\r\n\r\n return {\r\n id: parseNumberSafe(row.id),\r\n name: String(row.name || ''),\r\n slug: String(row.slug || ''),\r\n description: row.description || '',\r\n price: parseNumberSafe(row.price),\r\n sale_price: row.sale_price ? parseNumberSafe(row.sale_price) : undefined,\r\n on_sale: parseSQLiteBoolean(row.on_sale),\r\n images: parseStringToArray(row.images),\r\n brand: row.brand || '',\r\n sku: row.sku || '',\r\n stock: parseNumberSafe(row.stock),\r\n tags: parseJSONString(row.tags, []) || [],\r\n rating: parseNumberSafe(row.rating),\r\n review_count: parseNumberSafe(row.review_count),\r\n featured: parseSQLiteBoolean(row.featured),\r\n is_new: parseSQLiteBoolean(row.is_new),\r\n published: parseSQLiteBoolean(row.published),\r\n specifications: parseJSONString(row.specifications, {}) || {},\r\n variants: parseJSONString(row.variants, []) || [],\r\n created_at: row.created_at || new Date().toISOString(),\r\n updated_at: row.updated_at || new Date().toISOString(),\r\n meta_description: row.meta_description || '',\r\n meta_keywords: row.meta_keywords || '',\r\n category: primaryCategory?.slug || '',\r\n category_name: primaryCategory?.name || '',\r\n categories\r\n };\r\n};\r\n\r\nconst PRODUCTS_WITH_CATEGORIES_SQL = `\r\n SELECT p.*,\r\n GROUP_CONCAT(c.name) as category_names,\r\n GROUP_CONCAT(c.slug) as category_slugs,\r\n GROUP_CONCAT(c.id) as category_ids\r\n FROM products p\r\n LEFT JOIN product_category_relations pcr ON p.id = pcr.product_id\r\n LEFT JOIN product_categories c ON pcr.category_id = c.id\r\n WHERE p.published = 1\r\n GROUP BY p.id\r\n`;\r\n\r\nexport function useDbCategories() {\r\n const { data, isLoading: loading, error } = useRepositoryQuery<Category>('product_categories', {\r\n orderBy: [{ field: 'name', direction: 'ASC' }]\r\n });\r\n\r\n return {\r\n categories: data ?? [],\r\n loading,\r\n error: error?.message ?? null\r\n };\r\n}\r\n\r\nexport function useDbProducts() {\r\n const sql = `${PRODUCTS_WITH_CATEGORIES_SQL} ORDER BY p.name`;\r\n\r\n const { data, isLoading: loading, error } = useRawQuery<any>(\r\n ['products', 'all'],\r\n sql\r\n );\r\n\r\n const products = useMemo(() => {\r\n if (!data) return [];\r\n return data.map(transformProduct);\r\n }, [data]);\r\n\r\n return { products, loading, error: error?.message ?? null };\r\n}\r\n\r\nexport function useDbProductBySlug(slug: string) {\r\n const sql = `\r\n SELECT p.*,\r\n GROUP_CONCAT(c.name) as category_names,\r\n GROUP_CONCAT(c.slug) as category_slugs,\r\n GROUP_CONCAT(c.id) as category_ids\r\n FROM products p\r\n LEFT JOIN product_category_relations pcr ON p.id = pcr.product_id\r\n LEFT JOIN product_categories c ON pcr.category_id = c.id\r\n WHERE p.slug = ? AND p.published = 1\r\n GROUP BY p.id\r\n `;\r\n\r\n const { data, isLoading: loading, error } = useRawQueryOne<any>(\r\n ['products', 'slug', slug],\r\n sql,\r\n [slug],\r\n { enabled: !!slug }\r\n );\r\n\r\n const product = useMemo(() => {\r\n if (!data) return null;\r\n return transformProduct(data);\r\n }, [data]);\r\n\r\n return {\r\n product,\r\n loading,\r\n error: !data && !loading && slug ? 'Product not found' : (error?.message ?? null)\r\n };\r\n}\r\n\r\nexport function useDbFeaturedProducts() {\r\n const sql = `\r\n SELECT p.*,\r\n GROUP_CONCAT(c.name) as category_names,\r\n GROUP_CONCAT(c.slug) as category_slugs,\r\n GROUP_CONCAT(c.id) as category_ids\r\n FROM products p\r\n LEFT JOIN product_category_relations pcr ON p.id = pcr.product_id\r\n LEFT JOIN product_categories c ON pcr.category_id = c.id\r\n WHERE p.published = 1 AND p.featured = 1\r\n GROUP BY p.id\r\n ORDER BY p.created_at DESC LIMIT 8\r\n `;\r\n\r\n const { data, isLoading: loading, error } = useRawQuery<any>(\r\n ['products', 'featured'],\r\n sql\r\n );\r\n\r\n const products = useMemo(() => {\r\n if (!data) return [];\r\n return data.map(transformProduct);\r\n }, [data]);\r\n\r\n return { products, loading, error: error?.message ?? null };\r\n}\r\n"
41
41
  },
42
42
  {
43
- "path": "ecommerce-core/useSearch.ts",
43
+ "path": "ecommerce-core/useDbSearch.ts",
44
44
  "type": "registry:hook",
45
- "target": "$modules$/ecommerce-core/useSearch.ts",
46
- "content": "import { useState, useEffect, useCallback } from 'react';\r\nimport type { Product } from './types';\r\nimport { useProducts } from './useProducts';\r\n\r\nexport const useSearch = () => {\r\n const [searchTerm, setSearchTerm] = useState('');\r\n const [results, setResults] = useState<Product[]>([]);\r\n const [isSearching, setIsSearching] = useState(false);\r\n\r\n // Load all products via useProducts hook\r\n const { products: allProducts } = useProducts();\r\n\r\n // Perform search when searchTerm changes\r\n useEffect(() => {\r\n if (!searchTerm.trim()) {\r\n setResults([]);\r\n setIsSearching(false);\r\n return;\r\n }\r\n\r\n setIsSearching(true);\r\n\r\n const searchTimeout = setTimeout(() => {\r\n const filtered = allProducts.filter(product => {\r\n const term = searchTerm.toLowerCase();\r\n \r\n // Search in product name\r\n if (product.name.toLowerCase().includes(term)) return true;\r\n \r\n // Search in description\r\n if (product.description.toLowerCase().includes(term)) return true;\r\n \r\n // Search in category\r\n if (product.category_name?.toLowerCase().includes(term)) return true;\r\n \r\n // Search in brand\r\n if (product.brand?.toLowerCase().includes(term)) return true;\r\n \r\n // Search in tags\r\n if (product.tags.some(tag => tag.toLowerCase().includes(term))) return true;\r\n \r\n return false;\r\n });\r\n\r\n setResults(filtered);\r\n setIsSearching(false);\r\n }, 300); // Debounce search\r\n\r\n return () => clearTimeout(searchTimeout);\r\n }, [searchTerm, allProducts]);\r\n\r\n const clearSearch = useCallback(() => {\r\n setSearchTerm('');\r\n setResults([]);\r\n setIsSearching(false);\r\n }, []);\r\n\r\n // search function that takes a term and sets the searchTerm\r\n const search = useCallback((term: string) => {\r\n setSearchTerm(term);\r\n }, []);\r\n\r\n return {\r\n searchTerm,\r\n setSearchTerm,\r\n results,\r\n isSearching,\r\n clearSearch,\r\n clearResults: clearSearch, // alias for header-ecommerce compatibility\r\n search, // function to trigger search\r\n hasResults: results.length > 0\r\n };\r\n};\r\n"
45
+ "target": "$modules$/ecommerce-core/useDbSearch.ts",
46
+ "content": "import { useState, useEffect, useCallback } from 'react';\r\nimport type { Product } from './types';\r\nimport { useDbProducts } from './useDbProducts';\r\n\r\nexport const useDbSearch = () => {\r\n const [searchTerm, setSearchTerm] = useState('');\r\n const [results, setResults] = useState<Product[]>([]);\r\n const [isSearching, setIsSearching] = useState(false);\r\n\r\n // Load all products via useDbProducts hook\r\n const { products: allProducts } = useDbProducts();\r\n\r\n // Perform search when searchTerm changes\r\n useEffect(() => {\r\n if (!searchTerm.trim()) {\r\n setResults([]);\r\n setIsSearching(false);\r\n return;\r\n }\r\n\r\n setIsSearching(true);\r\n\r\n const searchTimeout = setTimeout(() => {\r\n const filtered = allProducts.filter(product => {\r\n const term = searchTerm.toLowerCase();\r\n \r\n // Search in product name\r\n if (product.name.toLowerCase().includes(term)) return true;\r\n \r\n // Search in description\r\n if (product.description.toLowerCase().includes(term)) return true;\r\n \r\n // Search in category\r\n if (product.category_name?.toLowerCase().includes(term)) return true;\r\n \r\n // Search in brand\r\n if (product.brand?.toLowerCase().includes(term)) return true;\r\n \r\n // Search in tags\r\n if (product.tags.some(tag => tag.toLowerCase().includes(term))) return true;\r\n \r\n return false;\r\n });\r\n\r\n setResults(filtered);\r\n setIsSearching(false);\r\n }, 300); // Debounce search\r\n\r\n return () => clearTimeout(searchTimeout);\r\n }, [searchTerm, allProducts]);\r\n\r\n const clearSearch = useCallback(() => {\r\n setSearchTerm('');\r\n setResults([]);\r\n setIsSearching(false);\r\n }, []);\r\n\r\n // search function that takes a term and sets the searchTerm\r\n const search = useCallback((term: string) => {\r\n setSearchTerm(term);\r\n }, []);\r\n\r\n return {\r\n searchTerm,\r\n setSearchTerm,\r\n results,\r\n isSearching,\r\n clearSearch,\r\n clearResults: clearSearch, // alias for header-ecommerce compatibility\r\n search, // function to trigger search\r\n hasResults: results.length > 0\r\n };\r\n};\r\n"
47
47
  },
48
48
  {
49
49
  "path": "ecommerce-core/format-price.ts",
@@ -99,13 +99,13 @@
99
99
  "isPaymentMethodAvailable",
100
100
  "useCart",
101
101
  "useCartStore",
102
- "useCategories",
102
+ "useDbCategories",
103
+ "useDbFeaturedProducts",
104
+ "useDbProductBySlug",
105
+ "useDbProducts",
106
+ "useDbSearch",
103
107
  "useFavorites",
104
- "useFavoritesStore",
105
- "useFeaturedProducts",
106
- "useProductBySlug",
107
- "useProducts",
108
- "useSearch"
108
+ "useFavoritesStore"
109
109
  ]
110
110
  }
111
111
  }
@@ -19,7 +19,7 @@
19
19
  "path": "favorites-blog-block/favorites-blog-block.tsx",
20
20
  "type": "registry:block",
21
21
  "target": "$modules$/favorites-blog-block/favorites-blog-block.tsx",
22
- "content": "import { Link } from \"react-router\";\nimport { Heart, BookOpen } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { PostCard } from \"@/modules/post-card/post-card\";\nimport { useTranslation } from \"react-i18next\";\nimport type { Post } from \"@/modules/blog-core/types\";\n\ninterface FavoritesBlogBlockProps {\n favorites: Post[];\n onClearAll?: () => void;\n}\n\nexport function FavoritesBlogBlock({\n favorites,\n onClearAll,\n}: FavoritesBlogBlockProps) {\n const { t } = useTranslation(\"favorites-blog-block\");\n\n // Empty State\n if (favorites.length === 0) {\n return (\n <div className=\"min-h-screen bg-muted/30 py-12\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\n <div className=\"text-center max-w-md mx-auto\">\n <Heart className=\"w-16 h-16 text-muted-foreground mx-auto mb-6\" />\n <h1 className=\"text-3xl font-bold text-foreground mb-4\">\n {t(\"noFavoritesYet\", \"No Favorites Yet\")}\n </h1>\n <p className=\"text-muted-foreground mb-8\">\n {t(\n \"noFavoritesDescription\",\n \"Start browsing our blog and add posts to your favorites by clicking the heart icon.\"\n )}\n </p>\n <Button asChild size=\"lg\">\n <Link to=\"/blog\">\n <BookOpen className=\"w-5 h-5 mr-2\" />\n {t(\"browseBlog\", \"Browse Blog\")}\n </Link>\n </Button>\n </div>\n </div>\n </div>\n );\n }\n\n // Favorites Grid\n return (\n <div className=\"min-h-screen py-12\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\n {/* Header */}\n <div className=\"flex justify-between items-center mb-8\">\n <div>\n <h1 className=\"text-3xl font-bold mb-2\">\n {t(\"title\", \"My Favorites\")}\n </h1>\n <p className=\"text-muted-foreground\">\n {t(\n \"favoritesCount\",\n `You have ${favorites.length} favorite posts`\n )}\n </p>\n </div>\n {onClearAll && favorites.length > 0 && (\n <Button variant=\"outline\" onClick={onClearAll}>\n {t(\"clearAll\", \"Clear All\")}\n </Button>\n )}\n </div>\n\n {/* Posts Grid */}\n <div className=\"grid gap-6 md:grid-cols-2 lg:grid-cols-3\">\n {favorites.map((post) => (\n <PostCard key={post.id} post={post} layout=\"grid\" />\n ))}\n </div>\n </div>\n </div>\n );\n}\n"
22
+ "content": "import { Link } from \"react-router\";\nimport { Heart, BookOpen } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { PostCard } from \"@/modules/post-card/post-card\";\nimport { useTranslation } from \"react-i18next\";\nimport type { Post } from \"@/modules/blog-core/types\";\n\ninterface FavoritesBlogBlockProps {\n favorites: Post[];\n onClearAll?: () => void;\n}\n\nexport function FavoritesBlogBlock({\n favorites,\n onClearAll,\n}: FavoritesBlogBlockProps) {\n const { t } = useTranslation(\"favorites-blog-block\");\n\n // Empty State\n if (favorites.length === 0) {\n return (\n <div className=\"min-h-screen bg-muted/30 py-12\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\n <div className=\"text-center max-w-md mx-auto\">\n <Heart className=\"w-16 h-16 text-muted-foreground mx-auto mb-6\" />\n <h1 className=\"text-3xl font-bold text-foreground mb-4\">\n {t(\"noFavoritesYet\", \"No Favorites Yet\")}\n </h1>\n <p className=\"text-muted-foreground mb-8\">\n {t(\n \"noFavoritesDescription\",\n \"Start browsing our blog and add posts to your favorites by clicking the heart icon.\"\n )}\n </p>\n <Button asChild size=\"lg\">\n <Link to=\"/blog\">\n <BookOpen className=\"w-5 h-5 mr-2\" />\n {t(\"browseBlog\", \"Browse Blog\")}\n </Link>\n </Button>\n </div>\n </div>\n </div>\n );\n }\n\n // Favorites Grid\n return (\n <div className=\"min-h-screen py-12\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\n {/* Header */}\n <div className=\"flex justify-between items-center mb-8\">\n <div>\n <h1 className=\"text-3xl font-bold mb-2\">\n {t(\"title\", \"My Favorites\")}\n </h1>\n <p className=\"text-muted-foreground\">\n {t(\n \"favoritesCount\",\n `You have ${favorites.length} favorite posts`\n )}\n </p>\n </div>\n {onClearAll && favorites.length > 0 && (\n <Button variant=\"outline\" onClick={onClearAll}>\n {t(\"clearAll\", \"Clear All\")}\n </Button>\n )}\n </div>\n\n {/* Posts Grid */}\n <div className=\"grid gap-6 md:grid-cols-2 lg:grid-cols-3\">\n {favorites.map((post) => (\n <div key={post.id} className=\"contents\" data-db-table=\"posts\" data-db-id={post.id}>\n <PostCard post={post} layout=\"grid\" />\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n"
23
23
  },
24
24
  {
25
25
  "path": "favorites-blog-block/lang/en.json",
@@ -23,7 +23,7 @@
23
23
  "path": "favorites-blog-page/favorites-blog-page.tsx",
24
24
  "type": "registry:page",
25
25
  "target": "$modules$/favorites-blog-page/favorites-blog-page.tsx",
26
- "content": "import { Link } from \"react-router\";\r\nimport { Heart, BookOpen } from \"lucide-react\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { PostCard } from \"@/modules/post-card/post-card\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { useBlog } from \"@/modules/blog-core\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\n\r\nexport function FavoritesBlogPage() {\r\n const { t } = useTranslation(\"favorites-blog-page\");\r\n const { favorites, clearFavorites } = useBlog();\r\n usePageTitle({ title: t(\"title\", \"My Favorites\") });\r\n\r\n // Empty State\r\n if (favorites.length === 0) {\r\n return (\r\n <Layout>\r\n <div className=\"min-h-screen bg-muted/30 py-12\">\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n <div className=\"text-center max-w-md mx-auto\">\r\n <Heart className=\"w-16 h-16 text-muted-foreground mx-auto mb-6\" />\r\n <h1 className=\"text-3xl font-bold text-foreground mb-4\">\r\n {t(\"noFavoritesYet\", \"No Favorites Yet\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-8\">\r\n {t(\r\n \"noFavoritesDescription\",\r\n \"Start browsing our blog and add posts to your favorites by clicking the heart icon.\"\r\n )}\r\n </p>\r\n <Button asChild size=\"lg\">\r\n <Link to=\"/blog\">\r\n <BookOpen className=\"w-5 h-5 mr-2\" />\r\n {t(\"browseBlog\", \"Browse Blog\")}\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 // Favorites Grid\r\n return (\r\n <Layout>\r\n <div className=\"min-h-screen py-12\">\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n {/* Header */}\r\n <div className=\"flex justify-between items-center mb-8\">\r\n <div>\r\n <h1 className=\"text-3xl font-bold mb-2\">\r\n {t(\"title\", \"My Favorites\")}\r\n </h1>\r\n <p className=\"text-muted-foreground\">\r\n {t(\r\n \"favoritesCount\",\r\n `You have ${favorites.length} favorite posts`\r\n )}\r\n </p>\r\n </div>\r\n <Button variant=\"outline\" onClick={clearFavorites}>\r\n {t(\"clearAll\", \"Clear All\")}\r\n </Button>\r\n </div>\r\n\r\n {/* Posts Grid */}\r\n <div className=\"grid gap-6 md:grid-cols-2 lg:grid-cols-3\">\r\n {favorites.map((post) => (\r\n <PostCard key={post.id} post={post} layout=\"grid\" />\r\n ))}\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default FavoritesBlogPage;\r\n"
26
+ "content": "import { Link } from \"react-router\";\r\nimport { Heart, BookOpen } from \"lucide-react\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { PostCard } from \"@/modules/post-card/post-card\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { useBlog } from \"@/modules/blog-core\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\n\r\nexport function FavoritesBlogPage() {\r\n const { t } = useTranslation(\"favorites-blog-page\");\r\n const { favorites, clearFavorites } = useBlog();\r\n usePageTitle({ title: t(\"title\", \"My Favorites\") });\r\n\r\n // Empty State\r\n if (favorites.length === 0) {\r\n return (\r\n <Layout>\r\n <div className=\"min-h-screen bg-muted/30 py-12\">\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n <div className=\"text-center max-w-md mx-auto\">\r\n <Heart className=\"w-16 h-16 text-muted-foreground mx-auto mb-6\" />\r\n <h1 className=\"text-3xl font-bold text-foreground mb-4\">\r\n {t(\"noFavoritesYet\", \"No Favorites Yet\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-8\">\r\n {t(\r\n \"noFavoritesDescription\",\r\n \"Start browsing our blog and add posts to your favorites by clicking the heart icon.\"\r\n )}\r\n </p>\r\n <Button asChild size=\"lg\">\r\n <Link to=\"/blog\">\r\n <BookOpen className=\"w-5 h-5 mr-2\" />\r\n {t(\"browseBlog\", \"Browse Blog\")}\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 // Favorites Grid\r\n return (\r\n <Layout>\r\n <div className=\"min-h-screen py-12\">\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n {/* Header */}\r\n <div className=\"flex justify-between items-center mb-8\">\r\n <div>\r\n <h1 className=\"text-3xl font-bold mb-2\">\r\n {t(\"title\", \"My Favorites\")}\r\n </h1>\r\n <p className=\"text-muted-foreground\">\r\n {t(\r\n \"favoritesCount\",\r\n `You have ${favorites.length} favorite posts`\r\n )}\r\n </p>\r\n </div>\r\n <Button variant=\"outline\" onClick={clearFavorites}>\r\n {t(\"clearAll\", \"Clear All\")}\r\n </Button>\r\n </div>\r\n\r\n {/* Posts Grid */}\r\n <div className=\"grid gap-6 md:grid-cols-2 lg:grid-cols-3\">\r\n {favorites.map((post) => (\r\n <div key={post.id} className=\"contents\" data-db-table=\"posts\" data-db-id={post.id}>\r\n <PostCard post={post} layout=\"grid\" />\r\n </div>\r\n ))}\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default FavoritesBlogPage;\r\n"
27
27
  },
28
28
  {
29
29
  "path": "favorites-blog-page/lang/en.json",
@@ -19,7 +19,7 @@
19
19
  "path": "favorites-ecommerce-block/favorites-ecommerce-block.tsx",
20
20
  "type": "registry:block",
21
21
  "target": "$modules$/favorites-ecommerce-block/favorites-ecommerce-block.tsx",
22
- "content": "import { Link } from \"react-router\";\nimport { Heart, ShoppingBag } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { ProductCard } from \"@/modules/product-card/product-card\";\nimport { useTranslation } from \"react-i18next\";\nimport type { Product } from \"@/modules/ecommerce-core/types\";\n\ninterface FavoritesEcommerceBlockProps {\n favorites: Product[];\n onClearAll?: () => void;\n}\n\nexport function FavoritesEcommerceBlock({\n favorites = [],\n onClearAll,\n}: FavoritesEcommerceBlockProps) {\n const { t } = useTranslation(\"favorites-ecommerce-block\");\n\n // Empty State\n if (favorites.length === 0) {\n return (\n <div className=\"min-h-screen bg-muted/30 py-12\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\n <div className=\"text-center max-w-md mx-auto\">\n <Heart className=\"w-16 h-16 text-muted-foreground mx-auto mb-6\" />\n <h1 className=\"text-3xl font-bold text-foreground mb-4\">\n {t(\"noFavoritesYet\", \"No Favorites Yet\")}\n </h1>\n <p className=\"text-muted-foreground mb-8\">\n {t(\n \"noFavoritesDescription\",\n \"Start browsing our products and add items to your favorites by clicking the heart icon.\"\n )}\n </p>\n <Button asChild size=\"lg\">\n <Link to=\"/products\">\n <ShoppingBag className=\"w-5 h-5 mr-2\" />\n {t(\"browseProducts\", \"Browse Products\")}\n </Link>\n </Button>\n </div>\n </div>\n </div>\n );\n }\n\n // Favorites Grid\n return (\n <div className=\"min-h-screen bg-muted/30 py-12\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\n {/* Header */}\n <div className=\"flex items-center justify-between mb-8\">\n <div>\n <h1 className=\"text-3xl font-bold text-foreground mb-2\">\n {t(\"title\", \"My Favorites\")}\n </h1>\n <p className=\"text-muted-foreground\">\n {favorites.length}{\" \"}\n {t(\n \"itemsInFavorites\",\n `item${favorites.length !== 1 ? \"s\" : \"\"} in your favorites`\n )}\n </p>\n </div>\n {onClearAll && (\n <Button variant=\"outline\" onClick={onClearAll}>\n {t(\"clearAll\", \"Clear All\")}\n </Button>\n )}\n </div>\n\n {/* Products Grid */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6\">\n {favorites.map((product) => (\n <ProductCard key={product.id} product={product} variant=\"grid\" />\n ))}\n </div>\n </div>\n </div>\n );\n}\n"
22
+ "content": "import { Link } from \"react-router\";\nimport { Heart, ShoppingBag } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { ProductCard } from \"@/modules/product-card/product-card\";\nimport { useTranslation } from \"react-i18next\";\nimport type { Product } from \"@/modules/ecommerce-core/types\";\n\ninterface FavoritesEcommerceBlockProps {\n favorites: Product[];\n onClearAll?: () => void;\n}\n\nexport function FavoritesEcommerceBlock({\n favorites = [],\n onClearAll,\n}: FavoritesEcommerceBlockProps) {\n const { t } = useTranslation(\"favorites-ecommerce-block\");\n\n // Empty State\n if (favorites.length === 0) {\n return (\n <div className=\"min-h-screen bg-muted/30 py-12\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\n <div className=\"text-center max-w-md mx-auto\">\n <Heart className=\"w-16 h-16 text-muted-foreground mx-auto mb-6\" />\n <h1 className=\"text-3xl font-bold text-foreground mb-4\">\n {t(\"noFavoritesYet\", \"No Favorites Yet\")}\n </h1>\n <p className=\"text-muted-foreground mb-8\">\n {t(\n \"noFavoritesDescription\",\n \"Start browsing our products and add items to your favorites by clicking the heart icon.\"\n )}\n </p>\n <Button asChild size=\"lg\">\n <Link to=\"/products\">\n <ShoppingBag className=\"w-5 h-5 mr-2\" />\n {t(\"browseProducts\", \"Browse Products\")}\n </Link>\n </Button>\n </div>\n </div>\n </div>\n );\n }\n\n // Favorites Grid\n return (\n <div className=\"min-h-screen bg-muted/30 py-12\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\n {/* Header */}\n <div className=\"flex items-center justify-between mb-8\">\n <div>\n <h1 className=\"text-3xl font-bold text-foreground mb-2\">\n {t(\"title\", \"My Favorites\")}\n </h1>\n <p className=\"text-muted-foreground\">\n {favorites.length}{\" \"}\n {t(\n \"itemsInFavorites\",\n `item${favorites.length !== 1 ? \"s\" : \"\"} in your favorites`\n )}\n </p>\n </div>\n {onClearAll && (\n <Button variant=\"outline\" onClick={onClearAll}>\n {t(\"clearAll\", \"Clear All\")}\n </Button>\n )}\n </div>\n\n {/* Products Grid */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6\">\n {favorites.map((product) => (\n <div key={product.id} className=\"contents\" data-db-table=\"products\" data-db-id={product.id}>\n <ProductCard product={product} variant=\"grid\" />\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n"
23
23
  },
24
24
  {
25
25
  "path": "favorites-ecommerce-block/lang/en.json",