@promakeai/cli 0.0.5 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/dist/index.js +214 -135
  2. package/dist/registry/about-page.json +1 -1
  3. package/dist/registry/about-section.json +1 -1
  4. package/dist/registry/api.json +55 -0
  5. package/dist/registry/auth.json +70 -0
  6. package/dist/registry/bento-grid-section.json +1 -1
  7. package/dist/registry/blog-list-page.json +1 -1
  8. package/dist/registry/blog-section.json +1 -1
  9. package/dist/registry/cart-drawer.json +1 -1
  10. package/dist/registry/cart-page.json +3 -2
  11. package/dist/registry/category-section.json +1 -1
  12. package/dist/registry/checkout-page.json +3 -2
  13. package/dist/registry/contact-info-grid.json +1 -1
  14. package/dist/registry/contact-page-centered.json +1 -1
  15. package/dist/registry/contact-page-map-overlay.json +1 -1
  16. package/dist/registry/contact-page.json +1 -1
  17. package/dist/registry/cookies-page.json +1 -1
  18. package/dist/registry/cta-section.json +1 -1
  19. package/dist/registry/db.json +129 -0
  20. package/dist/registry/docs/cart-page.md +1 -0
  21. package/dist/registry/docs/checkout-page.md +1 -0
  22. package/dist/registry/docs/forgot-password-page.md +37 -0
  23. package/dist/registry/docs/header-ecommerce.md +1 -0
  24. package/dist/registry/docs/products-page.md +1 -0
  25. package/dist/registry/docs/register-page.md +39 -0
  26. package/dist/registry/ecommerce-core.json +1 -1
  27. package/dist/registry/empty-page.json +1 -1
  28. package/dist/registry/faq-categorized.json +1 -1
  29. package/dist/registry/faq-simple.json +1 -1
  30. package/dist/registry/favorites-blog-block.json +1 -1
  31. package/dist/registry/favorites-ecommerce-block.json +1 -1
  32. package/dist/registry/feature-section.json +1 -1
  33. package/dist/registry/featured-products.json +1 -1
  34. package/dist/registry/footer-detailed.json +1 -1
  35. package/dist/registry/footer-minimal.json +3 -3
  36. package/dist/registry/footer.json +1 -1
  37. package/dist/registry/forgot-password-page.json +49 -0
  38. package/dist/registry/header-ecommerce.json +3 -2
  39. package/dist/registry/header-mega.json +1 -1
  40. package/dist/registry/header-minimal.json +1 -1
  41. package/dist/registry/header-simple.json +1 -1
  42. package/dist/registry/hero-cta.json +1 -1
  43. package/dist/registry/hero-gradient.json +1 -1
  44. package/dist/registry/hero-profile.json +1 -1
  45. package/dist/registry/hero.json +1 -1
  46. package/dist/registry/index.json +3 -0
  47. package/dist/registry/orders-list-block.json +1 -1
  48. package/dist/registry/payment-success-block.json +1 -1
  49. package/dist/registry/post-detail-block.json +1 -1
  50. package/dist/registry/pricing-section.json +1 -1
  51. package/dist/registry/privacy-page.json +1 -1
  52. package/dist/registry/products-page.json +3 -2
  53. package/dist/registry/register-page.json +49 -0
  54. package/dist/registry/related-posts-block.json +1 -1
  55. package/dist/registry/terms-page.json +1 -1
  56. package/dist/registry/testimonials-carousel.json +1 -1
  57. package/dist/registry/testimonials-grid.json +1 -1
  58. package/package.json +1 -1
  59. package/template/src/App.tsx +3 -24
  60. package/template/src/components/Layout.tsx +0 -4
  61. package/template/src/index.css +1 -0
  62. package/template/src/lang/en/index.json +1 -28
  63. package/template/src/lang/tr/index.json +1 -28
  64. package/template/src/pages/Index.tsx +1 -102
  65. package/template/src/components/Footer.tsx +0 -100
  66. package/template/src/components/Header.tsx +0 -79
  67. package/template/src/components/Hero.tsx +0 -69
  68. package/template/src/modules/api/USAGE.md +0 -515
  69. package/template/src/modules/api/customer-client.ts +0 -20
  70. package/template/src/modules/api/get-error-message.ts +0 -18
  71. package/template/src/modules/api/validation/en.json +0 -29
  72. package/template/src/modules/api/validation/tr.json +0 -29
  73. package/template/src/modules/auth/USAGE.md +0 -248
  74. package/template/src/modules/auth/auth-header-menu.tsx +0 -123
  75. package/template/src/modules/auth/auth-store.ts +0 -57
  76. package/template/src/modules/auth/forgot-password-page.tsx +0 -371
  77. package/template/src/modules/auth/login-page.tsx +0 -183
  78. package/template/src/modules/auth/register-page.tsx +0 -252
  79. package/template/src/modules/auth/use-auth.ts +0 -273
  80. package/template/src/modules/db/adapters/IDataAdapter.ts +0 -26
  81. package/template/src/modules/db/adapters/SqliteAdapter.ts +0 -364
  82. package/template/src/modules/db/adapters/index.ts +0 -2
  83. package/template/src/modules/db/config.ts +0 -59
  84. package/template/src/modules/db/core/DataManager.ts +0 -125
  85. package/template/src/modules/db/core/types.ts +0 -101
  86. package/template/src/modules/db/index.ts +0 -42
  87. package/template/src/modules/db/react/QueryProvider.tsx +0 -16
  88. package/template/src/modules/db/react/index.ts +0 -23
  89. package/template/src/modules/db/react/queryClient.ts +0 -64
  90. package/template/src/modules/db/react/useRepository.ts +0 -400
  91. package/template/src/modules/db/utils/parsers.ts +0 -96
@@ -16,7 +16,7 @@
16
16
  "path": "category-section/category-section.tsx",
17
17
  "type": "registry:component",
18
18
  "target": "$modules$/category-section/category-section.tsx",
19
- "content": "import { Link } from \"react-router\";\nimport { ArrowRight } from \"lucide-react\";\nimport { Card } from \"@/components/ui/card\";\nimport { useTranslation } from \"react-i18next\";\n\nexport interface CategoryItem {\n id: string | number;\n slug: string;\n name: string;\n description?: string;\n image: string;\n}\n\nexport interface CategorySectionProps {\n categories: CategoryItem[];\n loading?: boolean;\n}\n\nexport function CategorySection({\n categories,\n loading = false,\n}: CategorySectionProps) {\n const { t } = useTranslation(\"category-section\");\n\n return (\n <section className=\"py-8 sm:py-12 md:py-16 lg:py-20 bg-gradient-to-b from-background to-muted/20 border-t border-border/20\">\n <div className=\"container mx-auto px-3 sm:px-4 lg:px-8\">\n <div className=\"text-center mb-6 sm:mb-8 md:mb-12 lg:mb-16 px-2\">\n <h2 className=\"text-xl sm:text-2xl md:text-3xl lg:text-4xl xl:text-5xl font-bold mb-2 sm:mb-3 md:mb-4 bg-gradient-to-r from-primary to-primary/80 bg-clip-text text-transparent leading-normal pb-1\">\n {t(\"title\", \"Shop by Category\")}\n </h2>\n <div className=\"w-12 sm:w-16 h-1 bg-gradient-to-r from-primary/50 to-primary/20 mx-auto mb-3 sm:mb-4 md:mb-6 rounded-full\"></div>\n <p className=\"text-xs sm:text-sm md:text-base lg:text-lg text-muted-foreground max-w-3xl mx-auto leading-relaxed\">\n {t(\"subtitle\", \"Discover our carefully curated collections\")}\n </p>\n </div>\n\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 sm:gap-6 lg:gap-8\">\n {loading\n ? [...Array(4)].map((_, i) => (\n <div key={i} className=\"animate-pulse\">\n <div className=\"aspect-[3/2] bg-muted rounded-xl mb-4\"></div>\n <div className=\"h-5 bg-muted rounded w-3/4 mb-2\"></div>\n <div className=\"h-4 bg-muted rounded w-1/2\"></div>\n </div>\n ))\n : categories.map((category) => (\n <Link\n key={category.id}\n to={`/products?category=${category.slug}`}\n className=\"group block\"\n >\n <Card className=\"overflow-hidden border-0 p-0 shadow-lg hover:shadow-2xl transition-all duration-500 group-hover:-translate-y-2 rounded-2xl\">\n <div className=\"aspect-[4/3] relative overflow-hidden\">\n <img\n src={category.image}\n alt={category.name}\n className=\"absolute inset-0 w-full h-full object-cover group-hover:scale-110 transition-transform duration-700\"\n />\n <div className=\"absolute inset-0 bg-gradient-to-t from-black/60 via-black/20 to-transparent group-hover:from-black/70 transition-all duration-300\"></div>\n\n <div className=\"absolute bottom-0 left-0 right-0 p-4 sm:p-6\">\n <h3 className=\"text-lg sm:text-xl font-bold text-white mb-1 sm:mb-2 group-hover:text-primary-foreground transition-colors\">\n {category.name}\n </h3>\n {category.description && (\n <p className=\"text-xs sm:text-sm text-white/90 line-clamp-2 group-hover:text-white transition-colors\">\n {category.description}\n </p>\n )}\n\n <div className=\"flex items-center mt-2 sm:mt-3 text-white/80 group-hover:text-white transition-all duration-300 transform group-hover:translate-x-1\">\n <span className=\"text-xs sm:text-sm font-medium mr-2\">\n {t(\"explore\", \"Explore\")}\n </span>\n <ArrowRight className=\"w-3 h-3 sm:w-4 sm:h-4\" />\n </div>\n </div>\n </div>\n </Card>\n </Link>\n ))}\n </div>\n </div>\n </section>\n );\n}\n"
19
+ "content": "import { Link } from \"react-router\";\nimport { ArrowRight } from \"lucide-react\";\nimport { Card } from \"@/components/ui/card\";\nimport { useTranslation } from \"react-i18next\";\nimport { useCategories } from \"@/modules/ecommerce-core\";\n\nexport interface CategoryItem {\n id: string | number;\n slug: string;\n name: string;\n description?: string;\n image: string;\n}\n\nexport interface CategorySectionProps {\n categories?: CategoryItem[];\n loading?: boolean;\n}\n\nexport function CategorySection({\n categories: propCategories,\n loading: propLoading,\n}: CategorySectionProps) {\n const { t } = useTranslation(\"category-section\");\n const { categories: hookCategories, loading: hookLoading } = useCategories();\n\n const categories = propCategories ?? hookCategories;\n const loading = propLoading ?? hookLoading;\n\n return (\n <section className=\"py-8 sm:py-12 md:py-16 lg:py-20 bg-gradient-to-b from-background to-muted/20 border-t border-border/20\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-3 sm:px-4 lg:px-8\">\n <div className=\"text-center mb-6 sm:mb-8 md:mb-12 lg:mb-16 px-2\">\n <h2 className=\"text-xl sm:text-2xl md:text-3xl lg:text-4xl xl:text-5xl font-bold mb-2 sm:mb-3 md:mb-4 bg-gradient-to-r from-primary to-primary/80 bg-clip-text text-transparent leading-normal pb-1\">\n {t(\"title\", \"Shop by Category\")}\n </h2>\n <div className=\"w-12 sm:w-16 h-1 bg-gradient-to-r from-primary/50 to-primary/20 mx-auto mb-3 sm:mb-4 md:mb-6 rounded-full\"></div>\n <p className=\"text-xs sm:text-sm md:text-base lg:text-lg text-muted-foreground max-w-3xl mx-auto leading-relaxed\">\n {t(\"subtitle\", \"Discover our carefully curated collections\")}\n </p>\n </div>\n\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 sm:gap-6 lg:gap-8\">\n {loading\n ? [...Array(4)].map((_, i) => (\n <div key={i} className=\"animate-pulse\">\n <div className=\"aspect-[3/2] bg-muted rounded-xl mb-4\"></div>\n <div className=\"h-5 bg-muted rounded w-3/4 mb-2\"></div>\n <div className=\"h-4 bg-muted rounded w-1/2\"></div>\n </div>\n ))\n : categories.map((category) => (\n <Link\n key={category.id}\n to={`/products?category=${category.slug}`}\n className=\"group block\"\n >\n <Card className=\"overflow-hidden border-0 p-0 shadow-lg hover:shadow-2xl transition-all duration-500 group-hover:-translate-y-2 rounded-2xl\">\n <div className=\"aspect-[4/3] relative overflow-hidden\">\n <img\n src={category.image}\n alt={category.name}\n className=\"absolute inset-0 w-full h-full object-cover group-hover:scale-110 transition-transform duration-700\"\n />\n <div className=\"absolute inset-0 bg-gradient-to-t from-black/60 via-black/20 to-transparent group-hover:from-black/70 transition-all duration-300\"></div>\n\n <div className=\"absolute bottom-0 left-0 right-0 p-4 sm:p-6\">\n <h3 className=\"text-lg sm:text-xl font-bold text-white mb-1 sm:mb-2 group-hover:text-primary-foreground transition-colors\">\n {category.name}\n </h3>\n {category.description && (\n <p className=\"text-xs sm:text-sm text-white/90 line-clamp-2 group-hover:text-white transition-colors\">\n {category.description}\n </p>\n )}\n\n <div className=\"flex items-center mt-2 sm:mt-3 text-white/80 group-hover:text-white transition-all duration-300 transform group-hover:translate-x-1\">\n <span className=\"text-xs sm:text-sm font-medium mr-2\">\n {t(\"explore\", \"Explore\")}\n </span>\n <ArrowRight className=\"w-3 h-3 sm:w-4 sm:h-4\" />\n </div>\n </div>\n </div>\n </Card>\n </Link>\n ))}\n </div>\n </div>\n </section>\n );\n}\n"
20
20
  },
