@promakeai/cli 0.3.8 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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\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"
28
28
  },
29
29
  {
30
30
  "path": "checkout-page/lang/en.json",
@@ -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`
@@ -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",
@@ -23,7 +23,7 @@
23
23
  "path": "favorites-ecommerce-page/favorites-ecommerce-page.tsx",
24
24
  "type": "registry:page",
25
25
  "target": "$modules$/favorites-ecommerce-page/favorites-ecommerce-page.tsx",
26
- "content": "import { Link } from \"react-router\";\r\nimport { Heart, ShoppingBag } from \"lucide-react\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { ProductCard } from \"@/modules/product-card/product-card\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { useFavorites } from \"@/modules/ecommerce-core\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\n\r\nexport function FavoritesEcommercePage() {\r\n const { t } = useTranslation(\"favorites-ecommerce-page\");\r\n const { favorites, clearFavorites } = useFavorites();\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 products and add items to your favorites by clicking the heart icon.\"\r\n )}\r\n </p>\r\n <Button asChild size=\"lg\">\r\n <Link to=\"/products\">\r\n <ShoppingBag className=\"w-5 h-5 mr-2\" />\r\n {t(\"browseProducts\", \"Browse Products\")}\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 bg-muted/30 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 items-center justify-between mb-8\">\r\n <div>\r\n <h1 className=\"text-3xl font-bold text-foreground mb-2\">\r\n {t(\"title\", \"My Favorites\")}\r\n </h1>\r\n <p className=\"text-muted-foreground\">\r\n {favorites.length}{\" \"}\r\n {t(\r\n \"itemsInFavorites\",\r\n `item${favorites.length !== 1 ? \"s\" : \"\"} in your favorites`\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 {/* Products Grid */}\r\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6\">\r\n {favorites.map((product) => (\r\n <ProductCard key={product.id} product={product} variant=\"grid\" />\r\n ))}\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default FavoritesEcommercePage;\r\n"
26
+ "content": "import { Link } from \"react-router\";\r\nimport { Heart, ShoppingBag } from \"lucide-react\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { ProductCard } from \"@/modules/product-card/product-card\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { useFavorites } from \"@/modules/ecommerce-core\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\n\r\nexport function FavoritesEcommercePage() {\r\n const { t } = useTranslation(\"favorites-ecommerce-page\");\r\n const { favorites, clearFavorites } = useFavorites();\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 products and add items to your favorites by clicking the heart icon.\"\r\n )}\r\n </p>\r\n <Button asChild size=\"lg\">\r\n <Link to=\"/products\">\r\n <ShoppingBag className=\"w-5 h-5 mr-2\" />\r\n {t(\"browseProducts\", \"Browse Products\")}\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 bg-muted/30 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 items-center justify-between mb-8\">\r\n <div>\r\n <h1 className=\"text-3xl font-bold text-foreground mb-2\">\r\n {t(\"title\", \"My Favorites\")}\r\n </h1>\r\n <p className=\"text-muted-foreground\">\r\n {favorites.length}{\" \"}\r\n {t(\r\n \"itemsInFavorites\",\r\n `item${favorites.length !== 1 ? \"s\" : \"\"} in your favorites`\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 {/* Products Grid */}\r\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6\">\r\n {favorites.map((product) => (\r\n <div key={product.id} className=\"contents\" data-db-table=\"products\" data-db-id={product.id}>\r\n <ProductCard product={product} variant=\"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 FavoritesEcommercePage;\r\n"
27
27
  },
28
28
  {
29
29
  "path": "favorites-ecommerce-page/lang/en.json",
@@ -19,7 +19,7 @@
19
19
  "path": "featured-products/featured-products.tsx",
20
20
  "type": "registry:component",
21
21
  "target": "$modules$/featured-products/featured-products.tsx",
22
- "content": "import { Link } from \"react-router\";\nimport { ArrowRight } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { ProductCard } from \"@/modules/product-card/product-card\";\nimport { useTranslation } from \"react-i18next\";\nimport { useFeaturedProducts } from \"@/modules/ecommerce-core\";\nimport type { Product } from \"@/modules/ecommerce-core/types\";\n\ninterface FeaturedProductsProps {\n products?: Product[];\n loading?: boolean;\n}\n\nexport function FeaturedProducts({\n products: propProducts,\n loading: propLoading,\n}: FeaturedProductsProps) {\n const { t } = useTranslation(\"featured-products\");\n const { products: hookProducts, loading: hookLoading } = useFeaturedProducts();\n\n const products = propProducts ?? hookProducts;\n const loading = propLoading ?? hookLoading;\n\n return (\n <section className=\"py-8 sm:py-12 md:py-16 lg:py-20 bg-background border-t border-border/20 relative\">\n <div className=\"absolute top-0 left-1/2 transform -translate-x-1/2 w-16 sm:w-24 h-px bg-primary/30\"></div>\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', 'Featured Products')}\n </h2>\n <div className=\"w-12 sm:w-16 md:w-20 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 xl:text-xl text-muted-foreground max-w-2xl mx-auto leading-relaxed\">\n {t('subtitle', 'Hand-picked favorites from our collection')}\n </p>\n </div>\n\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6 lg:gap-8 xl:gap-10\">\n {loading ? (\n [...Array(3)].map((_, i) => (\n <div key={i} className=\"animate-pulse group\">\n <div className=\"aspect-square bg-gradient-to-br from-muted to-muted/50 rounded-2xl mb-6\"></div>\n <div className=\"space-y-3\">\n <div className=\"h-6 bg-muted rounded-lg w-3/4\"></div>\n <div className=\"h-4 bg-muted rounded w-1/2\"></div>\n <div className=\"h-5 bg-muted rounded w-2/3\"></div>\n </div>\n </div>\n ))\n ) : (\n products.map((product) => (\n <ProductCard\n key={product.id}\n product={product}\n variant=\"featured\"\n />\n ))\n )}\n </div>\n\n <div className=\"text-center mt-8 sm:mt-12 lg:mt-16\">\n <Button size=\"lg\" asChild className=\"px-6 sm:px-8 py-3 sm:py-4 text-base sm:text-lg\">\n <Link to=\"/products\">\n {t('viewAll', 'View All Products')}\n <ArrowRight className=\"w-4 h-4 sm:w-5 sm:h-5 ml-2\" />\n </Link>\n </Button>\n </div>\n </div>\n </section>\n );\n}\n"
22
+ "content": "import { Link } from \"react-router\";\nimport { ArrowRight } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { ProductCard } from \"@/modules/product-card/product-card\";\nimport { useTranslation } from \"react-i18next\";\nimport { useFeaturedProducts } from \"@/modules/ecommerce-core\";\nimport type { Product } from \"@/modules/ecommerce-core/types\";\n\ninterface FeaturedProductsProps {\n products?: Product[];\n loading?: boolean;\n}\n\nexport function FeaturedProducts({\n products: propProducts,\n loading: propLoading,\n}: FeaturedProductsProps) {\n const { t } = useTranslation(\"featured-products\");\n const { products: hookProducts, loading: hookLoading } = useFeaturedProducts();\n\n const products = propProducts ?? hookProducts;\n const loading = propLoading ?? hookLoading;\n\n return (\n <section className=\"py-8 sm:py-12 md:py-16 lg:py-20 bg-background border-t border-border/20 relative\">\n <div className=\"absolute top-0 left-1/2 transform -translate-x-1/2 w-16 sm:w-24 h-px bg-primary/30\"></div>\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', 'Featured Products')}\n </h2>\n <div className=\"w-12 sm:w-16 md:w-20 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 xl:text-xl text-muted-foreground max-w-2xl mx-auto leading-relaxed\">\n {t('subtitle', 'Hand-picked favorites from our collection')}\n </p>\n </div>\n\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6 lg:gap-8 xl:gap-10\">\n {loading ? (\n [...Array(3)].map((_, i) => (\n <div key={i} className=\"animate-pulse group\">\n <div className=\"aspect-square bg-gradient-to-br from-muted to-muted/50 rounded-2xl mb-6\"></div>\n <div className=\"space-y-3\">\n <div className=\"h-6 bg-muted rounded-lg w-3/4\"></div>\n <div className=\"h-4 bg-muted rounded w-1/2\"></div>\n <div className=\"h-5 bg-muted rounded w-2/3\"></div>\n </div>\n </div>\n ))\n ) : (\n products.map((product) => (\n <div key={product.id} className=\"contents\" data-db-table=\"products\" data-db-id={product.id}>\n <ProductCard\n product={product}\n variant=\"featured\"\n />\n </div>\n ))\n )}\n </div>\n\n <div className=\"text-center mt-8 sm:mt-12 lg:mt-16\">\n <Button size=\"lg\" asChild className=\"px-6 sm:px-8 py-3 sm:py-4 text-base sm:text-lg\">\n <Link to=\"/products\">\n {t('viewAll', 'View All Products')}\n <ArrowRight className=\"w-4 h-4 sm:w-5 sm:h-5 ml-2\" />\n </Link>\n </Button>\n </div>\n </div>\n </section>\n );\n}\n"
23
23
  },
24
24
  {
25
25
  "path": "featured-products/lang/en.json",
@@ -20,7 +20,7 @@
20
20
  "path": "header-ecommerce/header-ecommerce.tsx",
21
21
  "type": "registry:component",
22
22
  "target": "$modules$/header-ecommerce/header-ecommerce.tsx",
23
- "content": "import { useState } from \"react\";\nimport { Link, useNavigate } from \"react-router\";\nimport { ShoppingCart, Menu, Search, Heart, Package, User, LogOut } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Badge } from \"@/components/ui/badge\";\nimport {\n Sheet,\n SheetHeader,\n SheetTitle,\n SheetContent,\n SheetTrigger,\n} from \"@/components/ui/sheet\";\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from \"@/components/ui/dialog\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { Logo } from \"@/components/Logo\";\nimport { useAuth } from \"@/modules/auth-core\";\nimport { CartDrawer } from \"@/modules/cart-drawer\";\nimport { toast } from \"sonner\";\nimport { useTranslation } from \"react-i18next\";\nimport constants from \"@/constants/constants.json\";\nimport type { Product } from \"@/modules/ecommerce-core/types\";\nimport {\n useCart,\n useFavorites,\n useSearch,\n formatPrice,\n} from \"@/modules/ecommerce-core\";\n\nexport function HeaderEcommerce() {\n const [mobileMenuOpen, setMobileMenuOpen] = useState(false);\n const [mobileSearchOpen, setMobileSearchOpen] = useState(false);\n const [desktopSearchOpen, setDesktopSearchOpen] = useState(false);\n const [showResults, setShowResults] = useState(false);\n const { itemCount, state } = useCart();\n const { favoriteCount } = useFavorites();\n const { isAuthenticated, user, logout } = useAuth();\n const navigate = useNavigate();\n const { t } = useTranslation(\"header-ecommerce\");\n\n const handleLogout = () => {\n logout();\n toast.success(t(\"logoutToastTitle\", \"Goodbye!\"), {\n description: t(\"logoutToastDesc\", \"You have been logged out successfully.\"),\n });\n };\n\n const {\n searchTerm,\n setSearchTerm,\n results: searchResults,\n clearSearch,\n } = useSearch();\n\n const handleSearchSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (searchTerm.trim()) {\n navigate(`/products?search=${encodeURIComponent(searchTerm)}`);\n setShowResults(false);\n setDesktopSearchOpen(false);\n clearSearch();\n }\n };\n\n const handleSearchFocus = () => {\n setShowResults(true);\n };\n\n const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n setSearchTerm(e.target.value);\n setShowResults(true);\n };\n\n const navigation = [\n { name: t(\"home\"), href: \"/\" },\n { name: t(\"products\"), href: \"/products\" },\n { name: t(\"about\"), href: \"/about\" },\n { name: t(\"contact\"), href: \"/contact\" },\n ];\n\n return (\n <header className=\"sticky top-0 z-50 w-full border-b border-border/20 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-3 sm:px-4 lg:px-8\">\n <div className=\"flex h-14 sm:h-16 md:h-20 items-center justify-between gap-2\">\n {/* Logo */}\n <div className=\"flex-shrink-0 min-w-0\">\n <Logo size=\"sm\" className=\"text-base sm:text-xl lg:text-2xl\" />\n </div>\n\n {/* Desktop Navigation - Centered */}\n <nav className=\"hidden lg:flex items-center space-x-12 absolute left-1/2 transform -translate-x-1/2\">\n {navigation.map((item) => (\n <Link\n key={item.name}\n to={item.href}\n className=\"text-base font-medium transition-colors hover:text-primary relative group py-2\"\n >\n {item.name}\n <span className=\"absolute -bottom-1 left-0 w-0 h-0.5 bg-primary transition-all duration-300 group-hover:w-full\"></span>\n </Link>\n ))}\n </nav>\n\n {/* Search & Actions - Right Aligned */}\n <div className=\"flex items-center space-x-1 sm:space-x-2 lg:space-x-4 flex-shrink-0\">\n {/* Desktop Search - Modal */}\n <Dialog\n open={desktopSearchOpen}\n onOpenChange={setDesktopSearchOpen}\n >\n <DialogTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"hidden lg:flex h-10 w-10\"\n >\n <Search className=\"h-5 w-5\" />\n </Button>\n </DialogTrigger>\n <DialogContent className=\"sm:max-w-2xl\">\n <DialogHeader>\n <DialogTitle>\n {t(\"searchProducts\", \"Search Products\")}\n </DialogTitle>\n </DialogHeader>\n <div className=\"space-y-4\">\n <form onSubmit={handleSearchSubmit}>\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-5 w-5\" />\n <Input\n type=\"search\"\n placeholder={t(\n \"searchPlaceholder\",\n \"Search for products...\"\n )}\n value={searchTerm}\n onChange={handleSearchChange}\n className=\"pl-11 h-12 text-base\"\n autoFocus\n />\n </div>\n </form>\n\n {/* Desktop Search Results */}\n {searchTerm.trim() && (\n <div className=\"max-h-[400px] overflow-y-auto rounded-lg border bg-card\">\n {searchResults.length > 0 ? (\n <div className=\"divide-y\">\n <div className=\"px-4 py-3 bg-muted/50\">\n <p className=\"text-sm font-medium text-muted-foreground\">\n {searchResults.length}{\" \"}\n {searchResults.length === 1\n ? \"result\"\n : \"results\"}{\" \"}\n found\n </p>\n </div>\n {searchResults.slice(0, 8).map((product: Product) => (\n <Link\n key={product.id}\n to={`/products/${product.slug}`}\n onClick={() => {\n setDesktopSearchOpen(false);\n clearSearch();\n }}\n className=\"flex items-center gap-4 p-4 hover:bg-muted/50 transition-colors\"\n >\n <img\n src={\n product.images[0] || \"/images/placeholder.png\"\n }\n alt={product.name}\n className=\"w-16 h-16 object-cover rounded flex-shrink-0\"\n />\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"font-medium text-base line-clamp-1\">\n {product.name}\n </h4>\n <p className=\"text-sm text-muted-foreground capitalize\">\n {product.category}\n </p>\n <p className=\"text-base font-semibold text-primary mt-1\">\n {formatPrice(\n product.price,\n constants.site.currency\n )}\n </p>\n </div>\n </Link>\n ))}\n {searchResults.length > 8 && (\n <div className=\"px-4 py-3 bg-muted/30 text-center\">\n <button\n onClick={() => {\n navigate(\n `/products?search=${encodeURIComponent(\n searchTerm\n )}`\n );\n setDesktopSearchOpen(false);\n clearSearch();\n }}\n className=\"text-sm font-medium text-primary hover:underline\"\n >\n {t(\n \"viewAllResults\",\n `View all ${searchResults.length} results`\n )}\n </button>\n </div>\n )}\n </div>\n ) : (\n <div className=\"p-8 text-center\">\n <Search className=\"h-12 w-12 text-muted-foreground mx-auto mb-3 opacity-50\" />\n <p className=\"text-base text-muted-foreground\">\n {t(\"noResults\", \"No products found\")}\n </p>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {t(\n \"tryDifferentKeywords\",\n \"Try different keywords\"\n )}\n </p>\n </div>\n )}\n </div>\n )}\n </div>\n </DialogContent>\n </Dialog>\n\n {/* Search - Mobile (Hidden - moved to hamburger menu) */}\n <Dialog open={mobileSearchOpen} onOpenChange={setMobileSearchOpen}>\n <DialogTrigger asChild>\n <Button variant=\"ghost\" size=\"icon\" className=\"hidden\">\n <Search className=\"h-4 w-4 sm:h-5 sm:w-5\" />\n </Button>\n </DialogTrigger>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>{t(\"searchProducts\")}</DialogTitle>\n </DialogHeader>\n <form\n onSubmit={(e) => {\n e.preventDefault();\n if (searchTerm.trim()) {\n navigate(\n `/products?search=${encodeURIComponent(searchTerm)}`\n );\n setMobileSearchOpen(false);\n clearSearch();\n }\n }}\n className=\"space-y-4\"\n >\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4\" />\n <Input\n type=\"search\"\n placeholder={t(\"searchPlaceholder\")}\n value={searchTerm}\n onChange={(e) => setSearchTerm(e.target.value)}\n className=\"pl-10\"\n autoFocus\n />\n </div>\n <div className=\"flex gap-2\">\n <Button type=\"submit\" className=\"flex-1\">\n {t(\"searchButton\", \"Search\")}\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => {\n clearSearch();\n setMobileSearchOpen(false);\n }}\n >\n {t(\"cancel\", \"Cancel\")}\n </Button>\n </div>\n </form>\n\n {/* Mobile Search Results */}\n {searchTerm.trim() && (\n <div className=\"mt-4 max-h-64 overflow-y-auto\">\n {searchResults.length > 0 ? (\n <div className=\"space-y-2\">\n <p className=\"text-sm text-muted-foreground mb-2\">\n {searchResults.length} result\n {searchResults.length !== 1 ? \"s\" : \"\"} found\n </p>\n {searchResults.slice(0, 5).map((product: Product) => (\n <Link\n key={product.id}\n to={`/products/${product.slug}`}\n onClick={() => {\n setMobileSearchOpen(false);\n clearSearch();\n }}\n className=\"block p-2 rounded hover:bg-muted/50 transition-colors\"\n >\n <div className=\"flex items-center gap-3\">\n <img\n src={\n product.images[0] || \"/images/placeholder.png\"\n }\n alt={product.name}\n className=\"w-10 h-10 object-cover rounded\"\n />\n <div className=\"flex-1\">\n <h4 className=\"font-medium text-sm\">\n {product.name}\n </h4>\n <p className=\"text-xs text-muted-foreground\">\n {product.category}\n </p>\n <p className=\"text-sm font-medium\">\n {formatPrice(\n product.price,\n constants.site.currency\n )}\n </p>\n </div>\n </div>\n </Link>\n ))}\n </div>\n ) : (\n <p className=\"text-sm text-muted-foreground\">\n {t(\"noResults\")}\n </p>\n )}\n </div>\n )}\n </DialogContent>\n </Dialog>\n\n {/* Wishlist - Desktop Only */}\n <Link to=\"/favorites\" className=\"hidden lg:block\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"relative h-10 w-10\"\n >\n <Heart className=\"h-5 w-5\" />\n {favoriteCount > 0 && (\n <Badge\n variant=\"destructive\"\n className=\"absolute -top-1 -right-1 h-4 w-4 flex items-center justify-center p-0 text-[10px]\"\n >\n {favoriteCount}\n </Badge>\n )}\n </Button>\n </Link>\n\n {/* Cart - Desktop Only (Goes to Cart Page) */}\n <Link to=\"/cart\" className=\"hidden lg:block\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"relative h-10 w-10\"\n >\n <ShoppingCart className=\"h-5 w-5\" />\n {itemCount > 0 && (\n <Badge\n variant=\"destructive\"\n className=\"absolute -top-1 -right-1 h-4 w-4 flex items-center justify-center p-0 text-[10px]\"\n >\n {itemCount}\n </Badge>\n )}\n </Button>\n </Link>\n\n {/* Auth - Desktop Only */}\n <div className=\"hidden lg:flex\">\n {isAuthenticated ? (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\" size=\"icon\" className=\"h-10 w-10\">\n <User className=\"h-5 w-5\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-56\">\n <DropdownMenuLabel className=\"font-normal\">\n <div className=\"flex flex-col space-y-1\">\n <p className=\"text-sm font-medium\">{user?.username}</p>\n {user?.email && (\n <p className=\"text-xs text-muted-foreground\">{user.email}</p>\n )}\n </div>\n </DropdownMenuLabel>\n <DropdownMenuSeparator />\n <DropdownMenuItem asChild className=\"cursor-pointer\">\n <Link to=\"/my-orders\" className=\"flex items-center\">\n <Package className=\"mr-2 h-4 w-4\" />\n {t(\"myOrders\", \"My Orders\")}\n </Link>\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n onClick={handleLogout}\n className=\"text-red-600 focus:text-red-600 focus:bg-red-50 cursor-pointer\"\n >\n <LogOut className=\"mr-2 h-4 w-4\" />\n {t(\"logout\", \"Logout\")}\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n ) : (\n <Link to=\"/login\">\n <Button variant=\"ghost\" size=\"icon\" className=\"h-10 w-10\">\n <User className=\"h-5 w-5\" />\n </Button>\n </Link>\n )}\n </div>\n\n {/* Mobile Menu */}\n <Sheet open={mobileMenuOpen} onOpenChange={setMobileMenuOpen}>\n <SheetTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"lg:hidden h-8 w-8 sm:h-10 sm:w-10\"\n >\n <Menu className=\"h-4 w-4 sm:h-5 sm:w-5\" />\n </Button>\n </SheetTrigger>\n <SheetContent side=\"right\" className=\"w-[300px] sm:w-[400px] px-6\">\n <SheetHeader>\n <SheetTitle>{t(\"menu\")}</SheetTitle>\n </SheetHeader>\n\n {/* Mobile Search in Hamburger */}\n <div className=\"mt-6 pb-4 border-b\">\n <form onSubmit={handleSearchSubmit}>\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4\" />\n <Input\n type=\"search\"\n placeholder={t(\"searchPlaceholder\")}\n value={searchTerm}\n onChange={handleSearchChange}\n onFocus={handleSearchFocus}\n className=\"pl-10 h-11\"\n />\n </div>\n </form>\n\n {/* Search Results in Hamburger */}\n {showResults && searchTerm && (\n <div className=\"mt-3 max-h-[300px] overflow-y-auto rounded-lg border bg-card\">\n {searchResults.length > 0 ? (\n <div className=\"divide-y\">\n <div className=\"px-3 py-2 bg-muted/50\">\n <p className=\"text-xs font-medium text-muted-foreground\">\n {searchResults.length}{\" \"}\n {searchResults.length === 1\n ? \"result\"\n : \"results\"}\n </p>\n </div>\n {searchResults.slice(0, 5).map((product: Product) => (\n <Link\n key={product.id}\n to={`/products/${product.slug}`}\n onClick={() => {\n setMobileMenuOpen(false);\n clearSearch();\n setShowResults(false);\n }}\n className=\"flex items-center gap-3 p-3 hover:bg-muted/50 transition-colors\"\n >\n <img\n src={\n product.images[0] || \"/images/placeholder.png\"\n }\n alt={product.name}\n className=\"w-14 h-14 object-cover rounded flex-shrink-0\"\n />\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"font-medium text-sm line-clamp-1\">\n {product.name}\n </h4>\n <p className=\"text-xs text-muted-foreground capitalize\">\n {product.category}\n </p>\n <p className=\"text-sm font-semibold text-primary mt-1\">\n {formatPrice(\n product.price,\n constants.site.currency\n )}\n </p>\n </div>\n </Link>\n ))}\n {searchResults.length > 5 && (\n <div className=\"px-3 py-2 bg-muted/30 text-center\">\n <button\n onClick={() => {\n navigate(\n `/products?search=${encodeURIComponent(\n searchTerm\n )}`\n );\n setMobileMenuOpen(false);\n clearSearch();\n setShowResults(false);\n }}\n className=\"text-xs font-medium text-primary hover:underline\"\n >\n {t(\n \"viewAllResults\",\n `View all ${searchResults.length} results`\n )}\n </button>\n </div>\n )}\n </div>\n ) : (\n <div className=\"p-6 text-center\">\n <Search className=\"h-8 w-8 text-muted-foreground mx-auto mb-2 opacity-50\" />\n <p className=\"text-sm text-muted-foreground\">\n {t(\"noResults\", \"No results found\")}\n </p>\n </div>\n )}\n </div>\n )}\n </div>\n\n <div className=\"flex flex-col space-y-4 mt-6\">\n {navigation.map((item) => (\n <Link\n key={item.name}\n to={item.href}\n className=\"text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n {item.name}\n </Link>\n ))}\n <div className=\"border-t pt-4 space-y-4\">\n <Link\n to=\"/favorites\"\n className=\"flex items-center justify-between text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n <div className=\"flex items-center space-x-2\">\n <Heart className=\"h-5 w-5\" />\n <span>{t(\"favorites\")}</span>\n </div>\n <Badge variant=\"secondary\">{favoriteCount}</Badge>\n </Link>\n <Link\n to=\"/cart\"\n className=\"flex items-center justify-between w-full text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n <div className=\"flex items-center space-x-2\">\n <ShoppingCart className=\"h-5 w-5\" />\n <span>{t(\"cart\")}</span>\n </div>\n <div className=\"flex flex-col items-end\">\n <Badge variant=\"secondary\">{itemCount}</Badge>\n <span className=\"text-xs text-muted-foreground\">\n {formatPrice(state.total, constants.site.currency)}\n </span>\n </div>\n </Link>\n\n {/* Auth - Mobile */}\n {isAuthenticated ? (\n <div className=\"space-y-3\">\n <div className=\"flex items-center space-x-3 p-3 bg-muted/50 rounded-lg\">\n <div className=\"h-10 w-10 rounded-full bg-primary/10 flex items-center justify-center\">\n <User className=\"h-5 w-5 text-primary\" />\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium truncate\">{user?.username}</p>\n {user?.email && (\n <p className=\"text-xs text-muted-foreground truncate\">{user.email}</p>\n )}\n </div>\n </div>\n <Link\n to=\"/my-orders\"\n className=\"flex items-center space-x-2 text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n <Package className=\"h-5 w-5\" />\n <span>{t(\"myOrders\", \"My Orders\")}</span>\n </Link>\n <button\n onClick={() => {\n handleLogout();\n setMobileMenuOpen(false);\n }}\n className=\"flex items-center space-x-2 text-lg font-medium text-red-600 hover:text-red-700 transition-colors w-full\"\n >\n <LogOut className=\"h-5 w-5\" />\n <span>{t(\"logout\", \"Logout\")}</span>\n </button>\n </div>\n ) : (\n <Link\n to=\"/login\"\n className=\"flex items-center space-x-2 text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n <User className=\"h-5 w-5\" />\n <span>{t(\"login\", \"Login\")}</span>\n </Link>\n )}\n </div>\n </div>\n </SheetContent>\n </Sheet>\n </div>\n </div>\n </div>\n {/* Cart Drawer */}\n <CartDrawer showTrigger={false} />\n </header>\n );\n}\n"
23
+ "content": "import { useState } from \"react\";\nimport { Link, useNavigate } from \"react-router\";\nimport { ShoppingCart, Menu, Search, Heart, Package, User, LogOut } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Badge } from \"@/components/ui/badge\";\nimport {\n Sheet,\n SheetHeader,\n SheetTitle,\n SheetContent,\n SheetTrigger,\n} from \"@/components/ui/sheet\";\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from \"@/components/ui/dialog\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { Logo } from \"@/components/Logo\";\nimport { useAuth } from \"@/modules/auth-core\";\nimport { CartDrawer } from \"@/modules/cart-drawer\";\nimport { toast } from \"sonner\";\nimport { useTranslation } from \"react-i18next\";\nimport constants from \"@/constants/constants.json\";\nimport type { Product } from \"@/modules/ecommerce-core/types\";\nimport {\n useCart,\n useFavorites,\n useSearch,\n formatPrice,\n} from \"@/modules/ecommerce-core\";\n\nexport function HeaderEcommerce() {\n const [mobileMenuOpen, setMobileMenuOpen] = useState(false);\n const [mobileSearchOpen, setMobileSearchOpen] = useState(false);\n const [desktopSearchOpen, setDesktopSearchOpen] = useState(false);\n const [showResults, setShowResults] = useState(false);\n const { itemCount, state } = useCart();\n const { favoriteCount } = useFavorites();\n const { isAuthenticated, user, logout } = useAuth();\n const navigate = useNavigate();\n const { t } = useTranslation(\"header-ecommerce\");\n\n const handleLogout = () => {\n logout();\n toast.success(t(\"logoutToastTitle\", \"Goodbye!\"), {\n description: t(\"logoutToastDesc\", \"You have been logged out successfully.\"),\n });\n };\n\n const {\n searchTerm,\n setSearchTerm,\n results: searchResults,\n clearSearch,\n } = useSearch();\n\n const handleSearchSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (searchTerm.trim()) {\n navigate(`/products?search=${encodeURIComponent(searchTerm)}`);\n setShowResults(false);\n setDesktopSearchOpen(false);\n clearSearch();\n }\n };\n\n const handleSearchFocus = () => {\n setShowResults(true);\n };\n\n const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n setSearchTerm(e.target.value);\n setShowResults(true);\n };\n\n const navigation = [\n { name: t(\"home\"), href: \"/\" },\n { name: t(\"products\"), href: \"/products\" },\n { name: t(\"about\"), href: \"/about\" },\n { name: t(\"contact\"), href: \"/contact\" },\n ];\n\n return (\n <header className=\"sticky top-0 z-50 w-full border-b border-border/20 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-3 sm:px-4 lg:px-8\">\n <div className=\"flex h-14 sm:h-16 md:h-20 items-center justify-between gap-2\">\n {/* Logo */}\n <div className=\"flex-shrink-0 min-w-0\">\n <Logo size=\"sm\" className=\"text-base sm:text-xl lg:text-2xl\" />\n </div>\n\n {/* Desktop Navigation - Centered */}\n <nav className=\"hidden lg:flex items-center space-x-12 absolute left-1/2 transform -translate-x-1/2\">\n {navigation.map((item) => (\n <Link\n key={item.name}\n to={item.href}\n className=\"text-base font-medium transition-colors hover:text-primary relative group py-2\"\n >\n {item.name}\n <span className=\"absolute -bottom-1 left-0 w-0 h-0.5 bg-primary transition-all duration-300 group-hover:w-full\"></span>\n </Link>\n ))}\n </nav>\n\n {/* Search & Actions - Right Aligned */}\n <div className=\"flex items-center space-x-1 sm:space-x-2 lg:space-x-4 flex-shrink-0\">\n {/* Desktop Search - Modal */}\n <Dialog\n open={desktopSearchOpen}\n onOpenChange={setDesktopSearchOpen}\n >\n <DialogTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"hidden lg:flex h-10 w-10\"\n >\n <Search className=\"h-5 w-5\" />\n </Button>\n </DialogTrigger>\n <DialogContent className=\"sm:max-w-2xl\">\n <DialogHeader>\n <DialogTitle>\n {t(\"searchProducts\", \"Search Products\")}\n </DialogTitle>\n </DialogHeader>\n <div className=\"space-y-4\">\n <form onSubmit={handleSearchSubmit}>\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-5 w-5\" />\n <Input\n type=\"search\"\n placeholder={t(\n \"searchPlaceholder\",\n \"Search for products...\"\n )}\n value={searchTerm}\n onChange={handleSearchChange}\n className=\"pl-11 h-12 text-base\"\n autoFocus\n />\n </div>\n </form>\n\n {/* Desktop Search Results */}\n {searchTerm.trim() && (\n <div className=\"max-h-[400px] overflow-y-auto rounded-lg border bg-card\">\n {searchResults.length > 0 ? (\n <div className=\"divide-y\">\n <div className=\"px-4 py-3 bg-muted/50\">\n <p className=\"text-sm font-medium text-muted-foreground\">\n {searchResults.length}{\" \"}\n {searchResults.length === 1\n ? \"result\"\n : \"results\"}{\" \"}\n found\n </p>\n </div>\n {searchResults.slice(0, 8).map((product: Product) => (\n <div key={product.id} className=\"contents\" data-db-table=\"products\" data-db-id={product.id}>\n <Link\n to={`/products/${product.slug}`}\n onClick={() => {\n setDesktopSearchOpen(false);\n clearSearch();\n }}\n className=\"flex items-center gap-4 p-4 hover:bg-muted/50 transition-colors\"\n >\n <img\n src={\n product.images[0] || \"/images/placeholder.png\"\n }\n alt={product.name}\n className=\"w-16 h-16 object-cover rounded flex-shrink-0\"\n />\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"font-medium text-base line-clamp-1\">\n {product.name}\n </h4>\n <p className=\"text-sm text-muted-foreground capitalize\">\n {product.category}\n </p>\n <p className=\"text-base font-semibold text-primary mt-1\">\n {formatPrice(\n product.price,\n constants.site.currency\n )}\n </p>\n </div>\n </Link>\n </div>\n ))}\n {searchResults.length > 8 && (\n <div className=\"px-4 py-3 bg-muted/30 text-center\">\n <button\n onClick={() => {\n navigate(\n `/products?search=${encodeURIComponent(\n searchTerm\n )}`\n );\n setDesktopSearchOpen(false);\n clearSearch();\n }}\n className=\"text-sm font-medium text-primary hover:underline\"\n >\n {t(\n \"viewAllResults\",\n `View all ${searchResults.length} results`\n )}\n </button>\n </div>\n )}\n </div>\n ) : (\n <div className=\"p-8 text-center\">\n <Search className=\"h-12 w-12 text-muted-foreground mx-auto mb-3 opacity-50\" />\n <p className=\"text-base text-muted-foreground\">\n {t(\"noResults\", \"No products found\")}\n </p>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {t(\n \"tryDifferentKeywords\",\n \"Try different keywords\"\n )}\n </p>\n </div>\n )}\n </div>\n )}\n </div>\n </DialogContent>\n </Dialog>\n\n {/* Search - Mobile (Hidden - moved to hamburger menu) */}\n <Dialog open={mobileSearchOpen} onOpenChange={setMobileSearchOpen}>\n <DialogTrigger asChild>\n <Button variant=\"ghost\" size=\"icon\" className=\"hidden\">\n <Search className=\"h-4 w-4 sm:h-5 sm:w-5\" />\n </Button>\n </DialogTrigger>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>{t(\"searchProducts\")}</DialogTitle>\n </DialogHeader>\n <form\n onSubmit={(e) => {\n e.preventDefault();\n if (searchTerm.trim()) {\n navigate(\n `/products?search=${encodeURIComponent(searchTerm)}`\n );\n setMobileSearchOpen(false);\n clearSearch();\n }\n }}\n className=\"space-y-4\"\n >\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4\" />\n <Input\n type=\"search\"\n placeholder={t(\"searchPlaceholder\")}\n value={searchTerm}\n onChange={(e) => setSearchTerm(e.target.value)}\n className=\"pl-10\"\n autoFocus\n />\n </div>\n <div className=\"flex gap-2\">\n <Button type=\"submit\" className=\"flex-1\">\n {t(\"searchButton\", \"Search\")}\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => {\n clearSearch();\n setMobileSearchOpen(false);\n }}\n >\n {t(\"cancel\", \"Cancel\")}\n </Button>\n </div>\n </form>\n\n {/* Mobile Search Results */}\n {searchTerm.trim() && (\n <div className=\"mt-4 max-h-64 overflow-y-auto\">\n {searchResults.length > 0 ? (\n <div className=\"space-y-2\">\n <p className=\"text-sm text-muted-foreground mb-2\">\n {searchResults.length} result\n {searchResults.length !== 1 ? \"s\" : \"\"} found\n </p>\n {searchResults.slice(0, 5).map((product: Product) => (\n <div key={product.id} className=\"contents\" data-db-table=\"products\" data-db-id={product.id}>\n <Link\n to={`/products/${product.slug}`}\n onClick={() => {\n setMobileSearchOpen(false);\n clearSearch();\n }}\n className=\"block p-2 rounded hover:bg-muted/50 transition-colors\"\n >\n <div className=\"flex items-center gap-3\">\n <img\n src={\n product.images[0] || \"/images/placeholder.png\"\n }\n alt={product.name}\n className=\"w-10 h-10 object-cover rounded\"\n />\n <div className=\"flex-1\">\n <h4 className=\"font-medium text-sm\">\n {product.name}\n </h4>\n <p className=\"text-xs text-muted-foreground\">\n {product.category}\n </p>\n <p className=\"text-sm font-medium\">\n {formatPrice(\n product.price,\n constants.site.currency\n )}\n </p>\n </div>\n </div>\n </Link>\n </div>\n ))}\n </div>\n ) : (\n <p className=\"text-sm text-muted-foreground\">\n {t(\"noResults\")}\n </p>\n )}\n </div>\n )}\n </DialogContent>\n </Dialog>\n\n {/* Wishlist - Desktop Only */}\n <Link to=\"/favorites\" className=\"hidden lg:block\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"relative h-10 w-10\"\n >\n <Heart className=\"h-5 w-5\" />\n {favoriteCount > 0 && (\n <Badge\n variant=\"destructive\"\n className=\"absolute -top-1 -right-1 h-4 w-4 flex items-center justify-center p-0 text-[10px]\"\n >\n {favoriteCount}\n </Badge>\n )}\n </Button>\n </Link>\n\n {/* Cart - Desktop Only (Goes to Cart Page) */}\n <Link to=\"/cart\" className=\"hidden lg:block\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"relative h-10 w-10\"\n >\n <ShoppingCart className=\"h-5 w-5\" />\n {itemCount > 0 && (\n <Badge\n variant=\"destructive\"\n className=\"absolute -top-1 -right-1 h-4 w-4 flex items-center justify-center p-0 text-[10px]\"\n >\n {itemCount}\n </Badge>\n )}\n </Button>\n </Link>\n\n {/* Auth - Desktop Only */}\n <div className=\"hidden lg:flex\">\n {isAuthenticated ? (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\" size=\"icon\" className=\"h-10 w-10\">\n <User className=\"h-5 w-5\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-56\">\n <DropdownMenuLabel className=\"font-normal\">\n <div className=\"flex flex-col space-y-1\">\n <p className=\"text-sm font-medium\">{user?.username}</p>\n {user?.email && (\n <p className=\"text-xs text-muted-foreground\">{user.email}</p>\n )}\n </div>\n </DropdownMenuLabel>\n <DropdownMenuSeparator />\n <DropdownMenuItem asChild className=\"cursor-pointer\">\n <Link to=\"/my-orders\" className=\"flex items-center\">\n <Package className=\"mr-2 h-4 w-4\" />\n {t(\"myOrders\", \"My Orders\")}\n </Link>\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n onClick={handleLogout}\n className=\"text-red-600 focus:text-red-600 focus:bg-red-50 cursor-pointer\"\n >\n <LogOut className=\"mr-2 h-4 w-4\" />\n {t(\"logout\", \"Logout\")}\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n ) : (\n <Link to=\"/login\">\n <Button variant=\"ghost\" size=\"icon\" className=\"h-10 w-10\">\n <User className=\"h-5 w-5\" />\n </Button>\n </Link>\n )}\n </div>\n\n {/* Mobile Menu */}\n <Sheet open={mobileMenuOpen} onOpenChange={setMobileMenuOpen}>\n <SheetTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"lg:hidden h-8 w-8 sm:h-10 sm:w-10\"\n >\n <Menu className=\"h-4 w-4 sm:h-5 sm:w-5\" />\n </Button>\n </SheetTrigger>\n <SheetContent side=\"right\" className=\"w-[300px] sm:w-[400px] px-6\">\n <SheetHeader>\n <SheetTitle>{t(\"menu\")}</SheetTitle>\n </SheetHeader>\n\n {/* Mobile Search in Hamburger */}\n <div className=\"mt-6 pb-4 border-b\">\n <form onSubmit={handleSearchSubmit}>\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4\" />\n <Input\n type=\"search\"\n placeholder={t(\"searchPlaceholder\")}\n value={searchTerm}\n onChange={handleSearchChange}\n onFocus={handleSearchFocus}\n className=\"pl-10 h-11\"\n />\n </div>\n </form>\n\n {/* Search Results in Hamburger */}\n {showResults && searchTerm && (\n <div className=\"mt-3 max-h-[300px] overflow-y-auto rounded-lg border bg-card\">\n {searchResults.length > 0 ? (\n <div className=\"divide-y\">\n <div className=\"px-3 py-2 bg-muted/50\">\n <p className=\"text-xs font-medium text-muted-foreground\">\n {searchResults.length}{\" \"}\n {searchResults.length === 1\n ? \"result\"\n : \"results\"}\n </p>\n </div>\n {searchResults.slice(0, 5).map((product: Product) => (\n <div key={product.id} className=\"contents\" data-db-table=\"products\" data-db-id={product.id}>\n <Link\n to={`/products/${product.slug}`}\n onClick={() => {\n setMobileMenuOpen(false);\n clearSearch();\n setShowResults(false);\n }}\n className=\"flex items-center gap-3 p-3 hover:bg-muted/50 transition-colors\"\n >\n <img\n src={\n product.images[0] || \"/images/placeholder.png\"\n }\n alt={product.name}\n className=\"w-14 h-14 object-cover rounded flex-shrink-0\"\n />\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"font-medium text-sm line-clamp-1\">\n {product.name}\n </h4>\n <p className=\"text-xs text-muted-foreground capitalize\">\n {product.category}\n </p>\n <p className=\"text-sm font-semibold text-primary mt-1\">\n {formatPrice(\n product.price,\n constants.site.currency\n )}\n </p>\n </div>\n </Link>\n </div>\n ))}\n {searchResults.length > 5 && (\n <div className=\"px-3 py-2 bg-muted/30 text-center\">\n <button\n onClick={() => {\n navigate(\n `/products?search=${encodeURIComponent(\n searchTerm\n )}`\n );\n setMobileMenuOpen(false);\n clearSearch();\n setShowResults(false);\n }}\n className=\"text-xs font-medium text-primary hover:underline\"\n >\n {t(\n \"viewAllResults\",\n `View all ${searchResults.length} results`\n )}\n </button>\n </div>\n )}\n </div>\n ) : (\n <div className=\"p-6 text-center\">\n <Search className=\"h-8 w-8 text-muted-foreground mx-auto mb-2 opacity-50\" />\n <p className=\"text-sm text-muted-foreground\">\n {t(\"noResults\", \"No results found\")}\n </p>\n </div>\n )}\n </div>\n )}\n </div>\n\n <div className=\"flex flex-col space-y-4 mt-6\">\n {navigation.map((item) => (\n <Link\n key={item.name}\n to={item.href}\n className=\"text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n {item.name}\n </Link>\n ))}\n <div className=\"border-t pt-4 space-y-4\">\n <Link\n to=\"/favorites\"\n className=\"flex items-center justify-between text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n <div className=\"flex items-center space-x-2\">\n <Heart className=\"h-5 w-5\" />\n <span>{t(\"favorites\")}</span>\n </div>\n <Badge variant=\"secondary\">{favoriteCount}</Badge>\n </Link>\n <Link\n to=\"/cart\"\n className=\"flex items-center justify-between w-full text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n <div className=\"flex items-center space-x-2\">\n <ShoppingCart className=\"h-5 w-5\" />\n <span>{t(\"cart\")}</span>\n </div>\n <div className=\"flex flex-col items-end\">\n <Badge variant=\"secondary\">{itemCount}</Badge>\n <span className=\"text-xs text-muted-foreground\">\n {formatPrice(state.total, constants.site.currency)}\n </span>\n </div>\n </Link>\n\n {/* Auth - Mobile */}\n {isAuthenticated ? (\n <div className=\"space-y-3\">\n <div className=\"flex items-center space-x-3 p-3 bg-muted/50 rounded-lg\">\n <div className=\"h-10 w-10 rounded-full bg-primary/10 flex items-center justify-center\">\n <User className=\"h-5 w-5 text-primary\" />\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium truncate\">{user?.username}</p>\n {user?.email && (\n <p className=\"text-xs text-muted-foreground truncate\">{user.email}</p>\n )}\n </div>\n </div>\n <Link\n to=\"/my-orders\"\n className=\"flex items-center space-x-2 text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n <Package className=\"h-5 w-5\" />\n <span>{t(\"myOrders\", \"My Orders\")}</span>\n </Link>\n <button\n onClick={() => {\n handleLogout();\n setMobileMenuOpen(false);\n }}\n className=\"flex items-center space-x-2 text-lg font-medium text-red-600 hover:text-red-700 transition-colors w-full\"\n >\n <LogOut className=\"h-5 w-5\" />\n <span>{t(\"logout\", \"Logout\")}</span>\n </button>\n </div>\n ) : (\n <Link\n to=\"/login\"\n className=\"flex items-center space-x-2 text-lg font-medium hover:text-primary transition-colors\"\n onClick={() => setMobileMenuOpen(false)}\n >\n <User className=\"h-5 w-5\" />\n <span>{t(\"login\", \"Login\")}</span>\n </Link>\n )}\n </div>\n </div>\n </SheetContent>\n </Sheet>\n </div>\n </div>\n </div>\n {/* Cart Drawer */}\n <CartDrawer showTrigger={false} />\n </header>\n );\n}\n"
24
24
  },
25
25
  {
26
26
  "path": "header-ecommerce/lang/en.json",