21
21
  {
22
22
  "path": "category-section/lang/en.json",
@@ -4,7 +4,8 @@
4
4
  "title": "Checkout Page",
5
5
  "description": "Multi-step checkout page with customer information form (name, email, phone, address), shipping method selection, payment method options (credit card, PayPal, bank transfer), and order review. Features form validation, order summary sidebar, shipping cost calculation, and place order button. Includes progress indicator for checkout steps.",
6
6
  "registryDependencies": [
7
- "ecommerce-core"
7
+ "ecommerce-core",
8
+ "animations"
8
9
  ],
9
10
  "route": {
10
11
  "path": "/checkout",
@@ -22,7 +23,7 @@
22
23
  "path": "checkout-page/checkout-page.tsx",
23
24
  "type": "registry:page",
24
25
  "target": "$modules$/checkout-page/checkout-page.tsx",
25
- "content": "import { useState } from \"react\";\nimport { Link, useNavigate } 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 {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport { useTranslation } from \"react-i18next\";\nimport { useCart, formatPrice } from \"@/modules/ecommerce-core\";\nimport constants from \"@/constants/constants.json\";\nimport { FadeIn } from \"@/modules/animations\";\n\ninterface Country {\n value: string;\n label: string;\n}\n\ntype PaymentMethod = \"card\" | \"cash\" | \"transfer\";\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\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: \"TR\", label: \"Turkey\" },\n { value: \"JP\", label: \"Japan\" },\n];\n\nexport function CheckoutPage() {\n const { t } = useTranslation(\"checkout-page\");\n const navigate = useNavigate();\n const { state, clearCart } = useCart();\n const { items, total } = state;\n\n const currency = constants.site.currency || \"USD\";\n const shipping = 0;\n const tax = 0;\n const countries = DEFAULT_COUNTRIES;\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>(\"card\");\n const [isSubmitting, setIsSubmitting] = useState(false);\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 const finalTotal = total + shipping + tax;\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) return;\n\n setIsSubmitting(true);\n\n // Simulate order submission\n await new Promise((resolve) => setTimeout(resolve, 1500));\n\n // Clear cart and redirect to success page\n clearCart();\n navigate(\"/\");\n\n setIsSubmitting(false);\n };\n\n const paymentMethods = [\n {\n id: \"card\" as PaymentMethod,\n label: t(\"card\", \"Credit/Debit Card\"),\n description: t(\n \"cardDescription\",\n \"Pay securely with your credit or debit card\"\n ),\n icon: CreditCard,\n iconColor: \"text-blue-600\",\n },\n {\n id: \"transfer\" as PaymentMethod,\n label: t(\"transfer\", \"Bank Transfer\"),\n description: t(\n \"transferDescription\",\n \"Transfer payment to our bank account\"\n ),\n icon: Banknote,\n iconColor: \"text-primary\",\n },\n {\n id: \"cash\" as PaymentMethod,\n label: t(\"cash\", \"Cash on Delivery\"),\n description: t(\n \"cashDescription\",\n \"Pay when your order arrives at your doorstep\"\n ),\n icon: Truck,\n iconColor: \"text-green-600 dark:text-green-400\",\n },\n ];\n\n if (items.length === 0) {\n return (\n <Layout>\n <div className=\"container 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=\"container 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>\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>\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>\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>\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>\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>\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>\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>\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 {paymentMethods.map((method) => (\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 <method.icon\n className={`h-5 w-5 ${method.iconColor}`}\n />\n <div>\n <div className=\"font-medium\">{method.label}</div>\n <div className=\"text-sm text-muted-foreground\">\n {method.description}\n </div>\n </div>\n </div>\n </Label>\n </div>\n ))}\n </RadioGroup>\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 <div className=\"flex items-start space-x-2\">\n <Checkbox\n id=\"terms\"\n checked={agreedToTerms}\n onCheckedChange={(checked) =>\n setAgreedToTerms(checked as boolean)\n }\n />\n <Label htmlFor=\"terms\" className=\"text-sm leading-relaxed\">\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 </Label>\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"
26
+ "content": "import { useState } from \"react\";\nimport { Link, useNavigate } 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 {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport { useTranslation } from \"react-i18next\";\nimport { toast } from \"sonner\";\nimport { useCart, formatPrice } from \"@/modules/ecommerce-core\";\nimport { customerClient } from \"@/modules/api\";\nimport { getErrorMessage } from \"@/modules/api/get-error-message\";\nimport constants from \"@/constants/constants.json\";\nimport { FadeIn } from \"@/modules/animations\";\n\ninterface Country {\n value: string;\n label: string;\n}\n\ntype PaymentMethod = \"card\" | \"cash\" | \"transfer\";\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\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: \"TR\", label: \"Turkey\" },\n { value: \"JP\", label: \"Japan\" },\n];\n\nexport function CheckoutPage() {\n const { t } = useTranslation(\"checkout-page\");\n const navigate = useNavigate();\n const { state, clearCart } = useCart();\n const { items, total } = state;\n\n const currency = constants.site.currency || \"USD\";\n const shipping = 0;\n const tax = 0;\n const countries = DEFAULT_COUNTRIES;\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>(\"card\");\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 const finalTotal = total + shipping + tax;\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) return;\n\n setIsSubmitting(true);\n setError(null);\n\n try {\n const orderData = {\n items: items.map((item) => ({\n product_id: item.product.id,\n quantity: item.quantity,\n price: getProductPrice(item.product),\n })),\n shipping_address: {\n first_name: formData.firstName,\n last_name: formData.lastName,\n email: formData.email,\n phone: formData.phone,\n address: formData.address,\n city: formData.city,\n postal_code: formData.postalCode,\n country: formData.country,\n },\n payment_method: paymentMethod,\n notes: formData.notes,\n total: finalTotal,\n };\n\n const response = await customerClient.orders.create(orderData);\n\n toast.success(t(\"orderSuccess\", \"Order placed successfully!\"), {\n description: t(\"orderSuccessDesc\", \"Thank you for your purchase.\"),\n });\n\n clearCart();\n navigate(`/order-success/${response.id}`);\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 const paymentMethods = [\n {\n id: \"card\" as PaymentMethod,\n label: t(\"card\", \"Credit/Debit Card\"),\n description: t(\n \"cardDescription\",\n \"Pay securely with your credit or debit card\"\n ),\n icon: CreditCard,\n iconColor: \"text-blue-600\",\n },\n {\n id: \"transfer\" as PaymentMethod,\n label: t(\"transfer\", \"Bank Transfer\"),\n description: t(\n \"transferDescription\",\n \"Transfer payment to our bank account\"\n ),\n icon: Banknote,\n iconColor: \"text-primary\",\n },\n {\n id: \"cash\" as PaymentMethod,\n label: t(\"cash\", \"Cash on Delivery\"),\n description: t(\n \"cashDescription\",\n \"Pay when your order arrives at your doorstep\"\n ),\n icon: Truck,\n iconColor: \"text-green-600 dark:text-green-400\",\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>\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>\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>\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>\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>\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>\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>\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>\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 {paymentMethods.map((method) => (\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 <method.icon\n className={`h-5 w-5 ${method.iconColor}`}\n />\n <div>\n <div className=\"font-medium\">{method.label}</div>\n <div className=\"text-sm text-muted-foreground\">\n {method.description}\n </div>\n </div>\n </div>\n </Label>\n </div>\n ))}\n </RadioGroup>\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-start space-x-2\">\n <Checkbox\n id=\"terms\"\n checked={agreedToTerms}\n onCheckedChange={(checked) =>\n setAgreedToTerms(checked as boolean)\n }\n />\n <Label htmlFor=\"terms\" className=\"text-sm leading-relaxed\">\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 </Label>\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"
26
27
  },
27
28
  {
28
29
  "path": "checkout-page/lang/en.json",
@@ -16,7 +16,7 @@
16
16
  "path": "contact-info-grid/contact-info-grid.tsx",
17
17
  "type": "registry:component",
18
18
  "target": "$modules$/contact-info-grid/contact-info-grid.tsx",
19
- "content": "import { Mail, MapPin, MessageCircle, Phone } from \"lucide-react\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport constants from \"@/constants/constants.json\";\r\n\r\ninterface ContactInfoGridProps {\r\n className?: string;\r\n}\r\n\r\nexport function ContactInfoGrid({ className }: ContactInfoGridProps) {\r\n const { t } = useTranslation(\"contact-info-grid\");\r\n\r\n const contactItems = [\r\n {\r\n icon: Mail,\r\n label: t(\"emailLabel\", \"Email\"),\r\n description: t(\"emailDesc\", \"We respond to all emails within 24 hours.\"),\r\n value: constants.email || \"hello@example.com\",\r\n href: `mailto:${constants.email || \"hello@example.com\"}`,\r\n },\r\n {\r\n icon: MapPin,\r\n label: t(\"officeLabel\", \"Office\"),\r\n description: t(\"officeDesc\", \"Drop by our office for a chat.\"),\r\n value: `${constants.address?.line1 || \"\"}, ${constants.address?.city || \"\"}`,\r\n href: \"#\",\r\n },\r\n {\r\n icon: Phone,\r\n label: t(\"phoneLabel\", \"Phone\"),\r\n description: t(\"phoneDesc\", \"We're available Mon-Fri, 9am-5pm.\"),\r\n value: constants.phone || \"+1 234 567 890\",\r\n href: `tel:${constants.phone || \"+1234567890\"}`,\r\n },\r\n {\r\n icon: MessageCircle,\r\n label: t(\"chatLabel\", \"Live Chat\"),\r\n description: t(\"chatDesc\", \"Get instant help from our support team.\"),\r\n value: t(\"startChat\", \"Start Chat\"),\r\n href: \"#\",\r\n },\r\n ];\r\n\r\n return (\r\n <section className={cn(\"py-16 md:py-24\", className)}>\r\n <div className=\"container mx-auto px-4\">\r\n <div className=\"mb-12\">\r\n <h2 className=\"text-3xl font-bold md:text-4xl mb-4\">\r\n {t(\"title\", \"Get in Touch\")}\r\n </h2>\r\n <p className=\"text-lg text-muted-foreground max-w-xl\">\r\n {t(\"subtitle\", \"Have questions? We'd love to hear from you. Choose your preferred way to reach us.\")}\r\n </p>\r\n </div>\r\n\r\n <div className=\"grid sm:grid-cols-2 lg:grid-cols-4 gap-6\">\r\n {contactItems.map((item, index) => (\r\n <div\r\n key={index}\r\n className=\"rounded-xl bg-muted/50 p-6 hover:bg-muted transition-colors\"\r\n >\r\n <span className=\"mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-primary/10\">\r\n <item.icon className=\"h-6 w-6 text-primary\" />\r\n </span>\r\n <h3 className=\"mb-2 text-lg font-semibold\">{item.label}</h3>\r\n <p className=\"mb-3 text-sm text-muted-foreground\">\r\n {item.description}\r\n </p>\r\n <a\r\n href={item.href}\r\n className=\"text-sm font-semibold text-primary hover:underline\"\r\n >\r\n {item.value}\r\n </a>\r\n </div>\r\n ))}\r\n </div>\r\n </div>\r\n </section>\r\n );\r\n}\r\n"
19
+ "content": "import { Mail, MapPin, MessageCircle, Phone } from \"lucide-react\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport constants from \"@/constants/constants.json\";\r\n\r\ninterface ContactInfoGridProps {\r\n className?: string;\r\n}\r\n\r\nexport function ContactInfoGrid({ className }: ContactInfoGridProps) {\r\n const { t } = useTranslation(\"contact-info-grid\");\r\n\r\n const contactItems = [\r\n {\r\n icon: Mail,\r\n label: t(\"emailLabel\", \"Email\"),\r\n description: t(\"emailDesc\", \"We respond to all emails within 24 hours.\"),\r\n value: constants.email || \"hello@example.com\",\r\n href: `mailto:${constants.email || \"hello@example.com\"}`,\r\n },\r\n {\r\n icon: MapPin,\r\n label: t(\"officeLabel\", \"Office\"),\r\n description: t(\"officeDesc\", \"Drop by our office for a chat.\"),\r\n value: `${constants.address?.line1 || \"\"}, ${constants.address?.city || \"\"}`,\r\n href: \"#\",\r\n },\r\n {\r\n icon: Phone,\r\n label: t(\"phoneLabel\", \"Phone\"),\r\n description: t(\"phoneDesc\", \"We're available Mon-Fri, 9am-5pm.\"),\r\n value: constants.phone || \"+1 234 567 890\",\r\n href: `tel:${constants.phone || \"+1234567890\"}`,\r\n },\r\n {\r\n icon: MessageCircle,\r\n label: t(\"chatLabel\", \"Live Chat\"),\r\n description: t(\"chatDesc\", \"Get instant help from our support team.\"),\r\n value: t(\"startChat\", \"Start Chat\"),\r\n href: \"#\",\r\n },\r\n ];\r\n\r\n return (\r\n <section className={cn(\"py-16 md:py-24\", className)}>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n <div className=\"mb-12\">\r\n <h2 className=\"text-3xl font-bold md:text-4xl mb-4\">\r\n {t(\"title\", \"Get in Touch\")}\r\n </h2>\r\n <p className=\"text-lg text-muted-foreground max-w-xl\">\r\n {t(\"subtitle\", \"Have questions? We'd love to hear from you. Choose your preferred way to reach us.\")}\r\n </p>\r\n </div>\r\n\r\n <div className=\"grid sm:grid-cols-2 lg:grid-cols-4 gap-6\">\r\n {contactItems.map((item, index) => (\r\n <div\r\n key={index}\r\n className=\"rounded-xl bg-muted/50 p-6 hover:bg-muted transition-colors\"\r\n >\r\n <span className=\"mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-primary/10\">\r\n <item.icon className=\"h-6 w-6 text-primary\" />\r\n </span>\r\n <h3 className=\"mb-2 text-lg font-semibold\">{item.label}</h3>\r\n <p className=\"mb-3 text-sm text-muted-foreground\">\r\n {item.description}\r\n </p>\r\n <a\r\n href={item.href}\r\n className=\"text-sm font-semibold text-primary hover:underline\"\r\n >\r\n {item.value}\r\n </a>\r\n </div>\r\n ))}\r\n </div>\r\n </div>\r\n </section>\r\n );\r\n}\r\n"
20
20
  },
21
21
  {
22
22
  "path": "contact-info-grid/lang/en.json",
@@ -25,7 +25,7 @@
25
25
  "path": "contact-page-centered/contact-page-centered.tsx",
26
26
  "type": "registry:component",
27
27
  "target": "$modules$/contact-page-centered/contact-page-centered.tsx",
28
- "content": "import React, { useState } from \"react\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { Mail, Phone, MapPin } from \"lucide-react\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { useApiService } from \"@/lib/api\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Textarea } from \"@/components/ui/textarea\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { Card, CardContent } from \"@/components/ui/card\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport constants from \"@/constants/constants.json\";\r\n\r\ninterface ContactPageCenteredProps {\r\n className?: string;\r\n}\r\n\r\nexport function ContactPageCentered({ className }: ContactPageCenteredProps) {\r\n const { t } = useTranslation(\"contact-page-centered\");\r\n const apiService = useApiService();\r\n\r\n const [formData, setFormData] = useState({\r\n name: \"\",\r\n email: \"\",\r\n message: \"\",\r\n });\r\n const [isSubmitting, setIsSubmitting] = useState(false);\r\n const [submitStatus, setSubmitStatus] = useState<\"idle\" | \"success\" | \"error\">(\"idle\");\r\n\r\n const contactCards = [\r\n {\r\n icon: Mail,\r\n title: t(\"emailTitle\", \"Email\"),\r\n value: constants.email || \"hello@example.com\",\r\n href: `mailto:${constants.email || \"hello@example.com\"}`,\r\n },\r\n {\r\n icon: Phone,\r\n title: t(\"phoneTitle\", \"Phone\"),\r\n value: constants.phone || \"+1 234 567 890\",\r\n href: `tel:${constants.phone || \"+1234567890\"}`,\r\n },\r\n {\r\n icon: MapPin,\r\n title: t(\"addressTitle\", \"Address\"),\r\n value: constants.address?.city || \"New York, USA\",\r\n href: \"#\",\r\n },\r\n ];\r\n\r\n const handleSubmit = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setIsSubmitting(true);\r\n setSubmitStatus(\"idle\");\r\n\r\n try {\r\n await apiService.submitForm(\r\n formData,\r\n {\r\n email_subject1: \"Thank you for contacting us\",\r\n email_subject2: \"New Contact Form Submission\",\r\n fields: [\r\n { name: \"name\", required: true },\r\n { name: \"email\", required: true },\r\n { name: \"message\", required: true },\r\n ],\r\n },\r\n constants.site.defaultLanguage\r\n );\r\n\r\n setSubmitStatus(\"success\");\r\n setFormData({ name: \"\", email: \"\", message: \"\" });\r\n setTimeout(() => setSubmitStatus(\"idle\"), 5000);\r\n } catch {\r\n setSubmitStatus(\"error\");\r\n setTimeout(() => setSubmitStatus(\"idle\"), 5000);\r\n } finally {\r\n setIsSubmitting(false);\r\n }\r\n };\r\n\r\n const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {\r\n setFormData((prev) => ({ ...prev, [e.target.name]: e.target.value }));\r\n };\r\n\r\n return (\r\n <Layout>\r\n <div className={cn(\"min-h-screen bg-muted/30 py-16 md:py-24\", className)}>\r\n <div className=\"container mx-auto px-4 max-w-4xl\">\r\n {/* Header */}\r\n <div className=\"text-center mb-12\">\r\n <h1 className=\"text-4xl font-bold mb-4\">{t(\"title\", \"Contact Us\")}</h1>\r\n <p className=\"text-lg text-muted-foreground max-w-2xl mx-auto\">\r\n {t(\"subtitle\", \"We'd love to hear from you. Send us a message and we'll respond as soon as possible.\")}\r\n </p>\r\n </div>\r\n\r\n {/* Contact Cards */}\r\n <div className=\"grid sm:grid-cols-3 gap-4 mb-12\">\r\n {contactCards.map((card, index) => (\r\n <Card key={index} className=\"text-center\">\r\n <CardContent className=\"pt-6\">\r\n <div className=\"mx-auto w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center mb-4\">\r\n <card.icon className=\"h-6 w-6 text-primary\" />\r\n </div>\r\n <h3 className=\"font-semibold mb-1\">{card.title}</h3>\r\n <a\r\n href={card.href}\r\n className=\"text-sm text-muted-foreground hover:text-primary transition-colors\"\r\n >\r\n {card.value}\r\n </a>\r\n </CardContent>\r\n </Card>\r\n ))}\r\n </div>\r\n\r\n {/* Form */}\r\n <Card>\r\n <CardContent className=\"pt-6\">\r\n <form onSubmit={handleSubmit} className=\"space-y-6\">\r\n <div className=\"grid sm:grid-cols-2 gap-4\">\r\n <div>\r\n <Label htmlFor=\"name\">{t(\"nameLabel\", \"Name\")} *</Label>\r\n <Input\r\n id=\"name\"\r\n name=\"name\"\r\n value={formData.name}\r\n onChange={handleChange}\r\n placeholder={t(\"namePlaceholder\", \"Your name\")}\r\n required\r\n className=\"mt-1\"\r\n />\r\n </div>\r\n <div>\r\n <Label htmlFor=\"email\">{t(\"emailLabel\", \"Email\")} *</Label>\r\n <Input\r\n id=\"email\"\r\n name=\"email\"\r\n type=\"email\"\r\n value={formData.email}\r\n onChange={handleChange}\r\n placeholder={t(\"emailPlaceholder\", \"your@email.com\")}\r\n required\r\n className=\"mt-1\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div>\r\n <Label htmlFor=\"message\">{t(\"messageLabel\", \"Message\")} *</Label>\r\n <Textarea\r\n id=\"message\"\r\n name=\"message\"\r\n value={formData.message}\r\n onChange={handleChange}\r\n placeholder={t(\"messagePlaceholder\", \"How can we help you?\")}\r\n required\r\n rows={6}\r\n className=\"mt-1 resize-none\"\r\n />\r\n </div>\r\n\r\n {submitStatus === \"success\" && (\r\n <div className=\"p-4 bg-green-500/10 border border-green-500/30 rounded-lg\">\r\n <p className=\"text-green-600 dark:text-green-400 text-sm font-medium\">\r\n {t(\"success\", \"Message sent successfully! We'll get back to you soon.\")}\r\n </p>\r\n </div>\r\n )}\r\n\r\n {submitStatus === \"error\" && (\r\n <div className=\"p-4 bg-destructive/10 border border-destructive/30 rounded-lg\">\r\n <p className=\"text-destructive text-sm font-medium\">\r\n {t(\"error\", \"Something went wrong. Please try again.\")}\r\n </p>\r\n </div>\r\n )}\r\n\r\n <Button type=\"submit\" size=\"lg\" className=\"w-full\" disabled={isSubmitting}>\r\n {isSubmitting ? t(\"sending\", \"Sending...\") : t(\"submit\", \"Send Message\")}\r\n </Button>\r\n </form>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n"
28
+ "content": "import React, { useState } from \"react\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { Mail, Phone, MapPin } from \"lucide-react\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { useApiService } from \"@/lib/api\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Textarea } from \"@/components/ui/textarea\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { Card, CardContent } from \"@/components/ui/card\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport constants from \"@/constants/constants.json\";\r\n\r\ninterface ContactPageCenteredProps {\r\n className?: string;\r\n}\r\n\r\nexport function ContactPageCentered({ className }: ContactPageCenteredProps) {\r\n const { t } = useTranslation(\"contact-page-centered\");\r\n const apiService = useApiService();\r\n\r\n const [formData, setFormData] = useState({\r\n name: \"\",\r\n email: \"\",\r\n message: \"\",\r\n });\r\n const [isSubmitting, setIsSubmitting] = useState(false);\r\n const [submitStatus, setSubmitStatus] = useState<\"idle\" | \"success\" | \"error\">(\"idle\");\r\n\r\n const contactCards = [\r\n {\r\n icon: Mail,\r\n title: t(\"emailTitle\", \"Email\"),\r\n value: constants.email || \"hello@example.com\",\r\n href: `mailto:${constants.email || \"hello@example.com\"}`,\r\n },\r\n {\r\n icon: Phone,\r\n title: t(\"phoneTitle\", \"Phone\"),\r\n value: constants.phone || \"+1 234 567 890\",\r\n href: `tel:${constants.phone || \"+1234567890\"}`,\r\n },\r\n {\r\n icon: MapPin,\r\n title: t(\"addressTitle\", \"Address\"),\r\n value: constants.address?.city || \"New York, USA\",\r\n href: \"#\",\r\n },\r\n ];\r\n\r\n const handleSubmit = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setIsSubmitting(true);\r\n setSubmitStatus(\"idle\");\r\n\r\n try {\r\n await apiService.submitForm(\r\n formData,\r\n {\r\n email_subject1: \"Thank you for contacting us\",\r\n email_subject2: \"New Contact Form Submission\",\r\n fields: [\r\n { name: \"name\", required: true },\r\n { name: \"email\", required: true },\r\n { name: \"message\", required: true },\r\n ],\r\n },\r\n constants.site.defaultLanguage\r\n );\r\n\r\n setSubmitStatus(\"success\");\r\n setFormData({ name: \"\", email: \"\", message: \"\" });\r\n setTimeout(() => setSubmitStatus(\"idle\"), 5000);\r\n } catch {\r\n setSubmitStatus(\"error\");\r\n setTimeout(() => setSubmitStatus(\"idle\"), 5000);\r\n } finally {\r\n setIsSubmitting(false);\r\n }\r\n };\r\n\r\n const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {\r\n setFormData((prev) => ({ ...prev, [e.target.name]: e.target.value }));\r\n };\r\n\r\n return (\r\n <Layout>\r\n <div className={cn(\"min-h-screen bg-muted/30 py-16 md:py-24\", className)}>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 max-w-4xl\">\r\n {/* Header */}\r\n <div className=\"text-center mb-12\">\r\n <h1 className=\"text-4xl font-bold mb-4\">{t(\"title\", \"Contact Us\")}</h1>\r\n <p className=\"text-lg text-muted-foreground max-w-2xl mx-auto\">\r\n {t(\"subtitle\", \"We'd love to hear from you. Send us a message and we'll respond as soon as possible.\")}\r\n </p>\r\n </div>\r\n\r\n {/* Contact Cards */}\r\n <div className=\"grid sm:grid-cols-3 gap-4 mb-12\">\r\n {contactCards.map((card, index) => (\r\n <Card key={index} className=\"text-center\">\r\n <CardContent className=\"pt-6\">\r\n <div className=\"mx-auto w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center mb-4\">\r\n <card.icon className=\"h-6 w-6 text-primary\" />\r\n </div>\r\n <h3 className=\"font-semibold mb-1\">{card.title}</h3>\r\n <a\r\n href={card.href}\r\n className=\"text-sm text-muted-foreground hover:text-primary transition-colors\"\r\n >\r\n {card.value}\r\n </a>\r\n </CardContent>\r\n </Card>\r\n ))}\r\n </div>\r\n\r\n {/* Form */}\r\n <Card>\r\n <CardContent className=\"pt-6\">\r\n <form onSubmit={handleSubmit} className=\"space-y-6\">\r\n <div className=\"grid sm:grid-cols-2 gap-4\">\r\n <div>\r\n <Label htmlFor=\"name\">{t(\"nameLabel\", \"Name\")} *</Label>\r\n <Input\r\n id=\"name\"\r\n name=\"name\"\r\n value={formData.name}\r\n onChange={handleChange}\r\n placeholder={t(\"namePlaceholder\", \"Your name\")}\r\n required\r\n className=\"mt-1\"\r\n />\r\n </div>\r\n <div>\r\n <Label htmlFor=\"email\">{t(\"emailLabel\", \"Email\")} *</Label>\r\n <Input\r\n id=\"email\"\r\n name=\"email\"\r\n type=\"email\"\r\n value={formData.email}\r\n onChange={handleChange}\r\n placeholder={t(\"emailPlaceholder\", \"your@email.com\")}\r\n required\r\n className=\"mt-1\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div>\r\n <Label htmlFor=\"message\">{t(\"messageLabel\", \"Message\")} *</Label>\r\n <Textarea\r\n id=\"message\"\r\n name=\"message\"\r\n value={formData.message}\r\n onChange={handleChange}\r\n placeholder={t(\"messagePlaceholder\", \"How can we help you?\")}\r\n required\r\n rows={6}\r\n className=\"mt-1 resize-none\"\r\n />\r\n </div>\r\n\r\n {submitStatus === \"success\" && (\r\n <div className=\"p-4 bg-green-500/10 border border-green-500/30 rounded-lg\">\r\n <p className=\"text-green-600 dark:text-green-400 text-sm font-medium\">\r\n {t(\"success\", \"Message sent successfully! We'll get back to you soon.\")}\r\n </p>\r\n </div>\r\n )}\r\n\r\n {submitStatus === \"error\" && (\r\n <div className=\"p-4 bg-destructive/10 border border-destructive/30 rounded-lg\">\r\n <p className=\"text-destructive text-sm font-medium\">\r\n {t(\"error\", \"Something went wrong. Please try again.\")}\r\n </p>\r\n </div>\r\n )}\r\n\r\n <Button type=\"submit\" size=\"lg\" className=\"w-full\" disabled={isSubmitting}>\r\n {isSubmitting ? t(\"sending\", \"Sending...\") : t(\"submit\", \"Send Message\")}\r\n </Button>\r\n </form>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n"
29
29
  },
30
30
  {
31
31
  "path": "contact-page-centered/lang/en.json",
@@ -30,7 +30,7 @@
30
30
  "path": "contact-page-map-overlay/contact-page-map-overlay.tsx",
31
31
  "type": "registry:component",
32
32
  "target": "$modules$/contact-page-map-overlay/contact-page-map-overlay.tsx",
33
- "content": "import React, { useState, useMemo, useEffect } from \"react\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { useTranslation } from \"react-i18next\";\nimport { Layout } from \"@/components/Layout\";\nimport { useApiService } from \"@/lib/api\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { Label } from \"@/components/ui/label\";\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport {\n Mail,\n Phone,\n MapPin,\n Facebook,\n Twitter,\n Instagram,\n Linkedin,\n Send,\n ExternalLink,\n} from \"lucide-react\";\nimport constants from \"@/constants/constants.json\";\nimport { GoogleMap } from \"@/modules/google-map\";\n\nconst socialIcons: Record<string, React.ElementType> = {\n facebook: Facebook,\n twitter: Twitter,\n instagram: Instagram,\n linkedin: Linkedin,\n};\n\nexport function ContactPageMapOverlay() {\n const { t } = useTranslation(\"contact-page-map-overlay\");\n usePageTitle({ title: t(\"title\") });\n\n const apiService = useApiService();\n\n const socialLinks = useMemo(() => {\n const socialMedia = constants.socialMedia as\n | Record<string, string>\n | undefined;\n if (!socialMedia) return [];\n return Object.entries(socialMedia)\n .filter(([platform, url]) => url && socialIcons[platform])\n .map(([platform, url]) => ({\n platform,\n url,\n Icon: socialIcons[platform],\n }));\n }, []);\n\n const [formData, setFormData] = useState({\n name: \"\",\n email: \"\",\n message: \"\",\n });\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [submitStatus, setSubmitStatus] = useState<\n \"idle\" | \"success\" | \"error\"\n >(\"idle\");\n\n // Auto-reset status after 5 seconds with proper cleanup\n useEffect(() => {\n if (submitStatus === \"idle\") return;\n const timer = setTimeout(() => setSubmitStatus(\"idle\"), 5000);\n return () => clearTimeout(timer);\n }, [submitStatus]);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setIsSubmitting(true);\n setSubmitStatus(\"idle\");\n\n try {\n const currentLanguage = constants.site.defaultLanguage;\n\n await apiService.submitForm(\n formData,\n {\n email_subject1: \"Thank you for contacting us\",\n email_subject2: \"New Contact Form Submission\",\n fields: [\n { name: \"name\", required: true },\n { name: \"email\", required: true },\n { name: \"message\", required: true },\n ],\n },\n currentLanguage\n );\n\n setSubmitStatus(\"success\");\n setFormData({\n name: \"\",\n email: \"\",\n message: \"\",\n });\n } catch (error: unknown) {\n console.error(\"Form submission failed:\", error);\n setSubmitStatus(\"error\");\n } finally {\n setIsSubmitting(false);\n }\n };\n\n const handleChange = (\n e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>\n ) => {\n setFormData((prev) => ({\n ...prev,\n [e.target.name]: e.target.value,\n }));\n };\n\n // Default coordinates (can be customized via constants)\n const mapLatitude = (constants as any).location?.latitude || 41.0082;\n const mapLongitude = (constants as any).location?.longitude || 28.9784;\n\n return (\n <Layout>\n <div className=\"relative min-h-screen\">\n {/* Full-screen Map Background */}\n <div className=\"absolute inset-0\">\n <GoogleMap\n latitude={mapLatitude}\n longitude={mapLongitude}\n zoom={14}\n height=\"100%\"\n className=\"rounded-none border-0 h-full\"\n title={t(\"mapTitle\")}\n />\n {/* Dark overlay for better readability */}\n <div className=\"absolute inset-0 bg-black/30 pointer-events-none\" />\n </div>\n\n {/* Content Overlay */}\n <div className=\"relative z-10 min-h-screen flex items-center py-12 px-4\">\n <div className=\"container mx-auto max-w-6xl\">\n <div className=\"grid lg:grid-cols-5 gap-8 items-center\">\n {/* Form Card - Glassmorphism */}\n <Card className=\"lg:col-span-3 backdrop-blur-xl bg-background/85 dark:bg-background/90 border-white/20 shadow-2xl\">\n <CardHeader>\n <CardTitle className=\"text-2xl lg:text-3xl\">\n {t(\"title\")}\n </CardTitle>\n <p className=\"text-muted-foreground mt-2\">\n {t(\"description\")}\n </p>\n </CardHeader>\n <CardContent>\n <form onSubmit={handleSubmit} className=\"space-y-5\">\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <div>\n <Label htmlFor=\"name\" className=\"text-sm font-medium\">\n {t(\"fullName\")} *\n </Label>\n <Input\n id=\"name\"\n name=\"name\"\n type=\"text\"\n value={formData.name}\n onChange={handleChange}\n placeholder={t(\"fullNamePlaceholder\")}\n required\n className=\"mt-1.5 bg-background/50\"\n />\n </div>\n <div>\n <Label htmlFor=\"email\" className=\"text-sm font-medium\">\n {t(\"emailAddress\")} *\n </Label>\n <Input\n id=\"email\"\n name=\"email\"\n type=\"email\"\n value={formData.email}\n onChange={handleChange}\n placeholder={t(\"emailPlaceholder\")}\n required\n className=\"mt-1.5 bg-background/50\"\n />\n </div>\n </div>\n\n <div>\n <Label htmlFor=\"message\" className=\"text-sm font-medium\">\n {t(\"message\")} *\n </Label>\n <Textarea\n id=\"message\"\n name=\"message\"\n value={formData.message}\n onChange={handleChange}\n placeholder={t(\"messagePlaceholder\")}\n required\n rows={4}\n className=\"mt-1.5 resize-none bg-background/50\"\n />\n </div>\n\n {submitStatus === \"success\" && (\n <div className=\"p-3 bg-green-500/10 border border-green-500/30 rounded-lg\">\n <p className=\"text-green-600 dark:text-green-400 text-sm\">\n {t(\"success\")}\n </p>\n </div>\n )}\n\n {submitStatus === \"error\" && (\n <div className=\"p-3 bg-destructive/10 border border-destructive/30 rounded-lg\">\n <p className=\"text-destructive text-sm\">{t(\"error\")}</p>\n </div>\n )}\n\n <Button\n type=\"submit\"\n size=\"lg\"\n className=\"w-full\"\n disabled={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(\"sending\")}\n </>\n ) : (\n <>\n <Send className=\"w-4 h-4 mr-2\" />\n {t(\"submit\")}\n </>\n )}\n </Button>\n </form>\n </CardContent>\n </Card>\n\n {/* Contact Info Card - Glassmorphism */}\n <Card className=\"lg:col-span-2 backdrop-blur-xl bg-background/85 dark:bg-background/90 border-white/20 shadow-2xl\">\n <CardHeader>\n <CardTitle>{t(\"contactInfo\")}</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-5\">\n <div className=\"flex items-start gap-4\">\n <div className=\"w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0\">\n <Mail className=\"w-5 h-5 text-primary\" />\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">{t(\"email\")}</p>\n <a\n href={`mailto:${constants.email}`}\n className=\"font-medium hover:text-primary transition-colors\"\n >\n {constants.email}\n </a>\n </div>\n </div>\n\n <div className=\"flex items-start gap-4\">\n <div className=\"w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0\">\n <Phone className=\"w-5 h-5 text-primary\" />\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">{t(\"phone\")}</p>\n <a\n href={`tel:${constants.phone}`}\n className=\"font-medium hover:text-primary transition-colors\"\n >\n {constants.phone}\n </a>\n </div>\n </div>\n\n <div className=\"flex items-start gap-4\">\n <div className=\"w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0\">\n <MapPin className=\"w-5 h-5 text-primary\" />\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">{t(\"address\")}</p>\n <p className=\"font-medium\">\n {constants.address.line1}\n <br />\n {constants.address.city}, {constants.address.state} {constants.address.postalCode}\n </p>\n </div>\n </div>\n\n {/* Open in Maps Link */}\n <a\n href={`https://www.google.com/maps?q=${mapLatitude},${mapLongitude}`}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-2 text-sm text-primary hover:underline mt-2\"\n >\n <ExternalLink className=\"w-4 h-4\" />\n {t(\"openInMaps\")}\n </a>\n\n {/* Social Links */}\n {socialLinks.length > 0 && (\n <div className=\"pt-4 border-t\">\n <p className=\"text-sm text-muted-foreground mb-3\">\n {t(\"followUs\")}\n </p>\n <div className=\"flex gap-2\">\n {socialLinks.map(({ platform, url, Icon }) => (\n <a\n key={platform}\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"h-10 w-10 flex items-center justify-center rounded-full border text-muted-foreground hover:text-primary hover:border-primary transition-colors\"\n >\n <Icon className=\"h-4 w-4\" />\n </a>\n ))}\n </div>\n </div>\n )}\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n </div>\n </Layout>\n );\n}\n"
33
+ "content": "import React, { useState, useMemo, useEffect } from \"react\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { useTranslation } from \"react-i18next\";\nimport { Layout } from \"@/components/Layout\";\nimport { useApiService } from \"@/lib/api\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { Label } from \"@/components/ui/label\";\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport {\n Mail,\n Phone,\n MapPin,\n Facebook,\n Twitter,\n Instagram,\n Linkedin,\n Send,\n ExternalLink,\n} from \"lucide-react\";\nimport constants from \"@/constants/constants.json\";\nimport { GoogleMap } from \"@/modules/google-map\";\n\nconst socialIcons: Record<string, React.ElementType> = {\n facebook: Facebook,\n twitter: Twitter,\n instagram: Instagram,\n linkedin: Linkedin,\n};\n\nexport function ContactPageMapOverlay() {\n const { t } = useTranslation(\"contact-page-map-overlay\");\n usePageTitle({ title: t(\"title\") });\n\n const apiService = useApiService();\n\n const socialLinks = useMemo(() => {\n const socialMedia = constants.socialMedia as\n | Record<string, string>\n | undefined;\n if (!socialMedia) return [];\n return Object.entries(socialMedia)\n .filter(([platform, url]) => url && socialIcons[platform])\n .map(([platform, url]) => ({\n platform,\n url,\n Icon: socialIcons[platform],\n }));\n }, []);\n\n const [formData, setFormData] = useState({\n name: \"\",\n email: \"\",\n message: \"\",\n });\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [submitStatus, setSubmitStatus] = useState<\n \"idle\" | \"success\" | \"error\"\n >(\"idle\");\n\n // Auto-reset status after 5 seconds with proper cleanup\n useEffect(() => {\n if (submitStatus === \"idle\") return;\n const timer = setTimeout(() => setSubmitStatus(\"idle\"), 5000);\n return () => clearTimeout(timer);\n }, [submitStatus]);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setIsSubmitting(true);\n setSubmitStatus(\"idle\");\n\n try {\n const currentLanguage = constants.site.defaultLanguage;\n\n await apiService.submitForm(\n formData,\n {\n email_subject1: \"Thank you for contacting us\",\n email_subject2: \"New Contact Form Submission\",\n fields: [\n { name: \"name\", required: true },\n { name: \"email\", required: true },\n { name: \"message\", required: true },\n ],\n },\n currentLanguage\n );\n\n setSubmitStatus(\"success\");\n setFormData({\n name: \"\",\n email: \"\",\n message: \"\",\n });\n } catch (error: unknown) {\n console.error(\"Form submission failed:\", error);\n setSubmitStatus(\"error\");\n } finally {\n setIsSubmitting(false);\n }\n };\n\n const handleChange = (\n e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>\n ) => {\n setFormData((prev) => ({\n ...prev,\n [e.target.name]: e.target.value,\n }));\n };\n\n // Default coordinates (can be customized via constants)\n const mapLatitude = (constants as any).location?.latitude || 41.0082;\n const mapLongitude = (constants as any).location?.longitude || 28.9784;\n\n return (\n <Layout>\n <div className=\"relative min-h-screen\">\n {/* Full-screen Map Background */}\n <div className=\"absolute inset-0\">\n <GoogleMap\n latitude={mapLatitude}\n longitude={mapLongitude}\n zoom={14}\n height=\"100%\"\n className=\"rounded-none border-0 h-full\"\n title={t(\"mapTitle\")}\n />\n {/* Dark overlay for better readability */}\n <div className=\"absolute inset-0 bg-black/30 pointer-events-none\" />\n </div>\n\n {/* Content Overlay */}\n <div className=\"relative z-10 min-h-screen flex items-center py-12 px-4\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto max-w-6xl\">\n <div className=\"grid lg:grid-cols-5 gap-8 items-center\">\n {/* Form Card - Glassmorphism */}\n <Card className=\"lg:col-span-3 backdrop-blur-xl bg-background/85 dark:bg-background/90 border-white/20 shadow-2xl\">\n <CardHeader>\n <CardTitle className=\"text-2xl lg:text-3xl\">\n {t(\"title\")}\n </CardTitle>\n <p className=\"text-muted-foreground mt-2\">\n {t(\"description\")}\n </p>\n </CardHeader>\n <CardContent>\n <form onSubmit={handleSubmit} className=\"space-y-5\">\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <div>\n <Label htmlFor=\"name\" className=\"text-sm font-medium\">\n {t(\"fullName\")} *\n </Label>\n <Input\n id=\"name\"\n name=\"name\"\n type=\"text\"\n value={formData.name}\n onChange={handleChange}\n placeholder={t(\"fullNamePlaceholder\")}\n required\n className=\"mt-1.5 bg-background/50\"\n />\n </div>\n <div>\n <Label htmlFor=\"email\" className=\"text-sm font-medium\">\n {t(\"emailAddress\")} *\n </Label>\n <Input\n id=\"email\"\n name=\"email\"\n type=\"email\"\n value={formData.email}\n onChange={handleChange}\n placeholder={t(\"emailPlaceholder\")}\n required\n className=\"mt-1.5 bg-background/50\"\n />\n </div>\n </div>\n\n <div>\n <Label htmlFor=\"message\" className=\"text-sm font-medium\">\n {t(\"message\")} *\n </Label>\n <Textarea\n id=\"message\"\n name=\"message\"\n value={formData.message}\n onChange={handleChange}\n placeholder={t(\"messagePlaceholder\")}\n required\n rows={4}\n className=\"mt-1.5 resize-none bg-background/50\"\n />\n </div>\n\n {submitStatus === \"success\" && (\n <div className=\"p-3 bg-green-500/10 border border-green-500/30 rounded-lg\">\n <p className=\"text-green-600 dark:text-green-400 text-sm\">\n {t(\"success\")}\n </p>\n </div>\n )}\n\n {submitStatus === \"error\" && (\n <div className=\"p-3 bg-destructive/10 border border-destructive/30 rounded-lg\">\n <p className=\"text-destructive text-sm\">{t(\"error\")}</p>\n </div>\n )}\n\n <Button\n type=\"submit\"\n size=\"lg\"\n className=\"w-full\"\n disabled={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(\"sending\")}\n </>\n ) : (\n <>\n <Send className=\"w-4 h-4 mr-2\" />\n {t(\"submit\")}\n </>\n )}\n </Button>\n </form>\n </CardContent>\n </Card>\n\n {/* Contact Info Card - Glassmorphism */}\n <Card className=\"lg:col-span-2 backdrop-blur-xl bg-background/85 dark:bg-background/90 border-white/20 shadow-2xl\">\n <CardHeader>\n <CardTitle>{t(\"contactInfo\")}</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-5\">\n <div className=\"flex items-start gap-4\">\n <div className=\"w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0\">\n <Mail className=\"w-5 h-5 text-primary\" />\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">{t(\"email\")}</p>\n <a\n href={`mailto:${constants.email}`}\n className=\"font-medium hover:text-primary transition-colors\"\n >\n {constants.email}\n </a>\n </div>\n </div>\n\n <div className=\"flex items-start gap-4\">\n <div className=\"w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0\">\n <Phone className=\"w-5 h-5 text-primary\" />\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">{t(\"phone\")}</p>\n <a\n href={`tel:${constants.phone}`}\n className=\"font-medium hover:text-primary transition-colors\"\n >\n {constants.phone}\n </a>\n </div>\n </div>\n\n <div className=\"flex items-start gap-4\">\n <div className=\"w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0\">\n <MapPin className=\"w-5 h-5 text-primary\" />\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">{t(\"address\")}</p>\n <p className=\"font-medium\">\n {constants.address.line1}\n <br />\n {constants.address.city}, {constants.address.state} {constants.address.postalCode}\n </p>\n </div>\n </div>\n\n {/* Open in Maps Link */}\n <a\n href={`https://www.google.com/maps?q=${mapLatitude},${mapLongitude}`}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-2 text-sm text-primary hover:underline mt-2\"\n >\n <ExternalLink className=\"w-4 h-4\" />\n {t(\"openInMaps\")}\n </a>\n\n {/* Social Links */}\n {socialLinks.length > 0 && (\n <div className=\"pt-4 border-t\">\n <p className=\"text-sm text-muted-foreground mb-3\">\n {t(\"followUs\")}\n </p>\n <div className=\"flex gap-2\">\n {socialLinks.map(({ platform, url, Icon }) => (\n <a\n key={platform}\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"h-10 w-10 flex items-center justify-center rounded-full border text-muted-foreground hover:text-primary hover:border-primary transition-colors\"\n >\n <Icon className=\"h-4 w-4\" />\n </a>\n ))}\n </div>\n </div>\n )}\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n </div>\n </Layout>\n );\n}\n"
34
34
  },
35
35
  {
36
36
  "path": "contact-page-map-overlay/lang/en.json",
@@ -20,7 +20,7 @@
20
20
  "path": "contact-page/contact-page.tsx",
21
21
  "type": "registry:page",
22
22
  "target": "$modules$/contact-page/contact-page.tsx",
23
- "content": "import React, { useState, useMemo } from \"react\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { useTranslation } from \"react-i18next\";\nimport { Layout } from \"@/components/Layout\";\nimport { useApiService } from \"@/lib/api\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { Label } from \"@/components/ui/label\";\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport {\n Mail,\n Phone,\n MapPin,\n MessageSquare,\n Facebook,\n Twitter,\n Instagram,\n Linkedin,\n} from \"lucide-react\";\nimport constants from \"@/constants/constants.json\";\nimport { FadeIn, SlideInLeft, SlideInRight } from \"@/modules/animations\";\n\nconst socialIcons: Record<string, React.ElementType> = {\n facebook: Facebook,\n twitter: Twitter,\n instagram: Instagram,\n linkedin: Linkedin,\n};\n\nexport function ContactPage() {\n const { t } = useTranslation(\"contact-page\");\n usePageTitle({ title: t(\"title\") });\n\n const apiService = useApiService();\n\n const socialLinks = useMemo(() => {\n const socialMedia = constants.socialMedia as\n | Record<string, string>\n | undefined;\n if (!socialMedia) return [];\n return Object.entries(socialMedia)\n .filter(([platform, url]) => url && socialIcons[platform])\n .map(([platform, url]) => ({\n platform,\n url,\n Icon: socialIcons[platform],\n }));\n }, []);\n\n const [formData, setFormData] = useState({\n name: \"\",\n email: \"\",\n phone: \"\",\n subject: \"\",\n message: \"\",\n });\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [submitStatus, setSubmitStatus] = useState<\n \"idle\" | \"success\" | \"error\"\n >(\"idle\");\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setIsSubmitting(true);\n setSubmitStatus(\"idle\");\n\n try {\n const currentLanguage = constants.site.defaultLanguage;\n\n await apiService.submitForm(\n formData,\n {\n email_subject1: \"Thank you for contacting us\",\n email_subject2: \"New Contact Form Submission\",\n fields: [\n { name: \"name\", required: true },\n { name: \"email\", required: true },\n { name: \"phone\", required: false },\n { name: \"subject\", required: false },\n { name: \"message\", required: true },\n ],\n },\n currentLanguage\n );\n\n setSubmitStatus(\"success\");\n setFormData({\n name: \"\",\n email: \"\",\n phone: \"\",\n subject: \"\",\n message: \"\",\n });\n\n setTimeout(() => {\n setSubmitStatus(\"idle\");\n }, 5000);\n } catch (error: unknown) {\n console.error(\"Form submission failed:\", error);\n setSubmitStatus(\"error\");\n\n setTimeout(() => {\n setSubmitStatus(\"idle\");\n }, 5000);\n } finally {\n setIsSubmitting(false);\n }\n };\n\n const handleChange = (\n e: React.ChangeEvent<\n HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement\n >\n ) => {\n setFormData((prev) => ({\n ...prev,\n [e.target.name]: e.target.value,\n }));\n };\n\n return (\n <Layout>\n <div className=\"min-h-screen bg-muted/30 py-12\">\n <div className=\"container mx-auto px-4 max-w-6xl\">\n {/* Hero Section */}\n <FadeIn className=\"text-center mb-12\">\n <h1 className=\"text-4xl font-bold text-foreground mb-4\">\n {t(\"title\")}\n </h1>\n <div className=\"w-16 h-1 bg-primary mx-auto mb-6\"></div>\n <p className=\"text-lg text-muted-foreground max-w-3xl mx-auto\">\n {t(\"description\")}\n </p>\n </FadeIn>\n\n <div className=\"grid lg:grid-cols-3 gap-8\">\n {/* Contact Information */}\n <SlideInLeft className=\"lg:col-span-1 space-y-6\">\n {/* Company Info */}\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <MessageSquare className=\"w-5 h-5 text-primary\" />\n {t(\"getInTouch\")}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"flex items-start gap-3\">\n <Mail className=\"w-5 h-5 text-primary mt-1\" />\n <div>\n <p className=\"font-medium text-foreground\">\n {t(\"email\")}\n </p>\n <p className=\"text-muted-foreground\">{constants.email}</p>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {t(\"emailResponse\")}\n </p>\n </div>\n </div>\n\n <div className=\"flex items-start gap-3\">\n <Phone className=\"w-5 h-5 text-primary mt-1\" />\n <div>\n <p className=\"font-medium text-foreground\">\n {t(\"phone\")}\n </p>\n <p className=\"text-muted-foreground\">{constants.phone}</p>\n </div>\n </div>\n\n <div className=\"flex items-start gap-3\">\n <MapPin className=\"w-5 h-5 text-primary mt-1\" />\n <div>\n <p className=\"font-medium text-foreground\">\n {t(\"address\")}\n </p>\n <div className=\"text-muted-foreground\">\n <p>{constants.address.line1}</p>\n {constants.address.line2 && (\n <p>{constants.address.line2}</p>\n )}\n <p>\n {constants.address.city}, {constants.address.state}{\" \"}\n {constants.address.postalCode}\n </p>\n {constants.address.country && (\n <p>{constants.address.country}</p>\n )}\n </div>\n </div>\n </div>\n\n {/* Social Media Links */}\n {socialLinks.length > 0 && (\n <div className=\"pt-4 border-t\">\n <p className=\"font-medium text-foreground mb-3\">\n {t(\"followUs\")}\n </p>\n <div className=\"flex gap-2\">\n {socialLinks.map(({ platform, url, Icon }) => (\n <a\n key={platform}\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"h-10 w-10 flex items-center justify-center rounded-md border text-muted-foreground hover:text-primary hover:border-primary transition-colors\"\n >\n <Icon className=\"h-5 w-5\" />\n </a>\n ))}\n </div>\n </div>\n )}\n </CardContent>\n </Card>\n\n {/* Support Info */}\n <Card>\n <CardHeader>\n <CardTitle>{t(\"needSupport\")}</CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-muted-foreground mb-4\">\n {t(\"supportDescription\")}\n </p>\n <div className=\"space-y-2\">\n <p className=\"font-medium text-foreground\">\n {t(\"supportEmail\")}\n </p>\n <p className=\"text-muted-foreground\">{constants.email}</p>\n </div>\n </CardContent>\n </Card>\n </SlideInLeft>\n\n {/* Contact Form */}\n <SlideInRight className=\"lg:col-span-2\">\n <Card>\n <CardHeader>\n <CardTitle>{t(\"sendMessage\")}</CardTitle>\n </CardHeader>\n <CardContent>\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* Full Name */}\n <div>\n <Label htmlFor=\"name\">{t(\"fullName\")} *</Label>\n <Input\n id=\"name\"\n name=\"name\"\n type=\"text\"\n value={formData.name}\n onChange={handleChange}\n placeholder={t(\"fullNamePlaceholder\")}\n required\n className=\"mt-1\"\n />\n </div>\n\n {/* Email */}\n <div>\n <Label htmlFor=\"email\">{t(\"emailAddress\")} *</Label>\n <Input\n id=\"email\"\n name=\"email\"\n type=\"email\"\n value={formData.email}\n onChange={handleChange}\n placeholder={t(\"emailPlaceholder\")}\n required\n className=\"mt-1\"\n />\n </div>\n\n {/* Phone */}\n <div>\n <Label htmlFor=\"phone\">{t(\"phoneNumber\")}</Label>\n <Input\n id=\"phone\"\n name=\"phone\"\n type=\"tel\"\n value={formData.phone}\n onChange={handleChange}\n placeholder={t(\"phonePlaceholder\")}\n className=\"mt-1\"\n />\n </div>\n\n {/* Subject */}\n <div>\n <Label htmlFor=\"subject\">{t(\"subject\")}</Label>\n <Input\n id=\"subject\"\n name=\"subject\"\n type=\"text\"\n value={formData.subject}\n onChange={handleChange}\n placeholder={t(\"subjectPlaceholder\")}\n className=\"mt-1\"\n />\n </div>\n\n {/* Message */}\n <div>\n <Label htmlFor=\"message\">{t(\"message\")} *</Label>\n <Textarea\n id=\"message\"\n name=\"message\"\n value={formData.message}\n onChange={handleChange}\n placeholder={t(\"messagePlaceholder\")}\n required\n rows={6}\n className=\"mt-1 resize-none\"\n />\n </div>\n\n {submitStatus === \"success\" && (\n <div className=\"p-4 bg-green-500/10 dark:bg-green-500/20 border border-green-500/30 rounded-lg\">\n <p className=\"text-green-600 dark:text-green-400 text-sm font-medium\">\n {t(\"success\")}\n </p>\n </div>\n )}\n\n {submitStatus === \"error\" && (\n <div className=\"p-4 bg-destructive/10 border border-destructive/30 rounded-lg\">\n <p className=\"text-destructive text-sm font-medium\">\n {t(\"error\")}\n </p>\n </div>\n )}\n\n <Button\n type=\"submit\"\n size=\"lg\"\n className=\"w-full\"\n disabled={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(\"sending\")}\n </>\n ) : (\n t(\"submit\")\n )}\n </Button>\n </form>\n </CardContent>\n </Card>\n </SlideInRight>\n </div>\n </div>\n </div>\n </Layout>\n );\n}\n"
23
+ "content": "import React, { useState, useMemo } from \"react\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { useTranslation } from \"react-i18next\";\nimport { Layout } from \"@/components/Layout\";\nimport { useApiService } from \"@/lib/api\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { Label } from \"@/components/ui/label\";\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport {\n Mail,\n Phone,\n MapPin,\n MessageSquare,\n Facebook,\n Twitter,\n Instagram,\n Linkedin,\n} from \"lucide-react\";\nimport constants from \"@/constants/constants.json\";\nimport { FadeIn, SlideInLeft, SlideInRight } from \"@/modules/animations\";\n\nconst socialIcons: Record<string, React.ElementType> = {\n facebook: Facebook,\n twitter: Twitter,\n instagram: Instagram,\n linkedin: Linkedin,\n};\n\nexport function ContactPage() {\n const { t } = useTranslation(\"contact-page\");\n usePageTitle({ title: t(\"title\") });\n\n const apiService = useApiService();\n\n const socialLinks = useMemo(() => {\n const socialMedia = constants.socialMedia as\n | Record<string, string>\n | undefined;\n if (!socialMedia) return [];\n return Object.entries(socialMedia)\n .filter(([platform, url]) => url && socialIcons[platform])\n .map(([platform, url]) => ({\n platform,\n url,\n Icon: socialIcons[platform],\n }));\n }, []);\n\n const [formData, setFormData] = useState({\n name: \"\",\n email: \"\",\n phone: \"\",\n subject: \"\",\n message: \"\",\n });\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [submitStatus, setSubmitStatus] = useState<\n \"idle\" | \"success\" | \"error\"\n >(\"idle\");\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setIsSubmitting(true);\n setSubmitStatus(\"idle\");\n\n try {\n const currentLanguage = constants.site.defaultLanguage;\n\n await apiService.submitForm(\n formData,\n {\n email_subject1: \"Thank you for contacting us\",\n email_subject2: \"New Contact Form Submission\",\n fields: [\n { name: \"name\", required: true },\n { name: \"email\", required: true },\n { name: \"phone\", required: false },\n { name: \"subject\", required: false },\n { name: \"message\", required: true },\n ],\n },\n currentLanguage\n );\n\n setSubmitStatus(\"success\");\n setFormData({\n name: \"\",\n email: \"\",\n phone: \"\",\n subject: \"\",\n message: \"\",\n });\n\n setTimeout(() => {\n setSubmitStatus(\"idle\");\n }, 5000);\n } catch (error: unknown) {\n console.error(\"Form submission failed:\", error);\n setSubmitStatus(\"error\");\n\n setTimeout(() => {\n setSubmitStatus(\"idle\");\n }, 5000);\n } finally {\n setIsSubmitting(false);\n }\n };\n\n const handleChange = (\n e: React.ChangeEvent<\n HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement\n >\n ) => {\n setFormData((prev) => ({\n ...prev,\n [e.target.name]: e.target.value,\n }));\n };\n\n return (\n <Layout>\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 max-w-6xl\">\n {/* Hero Section */}\n <FadeIn className=\"text-center mb-12\">\n <h1 className=\"text-4xl font-bold text-foreground mb-4\">\n {t(\"title\")}\n </h1>\n <div className=\"w-16 h-1 bg-primary mx-auto mb-6\"></div>\n <p className=\"text-lg text-muted-foreground max-w-3xl mx-auto\">\n {t(\"description\")}\n </p>\n </FadeIn>\n\n <div className=\"grid lg:grid-cols-3 gap-8\">\n {/* Contact Information */}\n <SlideInLeft className=\"lg:col-span-1 space-y-6\">\n {/* Company Info */}\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <MessageSquare className=\"w-5 h-5 text-primary\" />\n {t(\"getInTouch\")}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"flex items-start gap-3\">\n <Mail className=\"w-5 h-5 text-primary mt-1\" />\n <div>\n <p className=\"font-medium text-foreground\">\n {t(\"email\")}\n </p>\n <p className=\"text-muted-foreground\">{constants.email}</p>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {t(\"emailResponse\")}\n </p>\n </div>\n </div>\n\n <div className=\"flex items-start gap-3\">\n <Phone className=\"w-5 h-5 text-primary mt-1\" />\n <div>\n <p className=\"font-medium text-foreground\">\n {t(\"phone\")}\n </p>\n <p className=\"text-muted-foreground\">{constants.phone}</p>\n </div>\n </div>\n\n <div className=\"flex items-start gap-3\">\n <MapPin className=\"w-5 h-5 text-primary mt-1\" />\n <div>\n <p className=\"font-medium text-foreground\">\n {t(\"address\")}\n </p>\n <div className=\"text-muted-foreground\">\n <p>{constants.address.line1}</p>\n {constants.address.line2 && (\n <p>{constants.address.line2}</p>\n )}\n <p>\n {constants.address.city}, {constants.address.state}{\" \"}\n {constants.address.postalCode}\n </p>\n {constants.address.country && (\n <p>{constants.address.country}</p>\n )}\n </div>\n </div>\n </div>\n\n {/* Social Media Links */}\n {socialLinks.length > 0 && (\n <div className=\"pt-4 border-t\">\n <p className=\"font-medium text-foreground mb-3\">\n {t(\"followUs\")}\n </p>\n <div className=\"flex gap-2\">\n {socialLinks.map(({ platform, url, Icon }) => (\n <a\n key={platform}\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"h-10 w-10 flex items-center justify-center rounded-md border text-muted-foreground hover:text-primary hover:border-primary transition-colors\"\n >\n <Icon className=\"h-5 w-5\" />\n </a>\n ))}\n </div>\n </div>\n )}\n </CardContent>\n </Card>\n\n {/* Support Info */}\n <Card>\n <CardHeader>\n <CardTitle>{t(\"needSupport\")}</CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-muted-foreground mb-4\">\n {t(\"supportDescription\")}\n </p>\n <div className=\"space-y-2\">\n <p className=\"font-medium text-foreground\">\n {t(\"supportEmail\")}\n </p>\n <p className=\"text-muted-foreground\">{constants.email}</p>\n </div>\n </CardContent>\n </Card>\n </SlideInLeft>\n\n {/* Contact Form */}\n <SlideInRight className=\"lg:col-span-2\">\n <Card>\n <CardHeader>\n <CardTitle>{t(\"sendMessage\")}</CardTitle>\n </CardHeader>\n <CardContent>\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* Full Name */}\n <div>\n <Label htmlFor=\"name\">{t(\"fullName\")} *</Label>\n <Input\n id=\"name\"\n name=\"name\"\n type=\"text\"\n value={formData.name}\n onChange={handleChange}\n placeholder={t(\"fullNamePlaceholder\")}\n required\n className=\"mt-1\"\n />\n </div>\n\n {/* Email */}\n <div>\n <Label htmlFor=\"email\">{t(\"emailAddress\")} *</Label>\n <Input\n id=\"email\"\n name=\"email\"\n type=\"email\"\n value={formData.email}\n onChange={handleChange}\n placeholder={t(\"emailPlaceholder\")}\n required\n className=\"mt-1\"\n />\n </div>\n\n {/* Phone */}\n <div>\n <Label htmlFor=\"phone\">{t(\"phoneNumber\")}</Label>\n <Input\n id=\"phone\"\n name=\"phone\"\n type=\"tel\"\n value={formData.phone}\n onChange={handleChange}\n placeholder={t(\"phonePlaceholder\")}\n className=\"mt-1\"\n />\n </div>\n\n {/* Subject */}\n <div>\n <Label htmlFor=\"subject\">{t(\"subject\")}</Label>\n <Input\n id=\"subject\"\n name=\"subject\"\n type=\"text\"\n value={formData.subject}\n onChange={handleChange}\n placeholder={t(\"subjectPlaceholder\")}\n className=\"mt-1\"\n />\n </div>\n\n {/* Message */}\n <div>\n <Label htmlFor=\"message\">{t(\"message\")} *</Label>\n <Textarea\n id=\"message\"\n name=\"message\"\n value={formData.message}\n onChange={handleChange}\n placeholder={t(\"messagePlaceholder\")}\n required\n rows={6}\n className=\"mt-1 resize-none\"\n />\n </div>\n\n {submitStatus === \"success\" && (\n <div className=\"p-4 bg-green-500/10 dark:bg-green-500/20 border border-green-500/30 rounded-lg\">\n <p className=\"text-green-600 dark:text-green-400 text-sm font-medium\">\n {t(\"success\")}\n </p>\n </div>\n )}\n\n {submitStatus === \"error\" && (\n <div className=\"p-4 bg-destructive/10 border border-destructive/30 rounded-lg\">\n <p className=\"text-destructive text-sm font-medium\">\n {t(\"error\")}\n </p>\n </div>\n )}\n\n <Button\n type=\"submit\"\n size=\"lg\"\n className=\"w-full\"\n disabled={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(\"sending\")}\n </>\n ) : (\n t(\"submit\")\n )}\n </Button>\n </form>\n </CardContent>\n </Card>\n </SlideInRight>\n </div>\n </div>\n </div>\n </Layout>\n );\n}\n"
24
24
  },
25
25
  {
26
26
  "path": "contact-page/lang/en.json",
@@ -20,7 +20,7 @@
20
20
  "path": "cookies-page/cookies-page.tsx",
21
21
  "type": "registry:page",
22
22
  "target": "$modules$/cookies-page/cookies-page.tsx",
23
- "content": "import { useTranslation } from \"react-i18next\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { Layout } from \"@/components/Layout\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { Cookie, Settings, BarChart, Megaphone, Shield, Info } from \"lucide-react\";\nimport { FadeIn, StaggerContainer, StaggerItem } from \"@/modules/animations\";\n\nexport function CookiesPage() {\n const { t } = useTranslation(\"cookies-page\");\n usePageTitle({ title: t(\"title\") });\n\n const cookieTypes = [\n { icon: Shield, titleKey: \"essentialTitle\", descKey: \"essentialDesc\" },\n { icon: BarChart, titleKey: \"analyticsTitle\", descKey: \"analyticsDesc\" },\n { icon: Settings, titleKey: \"functionalTitle\", descKey: \"functionalDesc\" },\n { icon: Megaphone, titleKey: \"marketingTitle\", descKey: \"marketingDesc\" },\n ];\n\n return (\n <Layout>\n <div className=\"min-h-screen bg-muted/30 py-12\">\n <div className=\"container mx-auto px-4\">\n {/* Header */}\n <FadeIn className=\"text-center mb-12\">\n <div className=\"w-16 h-16 bg-primary/10 rounded-full flex items-center justify-center mx-auto mb-4\">\n <Cookie className=\"w-8 h-8 text-primary\" />\n </div>\n <h1 className=\"text-4xl font-bold text-foreground mb-4\">\n {t(\"title\")}\n </h1>\n <p className=\"text-muted-foreground\">\n {t(\"lastUpdated\")}: {t(\"updateDate\")}\n </p>\n </FadeIn>\n\n {/* Introduction */}\n <FadeIn delay={0.1} className=\"max-w-4xl mx-auto mb-12\">\n <Card>\n <CardContent className=\"p-8\">\n <p className=\"text-muted-foreground leading-relaxed\">\n {t(\"introduction\")}\n </p>\n </CardContent>\n </Card>\n </FadeIn>\n\n {/* What Are Cookies */}\n <FadeIn delay={0.2} className=\"max-w-4xl mx-auto mb-8\">\n <Card>\n <CardContent className=\"p-8\">\n <div className=\"flex items-start gap-4\">\n <div className=\"w-10 h-10 bg-primary/10 rounded-lg flex items-center justify-center flex-shrink-0\">\n <Info className=\"w-5 h-5 text-primary\" />\n </div>\n <div className=\"flex-1\">\n <h2 className=\"text-xl font-semibold mb-3\">{t(\"whatTitle\")}</h2>\n <p className=\"text-muted-foreground\">{t(\"whatContent\")}</p>\n </div>\n </div>\n </CardContent>\n </Card>\n </FadeIn>\n\n {/* Cookie Types */}\n <div className=\"max-w-4xl mx-auto mb-8\">\n <FadeIn className=\"text-center mb-6\">\n <h2 className=\"text-2xl font-semibold\">{t(\"typesTitle\")}</h2>\n </FadeIn>\n <StaggerContainer className=\"grid md:grid-cols-2 gap-6\">\n {cookieTypes.map(({ icon: Icon, titleKey, descKey }) => (\n <StaggerItem key={titleKey}>\n <Card className=\"h-full\">\n <CardContent className=\"p-6\">\n <div className=\"flex items-start gap-4\">\n <div className=\"w-10 h-10 bg-primary/10 rounded-lg flex items-center justify-center flex-shrink-0\">\n <Icon className=\"w-5 h-5 text-primary\" />\n </div>\n <div className=\"flex-1\">\n <h3 className=\"font-semibold mb-2\">{t(titleKey)}</h3>\n <p className=\"text-sm text-muted-foreground\">{t(descKey)}</p>\n </div>\n </div>\n </CardContent>\n </Card>\n </StaggerItem>\n ))}\n </StaggerContainer>\n </div>\n\n {/* Management */}\n <FadeIn className=\"max-w-4xl mx-auto mb-12\">\n <Card>\n <CardContent className=\"p-8\">\n <div className=\"flex items-start gap-4\">\n <div className=\"w-10 h-10 bg-primary/10 rounded-lg flex items-center justify-center flex-shrink-0\">\n <Settings className=\"w-5 h-5 text-primary\" />\n </div>\n <div className=\"flex-1\">\n <h2 className=\"text-xl font-semibold mb-3\">{t(\"managementTitle\")}</h2>\n <p className=\"text-muted-foreground\">{t(\"managementContent\")}</p>\n </div>\n </div>\n </CardContent>\n </Card>\n </FadeIn>\n\n {/* Footer Note */}\n <FadeIn className=\"max-w-4xl mx-auto text-center\">\n <p className=\"text-sm text-muted-foreground\">{t(\"footerNote\")}</p>\n </FadeIn>\n </div>\n </div>\n </Layout>\n );\n}\n\nexport default CookiesPage;\n"
23
+ "content": "import { useTranslation } from \"react-i18next\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { Layout } from \"@/components/Layout\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { Cookie, Settings, BarChart, Megaphone, Shield, Info } from \"lucide-react\";\nimport { FadeIn, StaggerContainer, StaggerItem } from \"@/modules/animations\";\n\nexport function CookiesPage() {\n const { t } = useTranslation(\"cookies-page\");\n usePageTitle({ title: t(\"title\") });\n\n const cookieTypes = [\n { icon: Shield, titleKey: \"essentialTitle\", descKey: \"essentialDesc\" },\n { icon: BarChart, titleKey: \"analyticsTitle\", descKey: \"analyticsDesc\" },\n { icon: Settings, titleKey: \"functionalTitle\", descKey: \"functionalDesc\" },\n { icon: Megaphone, titleKey: \"marketingTitle\", descKey: \"marketingDesc\" },\n ];\n\n return (\n <Layout>\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 <FadeIn className=\"text-center mb-12\">\n <div className=\"w-16 h-16 bg-primary/10 rounded-full flex items-center justify-center mx-auto mb-4\">\n <Cookie className=\"w-8 h-8 text-primary\" />\n </div>\n <h1 className=\"text-4xl font-bold text-foreground mb-4\">\n {t(\"title\")}\n </h1>\n <p className=\"text-muted-foreground\">\n {t(\"lastUpdated\")}: {t(\"updateDate\")}\n </p>\n </FadeIn>\n\n {/* Introduction */}\n <FadeIn delay={0.1} className=\"max-w-4xl mx-auto mb-12\">\n <Card>\n <CardContent className=\"p-8\">\n <p className=\"text-muted-foreground leading-relaxed\">\n {t(\"introduction\")}\n </p>\n </CardContent>\n </Card>\n </FadeIn>\n\n {/* What Are Cookies */}\n <FadeIn delay={0.2} className=\"max-w-4xl mx-auto mb-8\">\n <Card>\n <CardContent className=\"p-8\">\n <div className=\"flex items-start gap-4\">\n <div className=\"w-10 h-10 bg-primary/10 rounded-lg flex items-center justify-center flex-shrink-0\">\n <Info className=\"w-5 h-5 text-primary\" />\n </div>\n <div className=\"flex-1\">\n <h2 className=\"text-xl font-semibold mb-3\">{t(\"whatTitle\")}</h2>\n <p className=\"text-muted-foreground\">{t(\"whatContent\")}</p>\n </div>\n </div>\n </CardContent>\n </Card>\n </FadeIn>\n\n {/* Cookie Types */}\n <div className=\"max-w-4xl mx-auto mb-8\">\n <FadeIn className=\"text-center mb-6\">\n <h2 className=\"text-2xl font-semibold\">{t(\"typesTitle\")}</h2>\n </FadeIn>\n <StaggerContainer className=\"grid md:grid-cols-2 gap-6\">\n {cookieTypes.map(({ icon: Icon, titleKey, descKey }) => (\n <StaggerItem key={titleKey}>\n <Card className=\"h-full\">\n <CardContent className=\"p-6\">\n <div className=\"flex items-start gap-4\">\n <div className=\"w-10 h-10 bg-primary/10 rounded-lg flex items-center justify-center flex-shrink-0\">\n <Icon className=\"w-5 h-5 text-primary\" />\n </div>\n <div className=\"flex-1\">\n <h3 className=\"font-semibold mb-2\">{t(titleKey)}</h3>\n <p className=\"text-sm text-muted-foreground\">{t(descKey)}</p>\n </div>\n </div>\n </CardContent>\n </Card>\n </StaggerItem>\n ))}\n </StaggerContainer>\n </div>\n\n {/* Management */}\n <FadeIn className=\"max-w-4xl mx-auto mb-12\">\n <Card>\n <CardContent className=\"p-8\">\n <div className=\"flex items-start gap-4\">\n <div className=\"w-10 h-10 bg-primary/10 rounded-lg flex items-center justify-center flex-shrink-0\">\n <Settings className=\"w-5 h-5 text-primary\" />\n </div>\n <div className=\"flex-1\">\n <h2 className=\"text-xl font-semibold mb-3\">{t(\"managementTitle\")}</h2>\n <p className=\"text-muted-foreground\">{t(\"managementContent\")}</p>\n </div>\n </div>\n </CardContent>\n </Card>\n </FadeIn>\n\n {/* Footer Note */}\n <FadeIn className=\"max-w-4xl mx-auto text-center\">\n <p className=\"text-sm text-muted-foreground\">{t(\"footerNote\")}</p>\n </FadeIn>\n </div>\n </div>\n </Layout>\n );\n}\n\nexport default CookiesPage;\n"
24
24
  },
25
25
  {
26
26
  "path": "cookies-page/lang/en.json",
@@ -16,7 +16,7 @@
16
16
  "path": "cta-section/cta-section.tsx",
17
17
  "type": "registry:component",
18
18
  "target": "$modules$/cta-section/cta-section.tsx",
19
- "content": "import { Link } from \"react-router\";\nimport { Button } from \"@/components/ui/button\";\nimport { useTranslation } from \"react-i18next\";\n\nexport function CtaSection() {\n const { t } = useTranslation(\"cta-section\");\n\n return (\n <section className=\"py-20 bg-primary text-primary-foreground\">\n <div className=\"container mx-auto px-4 text-center\">\n <h2 className=\"text-3xl lg:text-4xl font-bold mb-6\">\n {t(\"title\", \"Ready to Start Your Project?\")}\n </h2>\n <p className=\"text-xl opacity-90 max-w-2xl mx-auto mb-8\">\n {t(\n \"description\",\n \"Let's discuss your needs and create a solution that drives results for your business.\"\n )}\n </p>\n <div className=\"flex flex-col sm:flex-row gap-4 justify-center\">\n <Button\n size=\"lg\"\n variant=\"secondary\"\n className=\"px-8 py-3 text-lg\"\n asChild\n >\n <Link to=\"/contact\">{t(\"primaryButton\", \"Get Free Quote\")}</Link>\n </Button>\n <Button\n size=\"lg\"\n variant=\"outline\"\n className=\"px-8 py-3 text-lg border-primary-foreground text-primary-foreground bg-transparent hover:bg-primary-foreground hover:text-primary transition-all duration-300\"\n asChild\n >\n <Link to=\"/about\" className=\"text-primary-foreground hover:text-primary\">\n {t(\"secondaryButton\", \"Learn About Us\")}\n </Link>\n </Button>\n </div>\n </div>\n </section>\n );\n}\n"
19
+ "content": "import { Link } from \"react-router\";\nimport { Button } from \"@/components/ui/button\";\nimport { useTranslation } from \"react-i18next\";\n\nexport function CtaSection() {\n const { t } = useTranslation(\"cta-section\");\n\n return (\n <section className=\"py-20 bg-primary text-primary-foreground\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 text-center\">\n <h2 className=\"text-3xl lg:text-4xl font-bold mb-6\">\n {t(\"title\", \"Ready to Start Your Project?\")}\n </h2>\n <p className=\"text-xl opacity-90 max-w-2xl mx-auto mb-8\">\n {t(\n \"description\",\n \"Let's discuss your needs and create a solution that drives results for your business.\"\n )}\n </p>\n <div className=\"flex flex-col sm:flex-row gap-4 justify-center\">\n <Button\n size=\"lg\"\n variant=\"secondary\"\n className=\"px-8 py-3 text-lg\"\n asChild\n >\n <Link to=\"/contact\">{t(\"primaryButton\", \"Get Free Quote\")}</Link>\n </Button>\n <Button\n size=\"lg\"\n variant=\"outline\"\n className=\"px-8 py-3 text-lg border-primary-foreground text-primary-foreground bg-transparent hover:bg-primary-foreground hover:text-primary transition-all duration-300\"\n asChild\n >\n <Link to=\"/about\" className=\"text-primary-foreground hover:text-primary\">\n {t(\"secondaryButton\", \"Learn About Us\")}\n </Link>\n </Button>\n </div>\n </div>\n </section>\n );\n}\n"
20
20
  },
21
21
  {
22
22
  "path": "cta-section/lang/en.json",