@promakeai/cli 0.5.8 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -71
- package/dist/index.js +259 -254
- package/dist/registry/about-page.json +3 -3
- package/dist/registry/about-section.json +4 -4
- package/dist/registry/animations.json +2 -2
- package/dist/registry/announcement-bar.json +4 -4
- package/dist/registry/api.json +1 -1
- package/dist/registry/auth-core.json +3 -3
- package/dist/registry/bento-grid-section.json +4 -4
- package/dist/registry/blog-core.json +5 -5
- package/dist/registry/blog-list-page.json +4 -4
- package/dist/registry/blog-section.json +4 -4
- package/dist/registry/cards-carousel-section.json +4 -4
- package/dist/registry/cart-drawer.json +4 -4
- package/dist/registry/cart-page.json +4 -4
- package/dist/registry/case-study-page.json +3 -3
- package/dist/registry/category-section.json +4 -4
- package/dist/registry/checkout-page.json +4 -4
- package/dist/registry/coming-soon-page-minimal.json +4 -4
- package/dist/registry/coming-soon-page.json +4 -4
- package/dist/registry/contact-info-grid.json +4 -4
- package/dist/registry/contact-page-centered.json +4 -4
- package/dist/registry/contact-page-map-overlay.json +3 -3
- package/dist/registry/contact-page-map-split.json +3 -3
- package/dist/registry/contact-page-split.json +4 -4
- package/dist/registry/contact-page.json +4 -4
- package/dist/registry/content-section.json +4 -4
- package/dist/registry/cookie-consent.json +4 -4
- package/dist/registry/cookies-page.json +3 -3
- package/dist/registry/cta-section.json +3 -3
- package/dist/registry/ecommerce-core.json +8 -8
- package/dist/registry/empty-page.json +3 -3
- package/dist/registry/faq-categorized.json +4 -4
- package/dist/registry/faq-simple.json +4 -4
- package/dist/registry/favorites-blog-block.json +1 -1
- package/dist/registry/favorites-blog-page.json +4 -4
- package/dist/registry/favorites-ecommerce-block.json +1 -1
- package/dist/registry/favorites-ecommerce-page.json +4 -4
- package/dist/registry/feature-section.json +3 -3
- package/dist/registry/featured-products.json +4 -4
- package/dist/registry/footer-detailed.json +4 -4
- package/dist/registry/footer-minimal.json +3 -3
- package/dist/registry/footer.json +3 -3
- package/dist/registry/forgot-password-page-split.json +4 -4
- package/dist/registry/forgot-password-page.json +4 -4
- package/dist/registry/google-adsense.json +4 -4
- package/dist/registry/google-map.json +2 -2
- package/dist/registry/header-centered-pill.json +4 -4
- package/dist/registry/header-ecommerce.json +4 -4
- package/dist/registry/header-mega.json +4 -4
- package/dist/registry/header-minimal.json +4 -4
- package/dist/registry/header-simple.json +3 -3
- package/dist/registry/hero-carousel.json +3 -3
- package/dist/registry/hero-cta.json +4 -4
- package/dist/registry/hero-gradient.json +4 -4
- package/dist/registry/hero-grid.json +4 -4
- package/dist/registry/hero-profile.json +3 -3
- package/dist/registry/hero.json +3 -3
- package/dist/registry/index.json +103 -103
- package/dist/registry/landing-page-app.json +3 -3
- package/dist/registry/landing-page-saas.json +3 -3
- package/dist/registry/login-page-split.json +4 -4
- package/dist/registry/login-page.json +4 -4
- package/dist/registry/logo-cloud.json +4 -4
- package/dist/registry/masonry-grid.json +3 -3
- package/dist/registry/my-orders-page.json +4 -4
- package/dist/registry/newsletter-section.json +4 -4
- package/dist/registry/order-card-compact.json +1 -1
- package/dist/registry/order-confirmation-page.json +4 -4
- package/dist/registry/order-detail-block.json +1 -1
- package/dist/registry/orders-list-block.json +1 -1
- package/dist/registry/payment-success-block.json +2 -2
- package/dist/registry/portfolio-page.json +4 -4
- package/dist/registry/post-card.json +4 -4
- package/dist/registry/post-detail-block.json +2 -2
- package/dist/registry/post-detail-page.json +4 -4
- package/dist/registry/pricing-card.json +3 -3
- package/dist/registry/pricing-page.json +4 -4
- package/dist/registry/pricing-section.json +4 -4
- package/dist/registry/privacy-page.json +3 -3
- package/dist/registry/product-card-detailed.json +4 -4
- package/dist/registry/product-card-hover.json +4 -4
- package/dist/registry/product-card.json +4 -4
- package/dist/registry/product-detail-block.json +2 -2
- package/dist/registry/product-detail-page.json +4 -4
- package/dist/registry/product-detail-section.json +4 -4
- package/dist/registry/product-quick-view.json +4 -4
- package/dist/registry/products-page.json +4 -4
- package/dist/registry/reading-progress.json +4 -4
- package/dist/registry/register-page-split.json +4 -4
- package/dist/registry/register-page.json +4 -4
- package/dist/registry/related-posts-block.json +1 -1
- package/dist/registry/related-products-block.json +2 -2
- package/dist/registry/reset-password-page-split.json +4 -4
- package/dist/registry/reset-password-page.json +4 -4
- package/dist/registry/service-card.json +1 -1
- package/dist/registry/share-buttons.json +4 -4
- package/dist/registry/skill-card.json +1 -1
- package/dist/registry/team-page.json +4 -4
- package/dist/registry/terms-page.json +3 -3
- package/dist/registry/testimonials-carousel.json +4 -4
- package/dist/registry/testimonials-grid.json +4 -4
- package/dist/registry/timeline-section.json +4 -4
- package/dist/registry/video-hero.json +4 -4
- package/dist/registry/youtube-embed.json +4 -4
- package/package.json +59 -56
- package/template/.env +6 -6
- package/template/README.md +54 -54
- package/template/eslint.config.js +41 -41
- package/template/package.json +95 -95
- package/template/public/_redirects +1 -1
- package/template/public/robots.txt +14 -14
- package/template/scripts/init-db.ts +18 -18
- package/template/src/App.tsx +19 -19
- package/template/src/components/FormField.tsx +48 -48
- package/template/src/components/FormFileInput.tsx +75 -75
- package/template/src/components/GoogleAnalytics.tsx +34 -34
- package/template/src/components/LanguageSwitcher.tsx +53 -53
- package/template/src/components/PasswordInput.tsx +60 -60
- package/template/src/components/ScriptInjector.tsx +62 -62
- package/template/src/components/Stack.tsx +39 -39
- package/template/src/constants/constants.json +67 -67
- package/template/src/db/index.ts +20 -20
- package/template/src/db/provider.tsx +78 -78
- package/template/src/db/schema.json +258 -258
- package/template/src/db/types.ts +195 -195
- package/template/src/hooks/use-debounced-value.ts +12 -12
- package/template/src/lang/index.ts +90 -90
- package/template/src/lib/api.ts +345 -345
- package/template/src/lib/env.ts +19 -19
- package/template/src/router.tsx +14 -14
- package/template/src/vite-env.d.ts +1 -1
- package/template/vite.config.ts +68 -64
- package/template/public/data/database.db-shm +0 -0
- package/template/public/data/database.db-wal +0 -0
|
@@ -30,19 +30,19 @@
|
|
|
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 { 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\";\nimport { FormFileInput } from \"@/components/FormFileInput\";\nimport { FormField } from \"@/components/FormField\";\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 attachments: [] as File[]\n });\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [submitStatus, setSubmitStatus] = useState<\n \"idle\" | \"success\" | \"error\"\n >(\"idle\");\n const handleFileUploadChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const selectedFiles = Array.from(e.target.files || []) as File[];\n const maxFiles = constants.file?.maxFiles || 5;\n\n const remainingSlots = maxFiles - formData.attachments.length;\n\n // If the limit is exceeded, alert and do not add any files\n if (selectedFiles.length > remainingSlots) {\n alert(t(\"maxFilesLimit\", { max: maxFiles }));\n e.target.value = ''; // Clear the input\n return;\n }\n\n setFormData(prev => ({\n ...prev,\n attachments: [...prev.attachments, ...selectedFiles]\n }));\n\n e.target.value = ''; // Clear the input\n }\n\n const handleRemoveFile = (index: number) => {\n setFormData(prev => ({\n ...prev,\n attachments: prev.attachments.filter((_, i) => i !== index)\n }));\n };\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 try {\n const currentLanguage = constants.site.defaultLanguage;\n\n await apiService.submitFormWithFile(\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 { name: \"attachments\", required: false },\n ],\n },\n currentLanguage\n );\n\n setSubmitStatus(\"success\");\n setFormData({\n name: \"\",\n email: \"\",\n message: \"\",\n attachments: []\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-[calc(100vh-4rem)]\">\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-[calc(100vh-4rem)] flex items-center py-12 px-4\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto\">\n <div className=\"grid lg:grid-cols-2 gap-8 items-stretch\">\n {/* Form Card - Glassmorphism */}\n <Card className=\"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 <FormField label={t(\"fullName\")} htmlFor=\"name\" required>\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 </FormField>\n <FormField label={t(\"emailAddress\")} htmlFor=\"email\" required>\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 </FormField>\n </div>\n\n <FormField label={t(\"message\")} htmlFor=\"message\" required>\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 </FormField>\n <FormFileInput\n files={formData.attachments}\n onFilesChange={handleFileUploadChange}\n handleRemoveFile={handleRemoveFile}\n maxFiles={constants.file?.maxFiles || 5}\n accept={constants.file?.accept || \".pdf,.doc,.docx,.jpg,.jpeg,.png\"}\n disabled={isSubmitting}\n uploadButtonText={t(\"addFiles\")}\n maxFilesReachedText={t(\"maxFilesReached\")}\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=\"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\nexport default ContactPageMapOverlay;\n"
|
|
33
|
+
"content": "import React, { useState, useMemo, useEffect } from \"react\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { useTranslation } from \"react-i18next\";\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 { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\r\nimport {\r\n Mail,\r\n Phone,\r\n MapPin,\r\n Facebook,\r\n Twitter,\r\n Instagram,\r\n Linkedin,\r\n Send,\r\n ExternalLink,\r\n} from \"lucide-react\";\r\nimport constants from \"@/constants/constants.json\";\r\nimport { GoogleMap } from \"@/modules/google-map\";\r\nimport { FormFileInput } from \"@/components/FormFileInput\";\r\nimport { FormField } from \"@/components/FormField\";\r\n\r\nconst socialIcons: Record<string, React.ElementType> = {\r\n facebook: Facebook,\r\n twitter: Twitter,\r\n instagram: Instagram,\r\n linkedin: Linkedin,\r\n};\r\n\r\nexport function ContactPageMapOverlay() {\r\n const { t } = useTranslation(\"contact-page-map-overlay\");\r\n usePageTitle({ title: t(\"title\") });\r\n\r\n const apiService = useApiService();\r\n\r\n const socialLinks = useMemo(() => {\r\n const socialMedia = constants.socialMedia as\r\n | Record<string, string>\r\n | undefined;\r\n if (!socialMedia) return [];\r\n return Object.entries(socialMedia)\r\n .filter(([platform, url]) => url && socialIcons[platform])\r\n .map(([platform, url]) => ({\r\n platform,\r\n url,\r\n Icon: socialIcons[platform],\r\n }));\r\n }, []);\r\n\r\n const [formData, setFormData] = useState({\r\n name: \"\",\r\n email: \"\",\r\n message: \"\",\r\n attachments: [] as File[]\r\n });\r\n const [isSubmitting, setIsSubmitting] = useState(false);\r\n const [submitStatus, setSubmitStatus] = useState<\r\n \"idle\" | \"success\" | \"error\"\r\n >(\"idle\");\r\n const handleFileUploadChange = (e: React.ChangeEvent<HTMLInputElement>) => {\r\n const selectedFiles = Array.from(e.target.files || []) as File[];\r\n const maxFiles = constants.file?.maxFiles || 5;\r\n\r\n const remainingSlots = maxFiles - formData.attachments.length;\r\n\r\n // If the limit is exceeded, alert and do not add any files\r\n if (selectedFiles.length > remainingSlots) {\r\n alert(t(\"maxFilesLimit\", { max: maxFiles }));\r\n e.target.value = ''; // Clear the input\r\n return;\r\n }\r\n\r\n setFormData(prev => ({\r\n ...prev,\r\n attachments: [...prev.attachments, ...selectedFiles]\r\n }));\r\n\r\n e.target.value = ''; // Clear the input\r\n }\r\n\r\n const handleRemoveFile = (index: number) => {\r\n setFormData(prev => ({\r\n ...prev,\r\n attachments: prev.attachments.filter((_, i) => i !== index)\r\n }));\r\n };\r\n\r\n // Auto-reset status after 5 seconds with proper cleanup\r\n useEffect(() => {\r\n if (submitStatus === \"idle\") return;\r\n const timer = setTimeout(() => setSubmitStatus(\"idle\"), 5000);\r\n return () => clearTimeout(timer);\r\n }, [submitStatus]);\r\n\r\n const handleSubmit = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setIsSubmitting(true);\r\n setSubmitStatus(\"idle\");\r\n try {\r\n const currentLanguage = constants.site.defaultLanguage;\r\n\r\n await apiService.submitFormWithFile(\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 { name: \"attachments\", required: false },\r\n ],\r\n },\r\n currentLanguage\r\n );\r\n\r\n setSubmitStatus(\"success\");\r\n setFormData({\r\n name: \"\",\r\n email: \"\",\r\n message: \"\",\r\n attachments: []\r\n });\r\n } catch (error: unknown) {\r\n console.error(\"Form submission failed:\", error);\r\n setSubmitStatus(\"error\");\r\n } finally {\r\n setIsSubmitting(false);\r\n }\r\n };\r\n\r\n const handleChange = (\r\n e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>\r\n ) => {\r\n setFormData((prev) => ({\r\n ...prev,\r\n [e.target.name]: e.target.value,\r\n }));\r\n };\r\n\r\n // Default coordinates (can be customized via constants)\r\n const mapLatitude = (constants as any).location?.latitude || 41.0082;\r\n const mapLongitude = (constants as any).location?.longitude || 28.9784;\r\n\r\n return (\r\n <Layout>\r\n <div className=\"relative min-h-[calc(100vh-4rem)]\">\r\n {/* Full-screen Map Background */}\r\n <div className=\"absolute inset-0\">\r\n <GoogleMap\r\n latitude={mapLatitude}\r\n longitude={mapLongitude}\r\n zoom={14}\r\n height=\"100%\"\r\n className=\"rounded-none border-0 h-full\"\r\n title={t(\"mapTitle\")}\r\n />\r\n {/* Dark overlay for better readability */}\r\n <div className=\"absolute inset-0 bg-black/30 pointer-events-none\" />\r\n </div>\r\n\r\n {/* Content Overlay */}\r\n <div className=\"relative z-10 min-h-[calc(100vh-4rem)] flex items-center py-12 px-4\">\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto\">\r\n <div className=\"grid lg:grid-cols-2 gap-8 items-stretch\">\r\n {/* Form Card - Glassmorphism */}\r\n <Card className=\"backdrop-blur-xl bg-background/85 dark:bg-background/90 border-white/20 shadow-2xl\">\r\n <CardHeader>\r\n <CardTitle className=\"text-2xl lg:text-3xl\">\r\n {t(\"title\")}\r\n </CardTitle>\r\n <p className=\"text-muted-foreground mt-2\">\r\n {t(\"description\")}\r\n </p>\r\n </CardHeader>\r\n <CardContent>\r\n <form onSubmit={handleSubmit} className=\"space-y-5\">\r\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\r\n <FormField label={t(\"fullName\")} htmlFor=\"name\" required>\r\n <Input\r\n id=\"name\"\r\n name=\"name\"\r\n type=\"text\"\r\n value={formData.name}\r\n onChange={handleChange}\r\n placeholder={t(\"fullNamePlaceholder\")}\r\n required\r\n className=\"mt-1.5 bg-background/50\"\r\n />\r\n </FormField>\r\n <FormField label={t(\"emailAddress\")} htmlFor=\"email\" required>\r\n <Input\r\n id=\"email\"\r\n name=\"email\"\r\n type=\"email\"\r\n value={formData.email}\r\n onChange={handleChange}\r\n placeholder={t(\"emailPlaceholder\")}\r\n required\r\n className=\"mt-1.5 bg-background/50\"\r\n />\r\n </FormField>\r\n </div>\r\n\r\n <FormField label={t(\"message\")} htmlFor=\"message\" required>\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\")}\r\n required\r\n rows={4}\r\n className=\"mt-1.5 resize-none bg-background/50\"\r\n />\r\n </FormField>\r\n <FormFileInput\r\n files={formData.attachments}\r\n onFilesChange={handleFileUploadChange}\r\n handleRemoveFile={handleRemoveFile}\r\n maxFiles={constants.file?.maxFiles || 5}\r\n accept={constants.file?.accept || \".pdf,.doc,.docx,.jpg,.jpeg,.png\"}\r\n disabled={isSubmitting}\r\n uploadButtonText={t(\"addFiles\")}\r\n maxFilesReachedText={t(\"maxFilesReached\")}\r\n />\r\n {submitStatus === \"success\" && (\r\n <div className=\"p-3 bg-green-500/10 border border-green-500/30 rounded-lg\">\r\n <p className=\"text-green-600 dark:text-green-400 text-sm\">\r\n {t(\"success\")}\r\n </p>\r\n </div>\r\n )}\r\n\r\n {submitStatus === \"error\" && (\r\n <div className=\"p-3 bg-destructive/10 border border-destructive/30 rounded-lg\">\r\n <p className=\"text-destructive text-sm\">{t(\"error\")}</p>\r\n </div>\r\n )}\r\n\r\n <Button\r\n type=\"submit\"\r\n size=\"lg\"\r\n className=\"w-full\"\r\n disabled={isSubmitting}\r\n >\r\n {isSubmitting ? (\r\n <>\r\n <div className=\"w-4 h-4 border-2 border-primary-foreground border-t-transparent rounded-full animate-spin mr-2\" />\r\n {t(\"sending\")}\r\n </>\r\n ) : (\r\n <>\r\n <Send className=\"w-4 h-4 mr-2\" />\r\n {t(\"submit\")}\r\n </>\r\n )}\r\n </Button>\r\n </form>\r\n </CardContent>\r\n </Card>\r\n\r\n {/* Contact Info Card - Glassmorphism */}\r\n <Card className=\"backdrop-blur-xl bg-background/85 dark:bg-background/90 border-white/20 shadow-2xl\">\r\n <CardHeader>\r\n <CardTitle>{t(\"contactInfo\")}</CardTitle>\r\n </CardHeader>\r\n <CardContent className=\"space-y-5\">\r\n <div className=\"flex items-start gap-4\">\r\n <div className=\"w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0\">\r\n <Mail className=\"w-5 h-5 text-primary\" />\r\n </div>\r\n <div>\r\n <p className=\"text-sm text-muted-foreground\">{t(\"email\")}</p>\r\n <a\r\n href={`mailto:${constants.email}`}\r\n className=\"font-medium hover:text-primary transition-colors\"\r\n >\r\n {constants.email}\r\n </a>\r\n </div>\r\n </div>\r\n\r\n <div className=\"flex items-start gap-4\">\r\n <div className=\"w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0\">\r\n <Phone className=\"w-5 h-5 text-primary\" />\r\n </div>\r\n <div>\r\n <p className=\"text-sm text-muted-foreground\">{t(\"phone\")}</p>\r\n <a\r\n href={`tel:${constants.phone}`}\r\n className=\"font-medium hover:text-primary transition-colors\"\r\n >\r\n {constants.phone}\r\n </a>\r\n </div>\r\n </div>\r\n\r\n <div className=\"flex items-start gap-4\">\r\n <div className=\"w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0\">\r\n <MapPin className=\"w-5 h-5 text-primary\" />\r\n </div>\r\n <div>\r\n <p className=\"text-sm text-muted-foreground\">{t(\"address\")}</p>\r\n <p className=\"font-medium\">\r\n {constants.address.line1}\r\n <br />\r\n {constants.address.city}, {constants.address.state} {constants.address.postalCode}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n {/* Open in Maps Link */}\r\n <a\r\n href={`https://www.google.com/maps?q=${mapLatitude},${mapLongitude}`}\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n className=\"inline-flex items-center gap-2 text-sm text-primary hover:underline mt-2\"\r\n >\r\n <ExternalLink className=\"w-4 h-4\" />\r\n {t(\"openInMaps\")}\r\n </a>\r\n\r\n {/* Social Links */}\r\n {socialLinks.length > 0 && (\r\n <div className=\"pt-4 border-t\">\r\n <p className=\"text-sm text-muted-foreground mb-3\">\r\n {t(\"followUs\")}\r\n </p>\r\n <div className=\"flex gap-2\">\r\n {socialLinks.map(({ platform, url, Icon }) => (\r\n <a\r\n key={platform}\r\n href={url}\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\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\"\r\n >\r\n <Icon className=\"h-4 w-4\" />\r\n </a>\r\n ))}\r\n </div>\r\n </div>\r\n )}\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default ContactPageMapOverlay;\r\n"
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
"path": "contact-page-map-overlay/lang/en.json",
|
|
37
37
|
"type": "registry:lang",
|
|
38
38
|
"target": "$modules$/contact-page-map-overlay/lang/en.json",
|
|
39
|
-
"content": "{\n \"title\": \"Contact Us\",\n \"description\": \"We'd love to hear from you. Send us a message and we'll respond as soon as possible.\",\n \"contactInfo\": \"Contact Information\",\n \"email\": \"Email\",\n \"phone\": \"Phone\",\n \"address\": \"Address\",\n \"fullName\": \"Full Name\",\n \"fullNamePlaceholder\": \"Your name\",\n \"emailAddress\": \"Email Address\",\n \"emailPlaceholder\": \"your@email.com\",\n \"message\": \"Message\",\n \"messagePlaceholder\": \"How can we help you?\",\n \"submit\": \"Send Message\",\n \"sending\": \"Sending...\",\n \"success\": \"Message sent successfully! We'll get back to you soon.\",\n \"error\": \"Something went wrong. Please try again.\",\n \"followUs\": \"Follow us\",\n \"mapTitle\": \"Our Location\",\n \"openInMaps\": \"Open in Google Maps\",\n \"addFiles\": \"Add Files\",\n \"maxFilesReached\": \"Maximum files reached\",\n \"maxFilesLimit\": \"You can add up to {{max}} files\"\n}\n"
|
|
39
|
+
"content": "{\r\n \"title\": \"Contact Us\",\r\n \"description\": \"We'd love to hear from you. Send us a message and we'll respond as soon as possible.\",\r\n \"contactInfo\": \"Contact Information\",\r\n \"email\": \"Email\",\r\n \"phone\": \"Phone\",\r\n \"address\": \"Address\",\r\n \"fullName\": \"Full Name\",\r\n \"fullNamePlaceholder\": \"Your name\",\r\n \"emailAddress\": \"Email Address\",\r\n \"emailPlaceholder\": \"your@email.com\",\r\n \"message\": \"Message\",\r\n \"messagePlaceholder\": \"How can we help you?\",\r\n \"submit\": \"Send Message\",\r\n \"sending\": \"Sending...\",\r\n \"success\": \"Message sent successfully! We'll get back to you soon.\",\r\n \"error\": \"Something went wrong. Please try again.\",\r\n \"followUs\": \"Follow us\",\r\n \"mapTitle\": \"Our Location\",\r\n \"openInMaps\": \"Open in Google Maps\",\r\n \"addFiles\": \"Add Files\",\r\n \"maxFilesReached\": \"Maximum files reached\",\r\n \"maxFilesLimit\": \"You can add up to {{max}} files\"\r\n}\r\n"
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
42
|
"path": "contact-page-map-overlay/lang/tr.json",
|
|
43
43
|
"type": "registry:lang",
|
|
44
44
|
"target": "$modules$/contact-page-map-overlay/lang/tr.json",
|
|
45
|
-
"content": "{\n \"title\": \"Bize Ulaşın\",\n \"description\": \"Sizden haber almak isteriz. Bize mesaj gönderin, en kısa sürede yanıtlayacağız.\",\n \"contactInfo\": \"İletişim Bilgileri\",\n \"email\": \"E-posta\",\n \"phone\": \"Telefon\",\n \"address\": \"Adres\",\n \"fullName\": \"Ad Soyad\",\n \"fullNamePlaceholder\": \"Adınız\",\n \"emailAddress\": \"E-posta Adresi\",\n \"emailPlaceholder\": \"email@adresiniz.com\",\n \"message\": \"Mesaj\",\n \"messagePlaceholder\": \"Size nasıl yardımcı olabiliriz?\",\n \"submit\": \"Mesaj Gönder\",\n \"sending\": \"Gönderiliyor...\",\n \"success\": \"Mesaj başarıyla gönderildi! En kısa sürede yanıtlayacağız.\",\n \"error\": \"Bir hata oluştu. Lütfen tekrar deneyin.\",\n \"followUs\": \"Bizi takip edin\",\n \"mapTitle\": \"Konumumuz\",\n \"openInMaps\": \"Google Maps'te Aç\",\n \"addFiles\": \"Dosya Ekle\",\n \"maxFilesReached\": \"Maksimum dosya sayısına ulaşıldı\",\n \"maxFilesLimit\": \"En fazla {{max}} kadar dosya ekleyebilirsiniz\"\n}\n"
|
|
45
|
+
"content": "{\r\n \"title\": \"Bize Ulaşın\",\r\n \"description\": \"Sizden haber almak isteriz. Bize mesaj gönderin, en kısa sürede yanıtlayacağız.\",\r\n \"contactInfo\": \"İletişim Bilgileri\",\r\n \"email\": \"E-posta\",\r\n \"phone\": \"Telefon\",\r\n \"address\": \"Adres\",\r\n \"fullName\": \"Ad Soyad\",\r\n \"fullNamePlaceholder\": \"Adınız\",\r\n \"emailAddress\": \"E-posta Adresi\",\r\n \"emailPlaceholder\": \"email@adresiniz.com\",\r\n \"message\": \"Mesaj\",\r\n \"messagePlaceholder\": \"Size nasıl yardımcı olabiliriz?\",\r\n \"submit\": \"Mesaj Gönder\",\r\n \"sending\": \"Gönderiliyor...\",\r\n \"success\": \"Mesaj başarıyla gönderildi! En kısa sürede yanıtlayacağız.\",\r\n \"error\": \"Bir hata oluştu. Lütfen tekrar deneyin.\",\r\n \"followUs\": \"Bizi takip edin\",\r\n \"mapTitle\": \"Konumumuz\",\r\n \"openInMaps\": \"Google Maps'te Aç\",\r\n \"addFiles\": \"Dosya Ekle\",\r\n \"maxFilesReached\": \"Maksimum dosya sayısına ulaşıldı\",\r\n \"maxFilesLimit\": \"En fazla {{max}} kadar dosya ekleyebilirsiniz\"\r\n}\r\n"
|
|
46
46
|
}
|
|
47
47
|
],
|
|
48
48
|
"exports": {
|
|
@@ -30,19 +30,19 @@
|
|
|
30
30
|
"path": "contact-page-map-split/contact-page-map-split.tsx",
|
|
31
31
|
"type": "registry:component",
|
|
32
32
|
"target": "$modules$/contact-page-map-split/contact-page-map-split.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 { \n Mail,\n Phone,\n MapPin,\n Facebook,\n Twitter,\n Instagram,\n Linkedin,\n Send,\n} from \"lucide-react\";\nimport constants from \"@/constants/constants.json\";\nimport { GoogleMap } from \"@/modules/google-map\";\nimport { FormFileInput } from \"@/components/FormFileInput\";\nimport { FormField } from \"@/components/FormField\";\n\nconst socialIcons: Record<string, React.ElementType> = {\n facebook: Facebook,\n twitter: Twitter,\n instagram: Instagram,\n linkedin: Linkedin,\n};\n\nexport function ContactPageMapSplit() {\n const { t } = useTranslation(\"contact-page-map-split\");\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 message: \"\",\n attachments: [] as File[]\n });\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [submitStatus, setSubmitStatus] = useState<\n \"idle\" | \"success\" | \"error\"\n >(\"idle\");\n\n\n const handleFileUploadChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const selectedFiles = Array.from(e.target.files || []) as File[];\n const maxFiles = constants.file?.maxFiles || 5;\n\n const remainingSlots = maxFiles - formData.attachments.length;\n\n // If the limit is exceeded, alert and do not add any files\n if (selectedFiles.length > remainingSlots) {\n alert(t(\"maxFilesLimit\", { max: maxFiles }));\n e.target.value = ''; // Clear the input\n return;\n }\n\n setFormData(prev => ({\n ...prev,\n attachments: [...prev.attachments, ...selectedFiles]\n }));\n\n e.target.value = ''; // Clear the input\n }\n\n const handleRemoveFile = (index: number) => {\n setFormData(prev => ({\n ...prev,\n attachments: prev.attachments.filter((_, i) => i !== index)\n }));\n };\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.submitFormWithFile(\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: \"message\", required: true },\n { name: \"attachments\", required: false },\n ],\n },\n currentLanguage\n );\n\n setSubmitStatus(\"success\");\n setFormData({\n name: \"\",\n email: \"\",\n phone: \"\",\n message: \"\",\n attachments: []\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=\"min-h-screen flex flex-col lg:flex-row\">\n {/* Left Side - Form & Info */}\n <div className=\"w-full lg:w-1/2 bg-background py-12 lg:py-16 px-6 lg:px-12 flex flex-col justify-center\">\n <div className=\"max-w-lg mx-auto w-full\">\n {/* Header */}\n <div className=\"mb-10\">\n <h1 className=\"text-3xl lg:text-4xl font-bold text-foreground mb-3\">\n {t(\"title\")}\n </h1>\n <p className=\"text-muted-foreground\">\n {t(\"description\")}\n </p>\n </div>\n\n {/* Contact Info Cards */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4 mb-10\">\n <div className=\"flex items-center gap-3 p-4 rounded-xl bg-muted/50\">\n <div className=\"w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center\">\n <Mail className=\"w-5 h-5 text-primary\" />\n </div>\n <div className=\"min-w-0\">\n <p className=\"text-xs text-muted-foreground\">{t(\"email\")}</p>\n <p className=\"text-sm font-medium truncate\">{constants.email}</p>\n </div>\n </div>\n\n <div className=\"flex items-center gap-3 p-4 rounded-xl bg-muted/50\">\n <div className=\"w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center\">\n <Phone className=\"w-5 h-5 text-primary\" />\n </div>\n <div className=\"min-w-0\">\n <p className=\"text-xs text-muted-foreground\">{t(\"phone\")}</p>\n <p className=\"text-sm font-medium truncate\">{constants.phone}</p>\n </div>\n </div>\n\n <div className=\"flex items-center gap-3 p-4 rounded-xl bg-muted/50 sm:col-span-2\">\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 className=\"min-w-0\">\n <p className=\"text-xs text-muted-foreground\">{t(\"address\")}</p>\n <p className=\"text-sm font-medium\">\n {constants.address.line1}, {constants.address.city}\n </p>\n </div>\n </div>\n </div>\n\n {/* Contact Form */}\n <form onSubmit={handleSubmit} className=\"space-y-5\">\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <FormField label={t(\"fullName\")} htmlFor=\"name\" required>\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\"\n />\n </FormField>\n <FormField label={t(\"emailAddress\")} htmlFor=\"email\" required>\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\"\n />\n </FormField>\n </div>\n\n <FormField label={t(\"phoneNumber\")} htmlFor=\"phone\">\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.5\"\n />\n </FormField>\n <FormField label={t(\"message\")} htmlFor=\"message\" required>\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\"\n />\n </FormField>\n <FormFileInput\n files={formData.attachments}\n onFilesChange={handleFileUploadChange}\n handleRemoveFile={handleRemoveFile}\n maxFiles={constants.file?.maxFiles || 5}\n accept={constants.file?.accept || \".pdf,.doc,.docx,.jpg,.jpeg,.png\"}\n disabled={isSubmitting}\n uploadButtonText={t(\"addFiles\")}\n maxFilesReachedText={t(\"maxFilesReached\")}\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\n {/* Social Links */}\n {socialLinks.length > 0 && (\n <div className=\"mt-10 pt-6 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 </div>\n </div>\n\n {/* Right Side - Map */}\n <div className=\"w-full lg:w-1/2 h-[400px] lg:h-[calc(100vh-4rem)] relative\">\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 </div>\n </div>\n </Layout>\n );\n}\n\nexport default ContactPageMapSplit;\n"
|
|
33
|
+
"content": "import React, { useState, useMemo, useEffect } from \"react\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { useTranslation } from \"react-i18next\";\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 { \r\n Mail,\r\n Phone,\r\n MapPin,\r\n Facebook,\r\n Twitter,\r\n Instagram,\r\n Linkedin,\r\n Send,\r\n} from \"lucide-react\";\r\nimport constants from \"@/constants/constants.json\";\r\nimport { GoogleMap } from \"@/modules/google-map\";\r\nimport { FormFileInput } from \"@/components/FormFileInput\";\r\nimport { FormField } from \"@/components/FormField\";\r\n\r\nconst socialIcons: Record<string, React.ElementType> = {\r\n facebook: Facebook,\r\n twitter: Twitter,\r\n instagram: Instagram,\r\n linkedin: Linkedin,\r\n};\r\n\r\nexport function ContactPageMapSplit() {\r\n const { t } = useTranslation(\"contact-page-map-split\");\r\n usePageTitle({ title: t(\"title\") });\r\n\r\n const apiService = useApiService();\r\n\r\n const socialLinks = useMemo(() => {\r\n const socialMedia = constants.socialMedia as\r\n | Record<string, string>\r\n | undefined;\r\n if (!socialMedia) return [];\r\n return Object.entries(socialMedia)\r\n .filter(([platform, url]) => url && socialIcons[platform])\r\n .map(([platform, url]) => ({\r\n platform,\r\n url,\r\n Icon: socialIcons[platform],\r\n }));\r\n }, []);\r\n\r\n const [formData, setFormData] = useState({\r\n name: \"\",\r\n email: \"\",\r\n phone: \"\",\r\n message: \"\",\r\n attachments: [] as File[]\r\n });\r\n const [isSubmitting, setIsSubmitting] = useState(false);\r\n const [submitStatus, setSubmitStatus] = useState<\r\n \"idle\" | \"success\" | \"error\"\r\n >(\"idle\");\r\n\r\n\r\n const handleFileUploadChange = (e: React.ChangeEvent<HTMLInputElement>) => {\r\n const selectedFiles = Array.from(e.target.files || []) as File[];\r\n const maxFiles = constants.file?.maxFiles || 5;\r\n\r\n const remainingSlots = maxFiles - formData.attachments.length;\r\n\r\n // If the limit is exceeded, alert and do not add any files\r\n if (selectedFiles.length > remainingSlots) {\r\n alert(t(\"maxFilesLimit\", { max: maxFiles }));\r\n e.target.value = ''; // Clear the input\r\n return;\r\n }\r\n\r\n setFormData(prev => ({\r\n ...prev,\r\n attachments: [...prev.attachments, ...selectedFiles]\r\n }));\r\n\r\n e.target.value = ''; // Clear the input\r\n }\r\n\r\n const handleRemoveFile = (index: number) => {\r\n setFormData(prev => ({\r\n ...prev,\r\n attachments: prev.attachments.filter((_, i) => i !== index)\r\n }));\r\n };\r\n\r\n // Auto-reset status after 5 seconds with proper cleanup\r\n useEffect(() => {\r\n if (submitStatus === \"idle\") return;\r\n const timer = setTimeout(() => setSubmitStatus(\"idle\"), 5000);\r\n return () => clearTimeout(timer);\r\n }, [submitStatus]);\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 const currentLanguage = constants.site.defaultLanguage;\r\n\r\n await apiService.submitFormWithFile(\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: \"phone\", required: false },\r\n { name: \"message\", required: true },\r\n { name: \"attachments\", required: false },\r\n ],\r\n },\r\n currentLanguage\r\n );\r\n\r\n setSubmitStatus(\"success\");\r\n setFormData({\r\n name: \"\",\r\n email: \"\",\r\n phone: \"\",\r\n message: \"\",\r\n attachments: []\r\n });\r\n } catch (error: unknown) {\r\n console.error(\"Form submission failed:\", error);\r\n setSubmitStatus(\"error\");\r\n } finally {\r\n setIsSubmitting(false);\r\n }\r\n };\r\n\r\n const handleChange = (\r\n e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>\r\n ) => {\r\n setFormData((prev) => ({\r\n ...prev,\r\n [e.target.name]: e.target.value,\r\n }));\r\n };\r\n\r\n // Default coordinates (can be customized via constants)\r\n const mapLatitude = (constants as any).location?.latitude || 41.0082;\r\n const mapLongitude = (constants as any).location?.longitude || 28.9784;\r\n\r\n return (\r\n <Layout>\r\n <div className=\"min-h-screen flex flex-col lg:flex-row\">\r\n {/* Left Side - Form & Info */}\r\n <div className=\"w-full lg:w-1/2 bg-background py-12 lg:py-16 px-6 lg:px-12 flex flex-col justify-center\">\r\n <div className=\"max-w-lg mx-auto w-full\">\r\n {/* Header */}\r\n <div className=\"mb-10\">\r\n <h1 className=\"text-3xl lg:text-4xl font-bold text-foreground mb-3\">\r\n {t(\"title\")}\r\n </h1>\r\n <p className=\"text-muted-foreground\">\r\n {t(\"description\")}\r\n </p>\r\n </div>\r\n\r\n {/* Contact Info Cards */}\r\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4 mb-10\">\r\n <div className=\"flex items-center gap-3 p-4 rounded-xl bg-muted/50\">\r\n <div className=\"w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center\">\r\n <Mail className=\"w-5 h-5 text-primary\" />\r\n </div>\r\n <div className=\"min-w-0\">\r\n <p className=\"text-xs text-muted-foreground\">{t(\"email\")}</p>\r\n <p className=\"text-sm font-medium truncate\">{constants.email}</p>\r\n </div>\r\n </div>\r\n\r\n <div className=\"flex items-center gap-3 p-4 rounded-xl bg-muted/50\">\r\n <div className=\"w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center\">\r\n <Phone className=\"w-5 h-5 text-primary\" />\r\n </div>\r\n <div className=\"min-w-0\">\r\n <p className=\"text-xs text-muted-foreground\">{t(\"phone\")}</p>\r\n <p className=\"text-sm font-medium truncate\">{constants.phone}</p>\r\n </div>\r\n </div>\r\n\r\n <div className=\"flex items-center gap-3 p-4 rounded-xl bg-muted/50 sm:col-span-2\">\r\n <div className=\"w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0\">\r\n <MapPin className=\"w-5 h-5 text-primary\" />\r\n </div>\r\n <div className=\"min-w-0\">\r\n <p className=\"text-xs text-muted-foreground\">{t(\"address\")}</p>\r\n <p className=\"text-sm font-medium\">\r\n {constants.address.line1}, {constants.address.city}\r\n </p>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n {/* Contact Form */}\r\n <form onSubmit={handleSubmit} className=\"space-y-5\">\r\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\r\n <FormField label={t(\"fullName\")} htmlFor=\"name\" required>\r\n <Input\r\n id=\"name\"\r\n name=\"name\"\r\n type=\"text\"\r\n value={formData.name}\r\n onChange={handleChange}\r\n placeholder={t(\"fullNamePlaceholder\")}\r\n required\r\n className=\"mt-1.5\"\r\n />\r\n </FormField>\r\n <FormField label={t(\"emailAddress\")} htmlFor=\"email\" required>\r\n <Input\r\n id=\"email\"\r\n name=\"email\"\r\n type=\"email\"\r\n value={formData.email}\r\n onChange={handleChange}\r\n placeholder={t(\"emailPlaceholder\")}\r\n required\r\n className=\"mt-1.5\"\r\n />\r\n </FormField>\r\n </div>\r\n\r\n <FormField label={t(\"phoneNumber\")} htmlFor=\"phone\">\r\n <Input\r\n id=\"phone\"\r\n name=\"phone\"\r\n type=\"tel\"\r\n value={formData.phone}\r\n onChange={handleChange}\r\n placeholder={t(\"phonePlaceholder\")}\r\n className=\"mt-1.5\"\r\n />\r\n </FormField>\r\n <FormField label={t(\"message\")} htmlFor=\"message\" required>\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\")}\r\n required\r\n rows={4}\r\n className=\"mt-1.5 resize-none\"\r\n />\r\n </FormField>\r\n <FormFileInput\r\n files={formData.attachments}\r\n onFilesChange={handleFileUploadChange}\r\n handleRemoveFile={handleRemoveFile}\r\n maxFiles={constants.file?.maxFiles || 5}\r\n accept={constants.file?.accept || \".pdf,.doc,.docx,.jpg,.jpeg,.png\"}\r\n disabled={isSubmitting}\r\n uploadButtonText={t(\"addFiles\")}\r\n maxFilesReachedText={t(\"maxFilesReached\")}\r\n />\r\n {submitStatus === \"success\" && (\r\n <div className=\"p-3 bg-green-500/10 border border-green-500/30 rounded-lg\">\r\n <p className=\"text-green-600 dark:text-green-400 text-sm\">\r\n {t(\"success\")}\r\n </p>\r\n </div>\r\n )}\r\n\r\n {submitStatus === \"error\" && (\r\n <div className=\"p-3 bg-destructive/10 border border-destructive/30 rounded-lg\">\r\n <p className=\"text-destructive text-sm\">{t(\"error\")}</p>\r\n </div>\r\n )}\r\n\r\n <Button\r\n type=\"submit\"\r\n size=\"lg\"\r\n className=\"w-full\"\r\n disabled={isSubmitting}\r\n >\r\n {isSubmitting ? (\r\n <>\r\n <div className=\"w-4 h-4 border-2 border-primary-foreground border-t-transparent rounded-full animate-spin mr-2\" />\r\n {t(\"sending\")}\r\n </>\r\n ) : (\r\n <>\r\n <Send className=\"w-4 h-4 mr-2\" />\r\n {t(\"submit\")}\r\n </>\r\n )}\r\n </Button>\r\n </form>\r\n\r\n {/* Social Links */}\r\n {socialLinks.length > 0 && (\r\n <div className=\"mt-10 pt-6 border-t\">\r\n <p className=\"text-sm text-muted-foreground mb-3\">\r\n {t(\"followUs\")}\r\n </p>\r\n <div className=\"flex gap-2\">\r\n {socialLinks.map(({ platform, url, Icon }) => (\r\n <a\r\n key={platform}\r\n href={url}\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\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\"\r\n >\r\n <Icon className=\"h-4 w-4\" />\r\n </a>\r\n ))}\r\n </div>\r\n </div>\r\n )}\r\n </div>\r\n </div>\r\n\r\n {/* Right Side - Map */}\r\n <div className=\"w-full lg:w-1/2 h-[400px] lg:h-[calc(100vh-4rem)] relative\">\r\n <GoogleMap\r\n latitude={mapLatitude}\r\n longitude={mapLongitude}\r\n zoom={14}\r\n height=\"100%\"\r\n className=\"rounded-none border-0 h-full\"\r\n title={t(\"mapTitle\")}\r\n />\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default ContactPageMapSplit;\r\n"
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
"path": "contact-page-map-split/lang/en.json",
|
|
37
37
|
"type": "registry:lang",
|
|
38
38
|
"target": "$modules$/contact-page-map-split/lang/en.json",
|
|
39
|
-
"content": "{\n \"title\": \"Get in Touch\",\n \"description\": \"Have a question or want to work together? Fill out the form below and we'll get back to you as soon as possible.\",\n \"email\": \"Email\",\n \"phone\": \"Phone\",\n \"address\": \"Address\",\n \"fullName\": \"Full Name\",\n \"fullNamePlaceholder\": \"Your name\",\n \"emailAddress\": \"Email Address\",\n \"emailPlaceholder\": \"your@email.com\",\n \"phoneNumber\": \"Phone Number\",\n \"phonePlaceholder\": \"+1 (555) 000-0000\",\n \"message\": \"Message\",\n \"messagePlaceholder\": \"How can we help you?\",\n \"submit\": \"Send Message\",\n \"sending\": \"Sending...\",\n \"success\": \"Your message has been sent successfully! We'll get back to you soon.\",\n \"error\": \"Something went wrong. Please try again later.\",\n \"followUs\": \"Follow us on social media\",\n \"mapTitle\": \"Our Location\",\n \"addFiles\": \"Add Files\",\n \"maxFilesReached\": \"Maximum files reached\",\n \"maxFilesLimit\": \"You can add up to {{max}} files\"\n}\n"
|
|
39
|
+
"content": "{\r\n \"title\": \"Get in Touch\",\r\n \"description\": \"Have a question or want to work together? Fill out the form below and we'll get back to you as soon as possible.\",\r\n \"email\": \"Email\",\r\n \"phone\": \"Phone\",\r\n \"address\": \"Address\",\r\n \"fullName\": \"Full Name\",\r\n \"fullNamePlaceholder\": \"Your name\",\r\n \"emailAddress\": \"Email Address\",\r\n \"emailPlaceholder\": \"your@email.com\",\r\n \"phoneNumber\": \"Phone Number\",\r\n \"phonePlaceholder\": \"+1 (555) 000-0000\",\r\n \"message\": \"Message\",\r\n \"messagePlaceholder\": \"How can we help you?\",\r\n \"submit\": \"Send Message\",\r\n \"sending\": \"Sending...\",\r\n \"success\": \"Your message has been sent successfully! We'll get back to you soon.\",\r\n \"error\": \"Something went wrong. Please try again later.\",\r\n \"followUs\": \"Follow us on social media\",\r\n \"mapTitle\": \"Our Location\",\r\n \"addFiles\": \"Add Files\",\r\n \"maxFilesReached\": \"Maximum files reached\",\r\n \"maxFilesLimit\": \"You can add up to {{max}} files\"\r\n}\r\n"
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
42
|
"path": "contact-page-map-split/lang/tr.json",
|
|
43
43
|
"type": "registry:lang",
|
|
44
44
|
"target": "$modules$/contact-page-map-split/lang/tr.json",
|
|
45
|
-
"content": "{\n \"title\": \"Bize Ulaşın\",\n \"description\": \"Sorunuz mu var veya birlikte çalışmak mı istiyorsunuz? Aşağıdaki formu doldurun, en kısa sürede size döneceğiz.\",\n \"email\": \"E-posta\",\n \"phone\": \"Telefon\",\n \"address\": \"Adres\",\n \"fullName\": \"Ad Soyad\",\n \"fullNamePlaceholder\": \"Adınız\",\n \"emailAddress\": \"E-posta Adresi\",\n \"emailPlaceholder\": \"email@adresiniz.com\",\n \"phoneNumber\": \"Telefon Numarası\",\n \"phonePlaceholder\": \"+90 (555) 000 00 00\",\n \"message\": \"Mesaj\",\n \"messagePlaceholder\": \"Size nasıl yardımcı olabiliriz?\",\n \"submit\": \"Mesaj Gönder\",\n \"sending\": \"Gönderiliyor...\",\n \"success\": \"Mesajınız başarıyla gönderildi! En kısa sürede size döneceğiz.\",\n \"error\": \"Bir hata oluştu. Lütfen daha sonra tekrar deneyin.\",\n \"followUs\": \"Sosyal medyada bizi takip edin\",\n \"mapTitle\": \"Konumumuz\",\n \"addFiles\": \"Dosya Ekle\",\n \"maxFilesReached\": \"Maksimum dosya sayısına ulaşıldı\",\n \"maxFilesLimit\": \"En fazla {{max}} kadar dosya ekleyebilirsiniz\"\n}\n"
|
|
45
|
+
"content": "{\r\n \"title\": \"Bize Ulaşın\",\r\n \"description\": \"Sorunuz mu var veya birlikte çalışmak mı istiyorsunuz? Aşağıdaki formu doldurun, en kısa sürede size döneceğiz.\",\r\n \"email\": \"E-posta\",\r\n \"phone\": \"Telefon\",\r\n \"address\": \"Adres\",\r\n \"fullName\": \"Ad Soyad\",\r\n \"fullNamePlaceholder\": \"Adınız\",\r\n \"emailAddress\": \"E-posta Adresi\",\r\n \"emailPlaceholder\": \"email@adresiniz.com\",\r\n \"phoneNumber\": \"Telefon Numarası\",\r\n \"phonePlaceholder\": \"+90 (555) 000 00 00\",\r\n \"message\": \"Mesaj\",\r\n \"messagePlaceholder\": \"Size nasıl yardımcı olabiliriz?\",\r\n \"submit\": \"Mesaj Gönder\",\r\n \"sending\": \"Gönderiliyor...\",\r\n \"success\": \"Mesajınız başarıyla gönderildi! En kısa sürede size döneceğiz.\",\r\n \"error\": \"Bir hata oluştu. Lütfen daha sonra tekrar deneyin.\",\r\n \"followUs\": \"Sosyal medyada bizi takip edin\",\r\n \"mapTitle\": \"Konumumuz\",\r\n \"addFiles\": \"Dosya Ekle\",\r\n \"maxFilesReached\": \"Maksimum dosya sayısına ulaşıldı\",\r\n \"maxFilesLimit\": \"En fazla {{max}} kadar dosya ekleyebilirsiniz\"\r\n}\r\n"
|
|
46
46
|
}
|
|
47
47
|
],
|
|
48
48
|
"exports": {
|
|
@@ -18,25 +18,25 @@
|
|
|
18
18
|
"path": "contact-page-split/index.ts",
|
|
19
19
|
"type": "registry:index",
|
|
20
20
|
"target": "$modules$/contact-page-split/index.ts",
|
|
21
|
-
"content": "export * from './contact-page-split';\nexport { ContactPageSplit as default } from './contact-page-split';\n"
|
|
21
|
+
"content": "export * from './contact-page-split';\r\nexport { ContactPageSplit as default } from './contact-page-split';\r\n"
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
"path": "contact-page-split/contact-page-split.tsx",
|
|
25
25
|
"type": "registry:component",
|
|
26
26
|
"target": "$modules$/contact-page-split/contact-page-split.tsx",
|
|
27
|
-
"content": "import React, { useState } from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { Mail, Phone, MapPin, Clock } from \"lucide-react\";\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 { cn } from \"@/lib/utils\";\nimport constants from \"@/constants/constants.json\";\nimport { FormFileInput } from \"@/components/FormFileInput\";\nimport { FormField } from \"@/components/FormField\";\n\ninterface ContactPageSplitProps {\n className?: string;\n}\n\nexport function ContactPageSplit({ className }: ContactPageSplitProps) {\n const { t } = useTranslation(\"contact-page-split\");\n usePageTitle({ title: t(\"pageTitle\", \"Contact Us\") });\n const apiService = useApiService();\n\n const [formData, setFormData] = useState({\n name: \"\",\n email: \"\",\n phone: \"\",\n message: \"\",\n attachments: [] as File[]\n });\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [submitStatus, setSubmitStatus] = useState<\"idle\" | \"success\" | \"error\">(\"idle\");\n\n\n const handleFileUploadChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const selectedFiles = Array.from(e.target.files || []) as File[];\n const fileMaxFiles = constants.file?.maxFiles || 5;\n\n const remainingSlots = fileMaxFiles - formData.attachments.length;\n\n // If the limit is exceeded, alert and do not add any files\n if (selectedFiles.length > remainingSlots) {\n alert(t(\"maxFilesLimit\", { max: fileMaxFiles }));\n e.target.value = ''; // Clear the input\n return;\n }\n\n setFormData(prev => ({\n ...prev,\n attachments: [...prev.attachments, ...selectedFiles]\n }));\n\n e.target.value = ''; // Clear the input\n }\n\n const handleRemoveFile = (index: number) => {\n setFormData(prev => ({\n ...prev,\n attachments: prev.attachments.filter((_, i) => i !== index)\n }));\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setIsSubmitting(true);\n setSubmitStatus(\"idle\");\n\n try {\n await apiService.submitFormWithFile(\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: \"message\", required: true },\n { name: \"attachments\", required: false },\n ],\n },\n constants.site.defaultLanguage\n );\n\n setSubmitStatus(\"success\");\n setFormData({ name: \"\", email: \"\", phone: \"\", message: \"\", attachments: [] });\n setTimeout(() => setSubmitStatus(\"idle\"), 5000);\n } catch {\n setSubmitStatus(\"error\");\n setTimeout(() => setSubmitStatus(\"idle\"), 5000);\n } finally {\n setIsSubmitting(false);\n }\n };\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {\n setFormData((prev) => ({ ...prev, [e.target.name]: e.target.value }));\n };\n\n return (\n <Layout>\n <div className={cn(\"min-h-[calc(100vh-200px)] py-8 md:py-12\", className)}>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\n <div className=\"grid lg:grid-cols-2 h-full rounded-xl overflow-hidden shadow-lg\">\n {/* Left Side - Info & Image */}\n <div className=\"relative bg-primary text-primary-foreground p-8 lg:p-12 flex flex-col justify-center\">\n {/* Background Pattern */}\n <div className=\"absolute inset-0 bg-[linear-gradient(to_right,rgba(255,255,255,0.1)_1px,transparent_1px),linear-gradient(to_bottom,rgba(255,255,255,0.1)_1px,transparent_1px)] bg-[size:4rem_4rem]\" />\n\n <div className=\"relative z-10 max-w-lg\">\n <h1 className=\"text-3xl lg:text-4xl font-bold mb-4\">\n {t(\"title\", \"Let's Start a Conversation\")}\n </h1>\n <p className=\"text-primary-foreground/80 mb-8\">\n {t(\"subtitle\", \"Have a project in mind? We'd love to hear about it. Get in touch and let's create something amazing together.\")}\n </p>\n\n <div className=\"space-y-6\">\n <div className=\"flex items-start gap-4\">\n <div className=\"w-10 h-10 rounded-full bg-primary-foreground/10 flex items-center justify-center flex-shrink-0\">\n <Mail className=\"h-5 w-5\" />\n </div>\n <div>\n <p className=\"font-semibold\">{t(\"emailLabel\", \"Email\")}</p>\n <p className=\"text-primary-foreground/70\">{constants.email || \"hello@example.com\"}</p>\n </div>\n </div>\n\n <div className=\"flex items-start gap-4\">\n <div className=\"w-10 h-10 rounded-full bg-primary-foreground/10 flex items-center justify-center flex-shrink-0\">\n <Phone className=\"h-5 w-5\" />\n </div>\n <div>\n <p className=\"font-semibold\">{t(\"phoneLabel\", \"Phone\")}</p>\n <p className=\"text-primary-foreground/70\">{constants.phone || \"+1 234 567 890\"}</p>\n </div>\n </div>\n\n <div className=\"flex items-start gap-4\">\n <div className=\"w-10 h-10 rounded-full bg-primary-foreground/10 flex items-center justify-center flex-shrink-0\">\n <MapPin className=\"h-5 w-5\" />\n </div>\n <div>\n <p className=\"font-semibold\">{t(\"addressLabel\", \"Address\")}</p>\n <p className=\"text-primary-foreground/70\">\n {constants.address?.line1 || \"123 Main Street\"}<br />\n {constants.address?.city || \"New York\"}, {constants.address?.country || \"USA\"}\n </p>\n </div>\n </div>\n\n <div className=\"flex items-start gap-4\">\n <div className=\"w-10 h-10 rounded-full bg-primary-foreground/10 flex items-center justify-center flex-shrink-0\">\n <Clock className=\"h-5 w-5\" />\n </div>\n <div>\n <p className=\"font-semibold\">{t(\"hoursLabel\", \"Business Hours\")}</p>\n <p className=\"text-primary-foreground/70\">{t(\"hours\", \"Mon - Fri: 9:00 AM - 6:00 PM\")}</p>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n {/* Right Side - Form */}\n <div className=\"p-8 lg:p-12 flex items-center justify-center bg-background\">\n <div className=\"w-full max-w-md\">\n <h2 className=\"text-2xl font-bold mb-2\">{t(\"formTitle\", \"Send us a message\")}</h2>\n <p className=\"text-muted-foreground mb-8\">\n {t(\"formSubtitle\", \"Fill out the form below and we'll get back to you as soon as possible.\")}\n </p>\n\n <form onSubmit={handleSubmit} className=\"space-y-5\">\n <FormField label={t(\"nameLabel\", \"Full Name\")} htmlFor=\"name\" required>\n <Input\n id=\"name\"\n name=\"name\"\n value={formData.name}\n onChange={handleChange}\n placeholder={t(\"namePlaceholder\", \"John Doe\")}\n required\n className=\"mt-1\"\n />\n </FormField>\n\n <FormField label={t(\"emailInputLabel\", \"Email\")} htmlFor=\"email\" required>\n <Input\n id=\"email\"\n name=\"email\"\n type=\"email\"\n value={formData.email}\n onChange={handleChange}\n placeholder={t(\"emailPlaceholder\", \"john@example.com\")}\n required\n className=\"mt-1\"\n />\n </FormField>\n\n <FormField label={t(\"phoneInputLabel\", \"Phone\")} htmlFor=\"phone\">\n <Input\n id=\"phone\"\n name=\"phone\"\n type=\"tel\"\n value={formData.phone}\n onChange={handleChange}\n placeholder={t(\"phonePlaceholder\", \"+1 234 567 890\")}\n className=\"mt-1\"\n />\n </FormField>\n <FormField label={t(\"messageLabel\", \"Message\")} htmlFor=\"message\" required>\n <Textarea\n id=\"message\"\n name=\"message\"\n value={formData.message}\n onChange={handleChange}\n placeholder={t(\"messagePlaceholder\", \"Tell us about your project...\")}\n required\n rows={5}\n className=\"mt-1 resize-none\"\n />\n </FormField>\n <FormFileInput\n files={formData.attachments}\n onFilesChange={handleFileUploadChange}\n handleRemoveFile={handleRemoveFile}\n maxFiles={constants.file?.maxFiles || 5}\n accept={constants.file?.accept || \".pdf,.doc,.docx,.jpg,.jpeg,.png\"}\n disabled={isSubmitting}\n uploadButtonText={t(\"addFiles\")}\n maxFilesReachedText={t(\"maxFilesReached\")}\n />\n {submitStatus === \"success\" && (\n <div className=\"p-4 bg-green-500/10 border border-green-500/30 rounded-lg\">\n <p className=\"text-green-600 dark:text-green-400 text-sm font-medium\">\n {t(\"success\", \"Message sent! We'll be in touch soon.\")}\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\", \"Something went wrong. Please try again.\")}\n </p>\n </div>\n )}\n\n <Button type=\"submit\" size=\"lg\" className=\"w-full\" disabled={isSubmitting}>\n {isSubmitting ? t(\"sending\", \"Sending...\") : t(\"submit\", \"Send Message\")}\n </Button>\n </form>\n </div>\n </div>\n </div>\n </div>\n </div>\n </Layout>\n );\n}\n\nexport default ContactPageSplit;\n"
|
|
27
|
+
"content": "import React, { useState } from \"react\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { Mail, Phone, MapPin, Clock } 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 { cn } from \"@/lib/utils\";\r\nimport constants from \"@/constants/constants.json\";\r\nimport { FormFileInput } from \"@/components/FormFileInput\";\r\nimport { FormField } from \"@/components/FormField\";\r\n\r\ninterface ContactPageSplitProps {\r\n className?: string;\r\n}\r\n\r\nexport function ContactPageSplit({ className }: ContactPageSplitProps) {\r\n const { t } = useTranslation(\"contact-page-split\");\r\n usePageTitle({ title: t(\"pageTitle\", \"Contact Us\") });\r\n const apiService = useApiService();\r\n\r\n const [formData, setFormData] = useState({\r\n name: \"\",\r\n email: \"\",\r\n phone: \"\",\r\n message: \"\",\r\n attachments: [] as File[]\r\n });\r\n const [isSubmitting, setIsSubmitting] = useState(false);\r\n const [submitStatus, setSubmitStatus] = useState<\"idle\" | \"success\" | \"error\">(\"idle\");\r\n\r\n\r\n const handleFileUploadChange = (e: React.ChangeEvent<HTMLInputElement>) => {\r\n const selectedFiles = Array.from(e.target.files || []) as File[];\r\n const fileMaxFiles = constants.file?.maxFiles || 5;\r\n\r\n const remainingSlots = fileMaxFiles - formData.attachments.length;\r\n\r\n // If the limit is exceeded, alert and do not add any files\r\n if (selectedFiles.length > remainingSlots) {\r\n alert(t(\"maxFilesLimit\", { max: fileMaxFiles }));\r\n e.target.value = ''; // Clear the input\r\n return;\r\n }\r\n\r\n setFormData(prev => ({\r\n ...prev,\r\n attachments: [...prev.attachments, ...selectedFiles]\r\n }));\r\n\r\n e.target.value = ''; // Clear the input\r\n }\r\n\r\n const handleRemoveFile = (index: number) => {\r\n setFormData(prev => ({\r\n ...prev,\r\n attachments: prev.attachments.filter((_, i) => i !== index)\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.submitFormWithFile(\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: \"phone\", required: false },\r\n { name: \"message\", required: true },\r\n { name: \"attachments\", required: false },\r\n ],\r\n },\r\n constants.site.defaultLanguage\r\n );\r\n\r\n setSubmitStatus(\"success\");\r\n setFormData({ name: \"\", email: \"\", phone: \"\", message: \"\", attachments: [] });\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-[calc(100vh-200px)] py-8 md:py-12\", className)}>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n <div className=\"grid lg:grid-cols-2 h-full rounded-xl overflow-hidden shadow-lg\">\r\n {/* Left Side - Info & Image */}\r\n <div className=\"relative bg-primary text-primary-foreground p-8 lg:p-12 flex flex-col justify-center\">\r\n {/* Background Pattern */}\r\n <div className=\"absolute inset-0 bg-[linear-gradient(to_right,rgba(255,255,255,0.1)_1px,transparent_1px),linear-gradient(to_bottom,rgba(255,255,255,0.1)_1px,transparent_1px)] bg-[size:4rem_4rem]\" />\r\n\r\n <div className=\"relative z-10 max-w-lg\">\r\n <h1 className=\"text-3xl lg:text-4xl font-bold mb-4\">\r\n {t(\"title\", \"Let's Start a Conversation\")}\r\n </h1>\r\n <p className=\"text-primary-foreground/80 mb-8\">\r\n {t(\"subtitle\", \"Have a project in mind? We'd love to hear about it. Get in touch and let's create something amazing together.\")}\r\n </p>\r\n\r\n <div className=\"space-y-6\">\r\n <div className=\"flex items-start gap-4\">\r\n <div className=\"w-10 h-10 rounded-full bg-primary-foreground/10 flex items-center justify-center flex-shrink-0\">\r\n <Mail className=\"h-5 w-5\" />\r\n </div>\r\n <div>\r\n <p className=\"font-semibold\">{t(\"emailLabel\", \"Email\")}</p>\r\n <p className=\"text-primary-foreground/70\">{constants.email || \"hello@example.com\"}</p>\r\n </div>\r\n </div>\r\n\r\n <div className=\"flex items-start gap-4\">\r\n <div className=\"w-10 h-10 rounded-full bg-primary-foreground/10 flex items-center justify-center flex-shrink-0\">\r\n <Phone className=\"h-5 w-5\" />\r\n </div>\r\n <div>\r\n <p className=\"font-semibold\">{t(\"phoneLabel\", \"Phone\")}</p>\r\n <p className=\"text-primary-foreground/70\">{constants.phone || \"+1 234 567 890\"}</p>\r\n </div>\r\n </div>\r\n\r\n <div className=\"flex items-start gap-4\">\r\n <div className=\"w-10 h-10 rounded-full bg-primary-foreground/10 flex items-center justify-center flex-shrink-0\">\r\n <MapPin className=\"h-5 w-5\" />\r\n </div>\r\n <div>\r\n <p className=\"font-semibold\">{t(\"addressLabel\", \"Address\")}</p>\r\n <p className=\"text-primary-foreground/70\">\r\n {constants.address?.line1 || \"123 Main Street\"}<br />\r\n {constants.address?.city || \"New York\"}, {constants.address?.country || \"USA\"}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <div className=\"flex items-start gap-4\">\r\n <div className=\"w-10 h-10 rounded-full bg-primary-foreground/10 flex items-center justify-center flex-shrink-0\">\r\n <Clock className=\"h-5 w-5\" />\r\n </div>\r\n <div>\r\n <p className=\"font-semibold\">{t(\"hoursLabel\", \"Business Hours\")}</p>\r\n <p className=\"text-primary-foreground/70\">{t(\"hours\", \"Mon - Fri: 9:00 AM - 6:00 PM\")}</p>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n {/* Right Side - Form */}\r\n <div className=\"p-8 lg:p-12 flex items-center justify-center bg-background\">\r\n <div className=\"w-full max-w-md\">\r\n <h2 className=\"text-2xl font-bold mb-2\">{t(\"formTitle\", \"Send us a message\")}</h2>\r\n <p className=\"text-muted-foreground mb-8\">\r\n {t(\"formSubtitle\", \"Fill out the form below and we'll get back to you as soon as possible.\")}\r\n </p>\r\n\r\n <form onSubmit={handleSubmit} className=\"space-y-5\">\r\n <FormField label={t(\"nameLabel\", \"Full Name\")} htmlFor=\"name\" required>\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\", \"John Doe\")}\r\n required\r\n className=\"mt-1\"\r\n />\r\n </FormField>\r\n\r\n <FormField label={t(\"emailInputLabel\", \"Email\")} htmlFor=\"email\" required>\r\n <Input\r\n id=\"email\"\r\n name=\"email\"\r\n type=\"email\"\r\n value={formData.email}\r\n onChange={handleChange}\r\n placeholder={t(\"emailPlaceholder\", \"john@example.com\")}\r\n required\r\n className=\"mt-1\"\r\n />\r\n </FormField>\r\n\r\n <FormField label={t(\"phoneInputLabel\", \"Phone\")} htmlFor=\"phone\">\r\n <Input\r\n id=\"phone\"\r\n name=\"phone\"\r\n type=\"tel\"\r\n value={formData.phone}\r\n onChange={handleChange}\r\n placeholder={t(\"phonePlaceholder\", \"+1 234 567 890\")}\r\n className=\"mt-1\"\r\n />\r\n </FormField>\r\n <FormField label={t(\"messageLabel\", \"Message\")} htmlFor=\"message\" required>\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\", \"Tell us about your project...\")}\r\n required\r\n rows={5}\r\n className=\"mt-1 resize-none\"\r\n />\r\n </FormField>\r\n <FormFileInput\r\n files={formData.attachments}\r\n onFilesChange={handleFileUploadChange}\r\n handleRemoveFile={handleRemoveFile}\r\n maxFiles={constants.file?.maxFiles || 5}\r\n accept={constants.file?.accept || \".pdf,.doc,.docx,.jpg,.jpeg,.png\"}\r\n disabled={isSubmitting}\r\n uploadButtonText={t(\"addFiles\")}\r\n maxFilesReachedText={t(\"maxFilesReached\")}\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! We'll be in touch 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 </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default ContactPageSplit;\r\n"
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
"path": "contact-page-split/lang/en.json",
|
|
31
31
|
"type": "registry:lang",
|
|
32
32
|
"target": "$modules$/contact-page-split/lang/en.json",
|
|
33
|
-
"content": "{\n \"pageTitle\": \"Contact Us\",\n \"title\": \"Let's Start a Conversation\",\n \"subtitle\": \"This welcoming message sets the tone for visitor communication. Explain why they should contact you, what kind of help you can provide, or your typical response time. Use Promake to create an inviting message that encourages engagement.\",\n \"emailLabel\": \"Email\",\n \"phoneLabel\": \"Phone\",\n \"addressLabel\": \"Address\",\n \"hoursLabel\": \"Hours\",\n \"hours\": \"Mon - Fri: 9:00 AM - 6:00 PM\",\n \"formTitle\": \"Send us a message\",\n \"formSubtitle\": \"Fill out the form below and we'll get back to you as soon as possible.\",\n \"nameLabel\": \"Full Name\",\n \"namePlaceholder\": \"John Doe\",\n \"emailInputLabel\": \"Email\",\n \"emailPlaceholder\": \"john@example.com\",\n \"phoneInputLabel\": \"Phone\",\n \"phonePlaceholder\": \"+1 234 567 890\",\n \"messageLabel\": \"Message\",\n \"messagePlaceholder\": \"Tell us about your project...\",\n \"submit\": \"Send Message\",\n \"sending\": \"Sending...\",\n \"success\": \"Message sent! We'll be in touch soon.\",\n \"error\": \"Something went wrong. Please try again.\",\n \"addFiles\": \"Add Files\",\n \"maxFilesReached\": \"Maximum files reached\",\n \"maxFilesLimit\": \"You can add up to {{max}} files\"\n}\n"
|
|
33
|
+
"content": "{\r\n \"pageTitle\": \"Contact Us\",\r\n \"title\": \"Let's Start a Conversation\",\r\n \"subtitle\": \"This welcoming message sets the tone for visitor communication. Explain why they should contact you, what kind of help you can provide, or your typical response time. Use Promake to create an inviting message that encourages engagement.\",\r\n \"emailLabel\": \"Email\",\r\n \"phoneLabel\": \"Phone\",\r\n \"addressLabel\": \"Address\",\r\n \"hoursLabel\": \"Hours\",\r\n \"hours\": \"Mon - Fri: 9:00 AM - 6:00 PM\",\r\n \"formTitle\": \"Send us a message\",\r\n \"formSubtitle\": \"Fill out the form below and we'll get back to you as soon as possible.\",\r\n \"nameLabel\": \"Full Name\",\r\n \"namePlaceholder\": \"John Doe\",\r\n \"emailInputLabel\": \"Email\",\r\n \"emailPlaceholder\": \"john@example.com\",\r\n \"phoneInputLabel\": \"Phone\",\r\n \"phonePlaceholder\": \"+1 234 567 890\",\r\n \"messageLabel\": \"Message\",\r\n \"messagePlaceholder\": \"Tell us about your project...\",\r\n \"submit\": \"Send Message\",\r\n \"sending\": \"Sending...\",\r\n \"success\": \"Message sent! We'll be in touch soon.\",\r\n \"error\": \"Something went wrong. Please try again.\",\r\n \"addFiles\": \"Add Files\",\r\n \"maxFilesReached\": \"Maximum files reached\",\r\n \"maxFilesLimit\": \"You can add up to {{max}} files\"\r\n}\r\n"
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
"path": "contact-page-split/lang/tr.json",
|
|
37
37
|
"type": "registry:lang",
|
|
38
38
|
"target": "$modules$/contact-page-split/lang/tr.json",
|
|
39
|
-
"content": "{\n \"pageTitle\": \"İletişim\",\n \"title\": \"Bir Sohbet Başlatalım\",\n \"subtitle\": \"Bu karşılama mesajı ziyaretçi iletişimi için tonu belirler. Neden sizinle iletişime geçmeleri gerektiğini, ne tür yardım sağlayabileceğinizi veya tipik yanıt sürenizi açıklayın. Promake ile etkileşimi teşvik eden davetkar bir mesaj oluşturun.\",\n \"emailLabel\": \"E-posta\",\n \"phoneLabel\": \"Telefon\",\n \"addressLabel\": \"Adres\",\n \"hoursLabel\": \"Çalışma Saatleri\",\n \"hours\": \"Pazartesi - Cuma: 09:00 - 18:00\",\n \"formTitle\": \"Bize mesaj gönderin\",\n \"formSubtitle\": \"Aşağıdaki formu doldurun, en kısa sürede size döneceğiz.\",\n \"nameLabel\": \"Ad Soyad\",\n \"namePlaceholder\": \"Ahmet Yılmaz\",\n \"emailInputLabel\": \"E-posta\",\n \"emailPlaceholder\": \"ahmet@ornek.com\",\n \"phoneInputLabel\": \"Telefon\",\n \"phonePlaceholder\": \"+90 532 123 4567\",\n \"messageLabel\": \"Mesaj\",\n \"messagePlaceholder\": \"Projeniz hakkında bize bilgi verin...\",\n \"submit\": \"Mesaj Gönder\",\n \"sending\": \"Gönderiliyor...\",\n \"success\": \"Mesaj gönderildi! En kısa sürede iletişime geçeceğiz.\",\n \"error\": \"Bir şeyler yanlış gitti. Lütfen tekrar deneyin.\",\n \"addFiles\": \"Dosya Ekle\",\n \"maxFilesReached\": \"Maksimum dosya sayısına ulaşıldı\",\n \"maxFilesLimit\": \"En fazla {{max}} kadar dosya ekleyebilirsiniz\"\n}\n"
|
|
39
|
+
"content": "{\r\n \"pageTitle\": \"İletişim\",\r\n \"title\": \"Bir Sohbet Başlatalım\",\r\n \"subtitle\": \"Bu karşılama mesajı ziyaretçi iletişimi için tonu belirler. Neden sizinle iletişime geçmeleri gerektiğini, ne tür yardım sağlayabileceğinizi veya tipik yanıt sürenizi açıklayın. Promake ile etkileşimi teşvik eden davetkar bir mesaj oluşturun.\",\r\n \"emailLabel\": \"E-posta\",\r\n \"phoneLabel\": \"Telefon\",\r\n \"addressLabel\": \"Adres\",\r\n \"hoursLabel\": \"Çalışma Saatleri\",\r\n \"hours\": \"Pazartesi - Cuma: 09:00 - 18:00\",\r\n \"formTitle\": \"Bize mesaj gönderin\",\r\n \"formSubtitle\": \"Aşağıdaki formu doldurun, en kısa sürede size döneceğiz.\",\r\n \"nameLabel\": \"Ad Soyad\",\r\n \"namePlaceholder\": \"Ahmet Yılmaz\",\r\n \"emailInputLabel\": \"E-posta\",\r\n \"emailPlaceholder\": \"ahmet@ornek.com\",\r\n \"phoneInputLabel\": \"Telefon\",\r\n \"phonePlaceholder\": \"+90 532 123 4567\",\r\n \"messageLabel\": \"Mesaj\",\r\n \"messagePlaceholder\": \"Projeniz hakkında bize bilgi verin...\",\r\n \"submit\": \"Mesaj Gönder\",\r\n \"sending\": \"Gönderiliyor...\",\r\n \"success\": \"Mesaj gönderildi! En kısa sürede iletişime geçeceğiz.\",\r\n \"error\": \"Bir şeyler yanlış gitti. Lütfen tekrar deneyin.\",\r\n \"addFiles\": \"Dosya Ekle\",\r\n \"maxFilesReached\": \"Maksimum dosya sayısına ulaşıldı\",\r\n \"maxFilesLimit\": \"En fazla {{max}} kadar dosya ekleyebilirsiniz\"\r\n}\r\n"
|
|
40
40
|
}
|
|
41
41
|
],
|
|
42
42
|
"exports": {
|
|
@@ -16,25 +16,25 @@
|
|
|
16
16
|
"path": "contact-page/index.ts",
|
|
17
17
|
"type": "registry:index",
|
|
18
18
|
"target": "$modules$/contact-page/index.ts",
|
|
19
|
-
"content": "export * from './contact-page';\nexport { ContactPage as default } from './contact-page';\n"
|
|
19
|
+
"content": "export * from './contact-page';\r\nexport { ContactPage as default } from './contact-page';\r\n"
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
"path": "contact-page/contact-page.tsx",
|
|
23
23
|
"type": "registry:page",
|
|
24
24
|
"target": "$modules$/contact-page/contact-page.tsx",
|
|
25
|
-
"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 { 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\";\nimport { FormFileInput } from \"@/components/FormFileInput\";\nimport { FormField } from \"@/components/FormField\";\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 attachments: [] as File[],\n });\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [submitStatus, setSubmitStatus] = useState<\n \"idle\" | \"success\" | \"error\"\n >(\"idle\");\n const fileMaxFiles = constants.file?.maxFiles || 5;\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setIsSubmitting(true);\n setSubmitStatus(\"idle\");\n try {\n const currentLanguage = constants.site.defaultLanguage;\n\n await apiService.submitFormWithFile(\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 { name: \"attachments\", required: false },\n ],\n },\n currentLanguage\n );\n\n setSubmitStatus(\"success\");\n setFormData({\n name: \"\",\n email: \"\",\n phone: \"\",\n subject: \"\",\n message: \"\",\n attachments: [],\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 const handleFileUploadChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const selectedFiles = Array.from(e.target.files || []) as File[];\n const remainingSlots = fileMaxFiles - formData.attachments.length;\n\n if (selectedFiles.length > remainingSlots) {\n alert(t(\"maxFilesLimit\", { max: fileMaxFiles }));\n e.target.value = ''; // Clear the input value\n return;\n }\n\n setFormData(prev => ({\n ...prev,\n attachments: [...prev.attachments, ...selectedFiles]\n }));\n\n e.target.value = ''; // Clear the input\n }\n const handleRemoveFile = (index: number) => {\n setFormData(prev => ({\n ...prev,\n attachments: prev.attachments.filter((_, i) => i !== index)\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 <FormField label={t(\"fullName\")} htmlFor=\"name\" required>\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 </FormField>\n {/* Email */}\n <FormField label={t(\"emailAddress\")} htmlFor=\"email\" required>\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 </FormField>\n {/* Phone */}\n <FormField label={t(\"phoneNumber\")} htmlFor=\"phone\">\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 </FormField>\n {/* Subject */}\n <FormField label={t(\"subject\")} htmlFor=\"subject\" >\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 </FormField>\n {/* Message */}\n <FormField label={t(\"message\")} htmlFor=\"message\" required>\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 </FormField>\n <FormFileInput\n files={formData.attachments}\n onFilesChange={handleFileUploadChange}\n handleRemoveFile={handleRemoveFile}\n maxFiles={constants.file?.maxFiles || 5}\n accept={constants.file?.accept || \".pdf,.doc,.docx,.jpg,.jpeg,.png\"}\n disabled={isSubmitting}\n uploadButtonText={t(\"addFiles\")}\n maxFilesReachedText={t(\"maxFilesReached\")}\n />\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\nexport default ContactPage;\n"
|
|
25
|
+
"content": "import React, { useState, useMemo } from \"react\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { useTranslation } from \"react-i18next\";\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 { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\r\nimport {\r\n Mail,\r\n Phone,\r\n MapPin,\r\n MessageSquare,\r\n Facebook,\r\n Twitter,\r\n Instagram,\r\n Linkedin,\r\n} from \"lucide-react\";\r\nimport constants from \"@/constants/constants.json\";\r\nimport { FadeIn, SlideInLeft, SlideInRight } from \"@/modules/animations\";\r\nimport { FormFileInput } from \"@/components/FormFileInput\";\r\nimport { FormField } from \"@/components/FormField\";\r\n\r\nconst socialIcons: Record<string, React.ElementType> = {\r\n facebook: Facebook,\r\n twitter: Twitter,\r\n instagram: Instagram,\r\n linkedin: Linkedin,\r\n};\r\n\r\nexport function ContactPage() {\r\n const { t } = useTranslation(\"contact-page\");\r\n usePageTitle({ title: t(\"title\") });\r\n\r\n const apiService = useApiService();\r\n\r\n const socialLinks = useMemo(() => {\r\n const socialMedia = constants.socialMedia as\r\n | Record<string, string>\r\n | undefined;\r\n if (!socialMedia) return [];\r\n return Object.entries(socialMedia)\r\n .filter(([platform, url]) => url && socialIcons[platform])\r\n .map(([platform, url]) => ({\r\n platform,\r\n url,\r\n Icon: socialIcons[platform],\r\n }));\r\n }, []);\r\n\r\n const [formData, setFormData] = useState({\r\n name: \"\",\r\n email: \"\",\r\n phone: \"\",\r\n subject: \"\",\r\n message: \"\",\r\n attachments: [] as File[],\r\n });\r\n const [isSubmitting, setIsSubmitting] = useState(false);\r\n const [submitStatus, setSubmitStatus] = useState<\r\n \"idle\" | \"success\" | \"error\"\r\n >(\"idle\");\r\n const fileMaxFiles = constants.file?.maxFiles || 5;\r\n\r\n const handleSubmit = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setIsSubmitting(true);\r\n setSubmitStatus(\"idle\");\r\n try {\r\n const currentLanguage = constants.site.defaultLanguage;\r\n\r\n await apiService.submitFormWithFile(\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: \"phone\", required: false },\r\n { name: \"subject\", required: false },\r\n { name: \"message\", required: true },\r\n { name: \"attachments\", required: false },\r\n ],\r\n },\r\n currentLanguage\r\n );\r\n\r\n setSubmitStatus(\"success\");\r\n setFormData({\r\n name: \"\",\r\n email: \"\",\r\n phone: \"\",\r\n subject: \"\",\r\n message: \"\",\r\n attachments: [],\r\n });\r\n\r\n setTimeout(() => {\r\n setSubmitStatus(\"idle\");\r\n }, 5000);\r\n } catch (error: unknown) {\r\n console.error(\"Form submission failed:\", error);\r\n setSubmitStatus(\"error\");\r\n\r\n setTimeout(() => {\r\n setSubmitStatus(\"idle\");\r\n }, 5000);\r\n } finally {\r\n setIsSubmitting(false);\r\n }\r\n };\r\n const handleFileUploadChange = (e: React.ChangeEvent<HTMLInputElement>) => {\r\n const selectedFiles = Array.from(e.target.files || []) as File[];\r\n const remainingSlots = fileMaxFiles - formData.attachments.length;\r\n\r\n if (selectedFiles.length > remainingSlots) {\r\n alert(t(\"maxFilesLimit\", { max: fileMaxFiles }));\r\n e.target.value = ''; // Clear the input value\r\n return;\r\n }\r\n\r\n setFormData(prev => ({\r\n ...prev,\r\n attachments: [...prev.attachments, ...selectedFiles]\r\n }));\r\n\r\n e.target.value = ''; // Clear the input\r\n }\r\n const handleRemoveFile = (index: number) => {\r\n setFormData(prev => ({\r\n ...prev,\r\n attachments: prev.attachments.filter((_, i) => i !== index)\r\n }));\r\n };\r\n const handleChange = (\r\n e: React.ChangeEvent<\r\n HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement\r\n >\r\n ) => {\r\n setFormData((prev) => ({\r\n ...prev,\r\n [e.target.name]: e.target.value,\r\n }));\r\n };\r\n\r\n return (\r\n <Layout>\r\n <div className=\"min-h-screen bg-muted/30 py-12\">\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 max-w-6xl\">\r\n {/* Hero Section */}\r\n <FadeIn className=\"text-center mb-12\">\r\n <h1 className=\"text-4xl font-bold text-foreground mb-4\">\r\n {t(\"title\")}\r\n </h1>\r\n <div className=\"w-16 h-1 bg-primary mx-auto mb-6\"></div>\r\n <p className=\"text-lg text-muted-foreground max-w-3xl mx-auto\">\r\n {t(\"description\")}\r\n </p>\r\n </FadeIn>\r\n\r\n <div className=\"grid lg:grid-cols-3 gap-8\">\r\n {/* Contact Information */}\r\n <SlideInLeft className=\"lg:col-span-1 space-y-6\">\r\n {/* Company Info */}\r\n <Card>\r\n <CardHeader>\r\n <CardTitle className=\"flex items-center gap-2\">\r\n <MessageSquare className=\"w-5 h-5 text-primary\" />\r\n {t(\"getInTouch\")}\r\n </CardTitle>\r\n </CardHeader>\r\n <CardContent className=\"space-y-4\">\r\n <div className=\"flex items-start gap-3\">\r\n <Mail className=\"w-5 h-5 text-primary mt-1\" />\r\n <div>\r\n <p className=\"font-medium text-foreground\">\r\n {t(\"email\")}\r\n </p>\r\n <p className=\"text-muted-foreground\">{constants.email}</p>\r\n <p className=\"text-sm text-muted-foreground mt-1\">\r\n {t(\"emailResponse\")}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <div className=\"flex items-start gap-3\">\r\n <Phone className=\"w-5 h-5 text-primary mt-1\" />\r\n <div>\r\n <p className=\"font-medium text-foreground\">\r\n {t(\"phone\")}\r\n </p>\r\n <p className=\"text-muted-foreground\">{constants.phone}</p>\r\n </div>\r\n </div>\r\n\r\n <div className=\"flex items-start gap-3\">\r\n <MapPin className=\"w-5 h-5 text-primary mt-1\" />\r\n <div>\r\n <p className=\"font-medium text-foreground\">\r\n {t(\"address\")}\r\n </p>\r\n <div className=\"text-muted-foreground\">\r\n <p>{constants.address.line1}</p>\r\n {constants.address.line2 && (\r\n <p>{constants.address.line2}</p>\r\n )}\r\n <p>\r\n {constants.address.city}, {constants.address.state}{\" \"}\r\n {constants.address.postalCode}\r\n </p>\r\n {constants.address.country && (\r\n <p>{constants.address.country}</p>\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n {/* Social Media Links */}\r\n {socialLinks.length > 0 && (\r\n <div className=\"pt-4 border-t\">\r\n <p className=\"font-medium text-foreground mb-3\">\r\n {t(\"followUs\")}\r\n </p>\r\n <div className=\"flex gap-2\">\r\n {socialLinks.map(({ platform, url, Icon }) => (\r\n <a\r\n key={platform}\r\n href={url}\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\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\"\r\n >\r\n <Icon className=\"h-5 w-5\" />\r\n </a>\r\n ))}\r\n </div>\r\n </div>\r\n )}\r\n </CardContent>\r\n </Card>\r\n\r\n {/* Support Info */}\r\n <Card>\r\n <CardHeader>\r\n <CardTitle>{t(\"needSupport\")}</CardTitle>\r\n </CardHeader>\r\n <CardContent>\r\n <p className=\"text-muted-foreground mb-4\">\r\n {t(\"supportDescription\")}\r\n </p>\r\n <div className=\"space-y-2\">\r\n <p className=\"font-medium text-foreground\">\r\n {t(\"supportEmail\")}\r\n </p>\r\n <p className=\"text-muted-foreground\">{constants.email}</p>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n </SlideInLeft>\r\n\r\n {/* Contact Form */}\r\n <SlideInRight className=\"lg:col-span-2\">\r\n <Card>\r\n <CardHeader>\r\n <CardTitle>{t(\"sendMessage\")}</CardTitle>\r\n </CardHeader>\r\n <CardContent>\r\n <form onSubmit={handleSubmit} className=\"space-y-6\">\r\n {/* Full Name */}\r\n <FormField label={t(\"fullName\")} htmlFor=\"name\" required>\r\n <Input\r\n id=\"name\"\r\n name=\"name\"\r\n type=\"text\"\r\n value={formData.name}\r\n onChange={handleChange}\r\n placeholder={t(\"fullNamePlaceholder\")}\r\n required\r\n className=\"mt-1\"\r\n />\r\n </FormField>\r\n {/* Email */}\r\n <FormField label={t(\"emailAddress\")} htmlFor=\"email\" required>\r\n <Input\r\n id=\"email\"\r\n name=\"email\"\r\n type=\"email\"\r\n value={formData.email}\r\n onChange={handleChange}\r\n placeholder={t(\"emailPlaceholder\")}\r\n required\r\n className=\"mt-1\"\r\n />\r\n </FormField>\r\n {/* Phone */}\r\n <FormField label={t(\"phoneNumber\")} htmlFor=\"phone\">\r\n <Input\r\n id=\"phone\"\r\n name=\"phone\"\r\n type=\"tel\"\r\n value={formData.phone}\r\n onChange={handleChange}\r\n placeholder={t(\"phonePlaceholder\")}\r\n className=\"mt-1\"\r\n />\r\n </FormField>\r\n {/* Subject */}\r\n <FormField label={t(\"subject\")} htmlFor=\"subject\" >\r\n <Input\r\n id=\"subject\"\r\n name=\"subject\"\r\n type=\"text\"\r\n value={formData.subject}\r\n onChange={handleChange}\r\n placeholder={t(\"subjectPlaceholder\")}\r\n className=\"mt-1\"\r\n />\r\n </FormField>\r\n {/* Message */}\r\n <FormField label={t(\"message\")} htmlFor=\"message\" required>\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\")}\r\n required\r\n rows={6}\r\n className=\"mt-1 resize-none\"\r\n />\r\n </FormField>\r\n <FormFileInput\r\n files={formData.attachments}\r\n onFilesChange={handleFileUploadChange}\r\n handleRemoveFile={handleRemoveFile}\r\n maxFiles={constants.file?.maxFiles || 5}\r\n accept={constants.file?.accept || \".pdf,.doc,.docx,.jpg,.jpeg,.png\"}\r\n disabled={isSubmitting}\r\n uploadButtonText={t(\"addFiles\")}\r\n maxFilesReachedText={t(\"maxFilesReached\")}\r\n />\r\n\r\n {submitStatus === \"success\" && (\r\n <div className=\"p-4 bg-green-500/10 dark:bg-green-500/20 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\")}\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\")}\r\n </p>\r\n </div>\r\n )}\r\n\r\n <Button\r\n type=\"submit\"\r\n size=\"lg\"\r\n className=\"w-full\"\r\n disabled={isSubmitting}\r\n >\r\n {isSubmitting ? (\r\n <>\r\n <div className=\"w-4 h-4 border-2 border-primary-foreground border-t-transparent rounded-full animate-spin mr-2\" />\r\n {t(\"sending\")}\r\n </>\r\n ) : (\r\n t(\"submit\")\r\n )}\r\n </Button>\r\n </form>\r\n </CardContent>\r\n </Card>\r\n </SlideInRight>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default ContactPage;\r\n"
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
"path": "contact-page/lang/en.json",
|
|
29
29
|
"type": "registry:lang",
|
|
30
30
|
"target": "$modules$/contact-page/lang/en.json",
|
|
31
|
-
"content": "{\n \"title\": \"Contact Us\",\n \"getInTouch\": \"Get in Touch\",\n \"emailUs\": \"Email Us\",\n \"callUs\": \"Call Us\",\n \"visitUs\": \"Visit Us\",\n \"businessHours\": \"Hours\",\n \"sendMessage\": \"Send us a Message\",\n \"formNotAvailable\": \"Form is not available at the moment.\",\n \"fullName\": \"Full Name\",\n \"emailAddress\": \"Email Address\",\n \"phoneNumber\": \"Phone Number\",\n \"subject\": \"Subject\",\n \"message\": \"Message\",\n \"submit\": \"Send Message\",\n \"sending\": \"Sending...\",\n \"success\": \"Thank you for your message! We will get back to you soon.\",\n \"error\": \"Failed to send message. Please try again later.\",\n \"description\": \"Have a question or need support? We're here to help and typically respond within 24 hours.\",\n \"email\": \"Email\",\n \"phone\": \"Phone\",\n \"address\": \"Address\",\n \"fullNamePlaceholder\": \"Your full name\",\n \"emailPlaceholder\": \"your@email.com\",\n \"phonePlaceholder\": \"+1 (555) 123-4567\",\n \"subjectPlaceholder\": \"What is this regarding?\",\n \"messagePlaceholder\": \"Tell us how we can help you...\",\n \"loading\": \"Loading contact information...\",\n \"emailResponse\": \"We typically respond within 24 hours\",\n \"needSupport\": \"Need Support?\",\n \"supportDescription\": \"For technical support or general inquiries, contact our dedicated support team:\",\n \"supportEmail\": \"Support Email\",\n \"monday_friday\": \"Monday - Friday\",\n \"saturday\": \"Saturday\",\n \"sunday\": \"Sunday\",\n \"closed\": \"Closed\",\n \"am\": \"AM\",\n \"pm\": \"PM\",\n \"addFiles\": \"Add Files\",\n \"maxFilesReached\": \"Maximum files reached\",\n \"maxFilesLimit\": \"You can add up to {{max}} files\",\n \"followUs\": \"Follow Us\"\n}"
|
|
31
|
+
"content": "{\r\n \"title\": \"Contact Us\",\r\n \"getInTouch\": \"Get in Touch\",\r\n \"emailUs\": \"Email Us\",\r\n \"callUs\": \"Call Us\",\r\n \"visitUs\": \"Visit Us\",\r\n \"businessHours\": \"Hours\",\r\n \"sendMessage\": \"Send us a Message\",\r\n \"formNotAvailable\": \"Form is not available at the moment.\",\r\n \"fullName\": \"Full Name\",\r\n \"emailAddress\": \"Email Address\",\r\n \"phoneNumber\": \"Phone Number\",\r\n \"subject\": \"Subject\",\r\n \"message\": \"Message\",\r\n \"submit\": \"Send Message\",\r\n \"sending\": \"Sending...\",\r\n \"success\": \"Thank you for your message! We will get back to you soon.\",\r\n \"error\": \"Failed to send message. Please try again later.\",\r\n \"description\": \"Have a question or need support? We're here to help and typically respond within 24 hours.\",\r\n \"email\": \"Email\",\r\n \"phone\": \"Phone\",\r\n \"address\": \"Address\",\r\n \"fullNamePlaceholder\": \"Your full name\",\r\n \"emailPlaceholder\": \"your@email.com\",\r\n \"phonePlaceholder\": \"+1 (555) 123-4567\",\r\n \"subjectPlaceholder\": \"What is this regarding?\",\r\n \"messagePlaceholder\": \"Tell us how we can help you...\",\r\n \"loading\": \"Loading contact information...\",\r\n \"emailResponse\": \"We typically respond within 24 hours\",\r\n \"needSupport\": \"Need Support?\",\r\n \"supportDescription\": \"For technical support or general inquiries, contact our dedicated support team:\",\r\n \"supportEmail\": \"Support Email\",\r\n \"monday_friday\": \"Monday - Friday\",\r\n \"saturday\": \"Saturday\",\r\n \"sunday\": \"Sunday\",\r\n \"closed\": \"Closed\",\r\n \"am\": \"AM\",\r\n \"pm\": \"PM\",\r\n \"addFiles\": \"Add Files\",\r\n \"maxFilesReached\": \"Maximum files reached\",\r\n \"maxFilesLimit\": \"You can add up to {{max}} files\",\r\n \"followUs\": \"Follow Us\"\r\n}"
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
34
|
"path": "contact-page/lang/tr.json",
|
|
35
35
|
"type": "registry:lang",
|
|
36
36
|
"target": "$modules$/contact-page/lang/tr.json",
|
|
37
|
-
"content": "{\n \"title\": \"İletişim\",\n \"getInTouch\": \"Bize Ulaşın\",\n \"emailUs\": \"E-posta Gönderin\",\n \"callUs\": \"Bizi Arayın\",\n \"visitUs\": \"Bizi Ziyaret Edin\",\n \"businessHours\": \"Çalışma Saatleri\",\n \"sendMessage\": \"Bize Mesaj Gönderin\",\n \"formNotAvailable\": \"Form şu anda kullanılamıyor.\",\n \"fullName\": \"Ad Soyad\",\n \"emailAddress\": \"E-posta Adresi\",\n \"phoneNumber\": \"Telefon Numarası\",\n \"subject\": \"Konu\",\n \"message\": \"Mesaj\",\n \"submit\": \"Mesaj Gönder\",\n \"sending\": \"Gönderiliyor...\",\n \"success\": \"Mesajınız için teşekkürler! En kısa sürede size dönüş yapacağız.\",\n \"error\": \"Mesaj gönderilemedi. Lütfen tekrar deneyin.\",\n \"description\": \"İletişim seçenekleri ve müsaitlik bilgisi. Yanıt süreleri ve soru türlerini ekleyin.\",\n \"email\": \"E-posta\",\n \"phone\": \"Telefon\",\n \"address\": \"Adres\",\n \"fullNamePlaceholder\": \"Adınız ve soyadınız\",\n \"emailPlaceholder\": \"eposta@adresiniz.com\",\n \"phonePlaceholder\": \"+90 5XX XXX XX XX\",\n \"subjectPlaceholder\": \"Konu nedir?\",\n \"messagePlaceholder\": \"Size nasıl yardımcı olabiliriz...\",\n \"loading\": \"İletişim bilgileri yükleniyor...\",\n \"emailResponse\": \"Genellikle 24 saat içinde yanıt veririz\",\n \"needSupport\": \"Desteğe İhtiyacınız mı Var?\",\n \"supportDescription\": \"Teknik destek veya sipariş sorularınız için özel destek ekibimizle iletişime geçin:\",\n \"supportEmail\": \"Destek E-postası\",\n \"monday_friday\": \"Pazartesi - Cuma\",\n \"saturday\": \"Cumartesi\",\n \"sunday\": \"Pazar\",\n \"closed\": \"Kapalı\",\n \"am\": \"\",\n \"pm\": \"\",\n \"addFiles\": \"Dosya Ekle\",\n \"maxFilesReached\": \"Maksimum dosya sayısına ulaşıldı\",\n \"maxFilesLimit\": \"En fazla {{max}} kadar dosya ekleyebilirsiniz\",\n \"followUs\": \"Bizi Takip Edin\"\n}"
|
|
37
|
+
"content": "{\r\n \"title\": \"İletişim\",\r\n \"getInTouch\": \"Bize Ulaşın\",\r\n \"emailUs\": \"E-posta Gönderin\",\r\n \"callUs\": \"Bizi Arayın\",\r\n \"visitUs\": \"Bizi Ziyaret Edin\",\r\n \"businessHours\": \"Çalışma Saatleri\",\r\n \"sendMessage\": \"Bize Mesaj Gönderin\",\r\n \"formNotAvailable\": \"Form şu anda kullanılamıyor.\",\r\n \"fullName\": \"Ad Soyad\",\r\n \"emailAddress\": \"E-posta Adresi\",\r\n \"phoneNumber\": \"Telefon Numarası\",\r\n \"subject\": \"Konu\",\r\n \"message\": \"Mesaj\",\r\n \"submit\": \"Mesaj Gönder\",\r\n \"sending\": \"Gönderiliyor...\",\r\n \"success\": \"Mesajınız için teşekkürler! En kısa sürede size dönüş yapacağız.\",\r\n \"error\": \"Mesaj gönderilemedi. Lütfen tekrar deneyin.\",\r\n \"description\": \"İletişim seçenekleri ve müsaitlik bilgisi. Yanıt süreleri ve soru türlerini ekleyin.\",\r\n \"email\": \"E-posta\",\r\n \"phone\": \"Telefon\",\r\n \"address\": \"Adres\",\r\n \"fullNamePlaceholder\": \"Adınız ve soyadınız\",\r\n \"emailPlaceholder\": \"eposta@adresiniz.com\",\r\n \"phonePlaceholder\": \"+90 5XX XXX XX XX\",\r\n \"subjectPlaceholder\": \"Konu nedir?\",\r\n \"messagePlaceholder\": \"Size nasıl yardımcı olabiliriz...\",\r\n \"loading\": \"İletişim bilgileri yükleniyor...\",\r\n \"emailResponse\": \"Genellikle 24 saat içinde yanıt veririz\",\r\n \"needSupport\": \"Desteğe İhtiyacınız mı Var?\",\r\n \"supportDescription\": \"Teknik destek veya sipariş sorularınız için özel destek ekibimizle iletişime geçin:\",\r\n \"supportEmail\": \"Destek E-postası\",\r\n \"monday_friday\": \"Pazartesi - Cuma\",\r\n \"saturday\": \"Cumartesi\",\r\n \"sunday\": \"Pazar\",\r\n \"closed\": \"Kapalı\",\r\n \"am\": \"\",\r\n \"pm\": \"\",\r\n \"addFiles\": \"Dosya Ekle\",\r\n \"maxFilesReached\": \"Maksimum dosya sayısına ulaşıldı\",\r\n \"maxFilesLimit\": \"En fazla {{max}} kadar dosya ekleyebilirsiniz\",\r\n \"followUs\": \"Bizi Takip Edin\"\r\n}"
|
|
38
38
|
}
|
|
39
39
|
],
|
|
40
40
|
"exports": {
|
|
@@ -10,25 +10,25 @@
|
|
|
10
10
|
"path": "content-section/index.ts",
|
|
11
11
|
"type": "registry:index",
|
|
12
12
|
"target": "$modules$/content-section/index.ts",
|
|
13
|
-
"content": "export * from './content-section';\n"
|
|
13
|
+
"content": "export * from './content-section';\r\n"
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
"path": "content-section/content-section.tsx",
|
|
17
17
|
"type": "registry:component",
|
|
18
18
|
"target": "$modules$/content-section/content-section.tsx",
|
|
19
|
-
"content": "import { Link } from \"react-router\";\nimport { ChevronRight } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\ninterface ContentSectionProps {\n image: string;\n imageAlt?: string;\n title: string;\n description: string;\n ctaText?: string;\n ctaLink?: string;\n grayscale?: boolean;\n className?: string;\n}\n\nexport function ContentSection({\n image,\n imageAlt,\n title,\n description,\n ctaText,\n ctaLink = \"#\",\n grayscale = false,\n className,\n}: ContentSectionProps) {\n\n return (\n <section className={cn(\"py-16 md:py-32\", className)}>\n <div className=\"mx-auto max-w-5xl space-y-8 px-6 md:space-y-12\">\n <img\n className={cn(\"rounded-lg w-full\", grayscale && \"grayscale\")}\n src={image}\n alt={imageAlt || title}\n loading=\"lazy\"\n />\n\n <div className=\"grid gap-6 md:grid-cols-2 md:gap-12\">\n <h2 className=\"text-3xl md:text-4xl font-medium leading-tight\">\n {title}\n </h2>\n <div className=\"space-y-6\">\n <p className=\"text-muted-foreground leading-relaxed\">\n {description}\n </p>\n\n {ctaText && (\n <Button asChild variant=\"secondary\" size=\"sm\" className=\"gap-1 pr-1.5\">\n <Link to={ctaLink}>\n <span>{ctaText}</span>\n <ChevronRight className=\"size-4\" />\n </Link>\n </Button>\n )}\n </div>\n </div>\n </div>\n </section>\n );\n}\n"
|
|
19
|
+
"content": "import { Link } from \"react-router\";\r\nimport { ChevronRight } from \"lucide-react\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ninterface ContentSectionProps {\r\n image: string;\r\n imageAlt?: string;\r\n title: string;\r\n description: string;\r\n ctaText?: string;\r\n ctaLink?: string;\r\n grayscale?: boolean;\r\n className?: string;\r\n}\r\n\r\nexport function ContentSection({\r\n image,\r\n imageAlt,\r\n title,\r\n description,\r\n ctaText,\r\n ctaLink = \"#\",\r\n grayscale = false,\r\n className,\r\n}: ContentSectionProps) {\r\n\r\n return (\r\n <section className={cn(\"py-16 md:py-32\", className)}>\r\n <div className=\"mx-auto max-w-5xl space-y-8 px-6 md:space-y-12\">\r\n <img\r\n className={cn(\"rounded-lg w-full\", grayscale && \"grayscale\")}\r\n src={image}\r\n alt={imageAlt || title}\r\n loading=\"lazy\"\r\n />\r\n\r\n <div className=\"grid gap-6 md:grid-cols-2 md:gap-12\">\r\n <h2 className=\"text-3xl md:text-4xl font-medium leading-tight\">\r\n {title}\r\n </h2>\r\n <div className=\"space-y-6\">\r\n <p className=\"text-muted-foreground leading-relaxed\">\r\n {description}\r\n </p>\r\n\r\n {ctaText && (\r\n <Button asChild variant=\"secondary\" size=\"sm\" className=\"gap-1 pr-1.5\">\r\n <Link to={ctaLink}>\r\n <span>{ctaText}</span>\r\n <ChevronRight className=\"size-4\" />\r\n </Link>\r\n </Button>\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n </section>\r\n );\r\n}\r\n"
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
"path": "content-section/lang/en.json",
|
|
23
23
|
"type": "registry:lang",
|
|
24
24
|
"target": "$modules$/content-section/lang/en.json",
|
|
25
|
-
"content": "{\n \"learnMore\": \"Learn More\"\n}\n"
|
|
25
|
+
"content": "{\r\n \"learnMore\": \"Learn More\"\r\n}\r\n"
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
"path": "content-section/lang/tr.json",
|
|
29
29
|
"type": "registry:lang",
|
|
30
30
|
"target": "$modules$/content-section/lang/tr.json",
|
|
31
|
-
"content": "{\n \"learnMore\": \"Daha Fazla\"\n}\n"
|
|
31
|
+
"content": "{\r\n \"learnMore\": \"Daha Fazla\"\r\n}\r\n"
|
|
32
32
|
}
|
|
33
33
|
],
|
|
34
34
|
"exports": {
|
|
@@ -13,25 +13,25 @@
|
|
|
13
13
|
"path": "cookie-consent/index.ts",
|
|
14
14
|
"type": "registry:index",
|
|
15
15
|
"target": "$modules$/cookie-consent/index.ts",
|
|
16
|
-
"content": "export { CookieConsent } from \"./cookie-consent\";\n"
|
|
16
|
+
"content": "export { CookieConsent } from \"./cookie-consent\";\r\n"
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
"path": "cookie-consent/cookie-consent.tsx",
|
|
20
20
|
"type": "registry:component",
|
|
21
21
|
"target": "$modules$/cookie-consent/cookie-consent.tsx",
|
|
22
|
-
"content": "import { useState, useEffect } from \"react\";\nimport { Link } from \"react-router\";\nimport { useTranslation } from \"react-i18next\";\nimport { X } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { motion, AnimatePresence } from \"motion/react\";\n\ninterface CookieConsentProps {\n className?: string;\n privacyPolicyUrl?: string;\n cookiePolicyUrl?: string;\n onAccept?: () => void;\n onDecline?: () => void;\n width?: \"full\" | \"compact\" | \"auto\";\n position?: \"left\" | \"right\" | \"center\";\n storageKey?: string;\n}\n\nconst DEFAULT_STORAGE_KEY = \"cookie-consent\";\n\nexport function CookieConsent({\n className,\n privacyPolicyUrl = \"/privacy\",\n cookiePolicyUrl = \"/cookies\",\n onAccept,\n onDecline,\n width = \"full\",\n position = \"center\",\n storageKey = DEFAULT_STORAGE_KEY,\n}: CookieConsentProps) {\n const { t } = useTranslation(\"cookie-consent\");\n const [isVisible, setIsVisible] = useState(false);\n const [isClosing, setIsClosing] = useState(false);\n\n useEffect(() => {\n const consent = localStorage.getItem(storageKey);\n if (!consent) {\n // Small delay for better UX\n const timer = setTimeout(() => setIsVisible(true), 1000);\n return () => clearTimeout(timer);\n }\n }, [storageKey]);\n\n const handleAccept = () => {\n localStorage.setItem(storageKey, \"accepted\");\n setIsClosing(true);\n setTimeout(() => {\n setIsVisible(false);\n onAccept?.();\n }, 300);\n };\n\n const handleDecline = () => {\n localStorage.setItem(storageKey, \"declined\");\n setIsClosing(true);\n setTimeout(() => {\n setIsVisible(false);\n onDecline?.();\n }, 300);\n };\n\n const handleClose = () => {\n setIsClosing(true);\n setTimeout(() => setIsVisible(false), 300);\n };\n\n if (!isVisible) return null;\n\n return (\n <AnimatePresence>\n {isVisible && (\n <motion.div\n initial={{ y: 100, opacity: 0 }}\n animate={{ y: isClosing ? 100 : 0, opacity: isClosing ? 0 : 1 }}\n exit={{ y: 100, opacity: 0 }}\n transition={{ type: \"spring\", damping: 25, stiffness: 300 }}\n className={cn(\n \"fixed bottom-0 z-50 p-4 md:p-6 left-0 right-0\",\n position === \"left\" && \"md:right-auto\",\n position === \"right\" && \"md:left-auto\",\n className\n )}\n >\n <div className={cn(\n \"w-full mx-auto\",\n width === \"full\" && \"max-w-[var(--container-max-width)]\",\n width === \"compact\" && \"md:max-w-md\",\n width === \"auto\" && \"md:w-auto\"\n )}>\n <div className=\"bg-card border border-border rounded-2xl shadow-2xl p-5 relative\">\n {/* Close button */}\n <button\n onClick={handleClose}\n className=\"absolute top-3 right-3 p-1 text-muted-foreground hover:text-foreground transition-colors\"\n aria-label={t(\"close\", \"Close\")}\n >\n <X className=\"h-4 w-4\" />\n </button>\n\n {/* Title & Description */}\n <div className=\"mb-4 pr-6\">\n <h3 className=\"text-base font-semibold text-foreground mb-1\">\n {t(\"title\", \"Cookie Notice\")}\n </h3>\n <p className=\"text-sm text-muted-foreground\">\n {t(\"description\", \"We use cookies to improve your experience.\")}{\" \"}\n <Link to={privacyPolicyUrl} className=\"text-primary hover:underline\">\n {t(\"privacyPolicy\", \"Privacy\")}\n </Link>\n {\" · \"}\n <Link to={cookiePolicyUrl} className=\"text-primary hover:underline\">\n {t(\"cookiePolicy\", \"Cookies\")}\n </Link>\n </p>\n </div>\n\n {/* Buttons */}\n <div className=\"flex flex-row gap-3\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={handleDecline}\n className=\"flex-1\"\n >\n {t(\"decline\", \"Decline\")}\n </Button>\n <Button\n size=\"sm\"\n onClick={handleAccept}\n className=\"flex-1\"\n >\n {t(\"accept\", \"Accept\")}\n </Button>\n </div>\n </div>\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n );\n}\n"
|
|
22
|
+
"content": "import { useState, useEffect } from \"react\";\r\nimport { Link } from \"react-router\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { X } from \"lucide-react\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { motion, AnimatePresence } from \"motion/react\";\r\n\r\ninterface CookieConsentProps {\r\n className?: string;\r\n privacyPolicyUrl?: string;\r\n cookiePolicyUrl?: string;\r\n onAccept?: () => void;\r\n onDecline?: () => void;\r\n width?: \"full\" | \"compact\" | \"auto\";\r\n position?: \"left\" | \"right\" | \"center\";\r\n storageKey?: string;\r\n}\r\n\r\nconst DEFAULT_STORAGE_KEY = \"cookie-consent\";\r\n\r\nexport function CookieConsent({\r\n className,\r\n privacyPolicyUrl = \"/privacy\",\r\n cookiePolicyUrl = \"/cookies\",\r\n onAccept,\r\n onDecline,\r\n width = \"full\",\r\n position = \"center\",\r\n storageKey = DEFAULT_STORAGE_KEY,\r\n}: CookieConsentProps) {\r\n const { t } = useTranslation(\"cookie-consent\");\r\n const [isVisible, setIsVisible] = useState(false);\r\n const [isClosing, setIsClosing] = useState(false);\r\n\r\n useEffect(() => {\r\n const consent = localStorage.getItem(storageKey);\r\n if (!consent) {\r\n // Small delay for better UX\r\n const timer = setTimeout(() => setIsVisible(true), 1000);\r\n return () => clearTimeout(timer);\r\n }\r\n }, [storageKey]);\r\n\r\n const handleAccept = () => {\r\n localStorage.setItem(storageKey, \"accepted\");\r\n setIsClosing(true);\r\n setTimeout(() => {\r\n setIsVisible(false);\r\n onAccept?.();\r\n }, 300);\r\n };\r\n\r\n const handleDecline = () => {\r\n localStorage.setItem(storageKey, \"declined\");\r\n setIsClosing(true);\r\n setTimeout(() => {\r\n setIsVisible(false);\r\n onDecline?.();\r\n }, 300);\r\n };\r\n\r\n const handleClose = () => {\r\n setIsClosing(true);\r\n setTimeout(() => setIsVisible(false), 300);\r\n };\r\n\r\n if (!isVisible) return null;\r\n\r\n return (\r\n <AnimatePresence>\r\n {isVisible && (\r\n <motion.div\r\n initial={{ y: 100, opacity: 0 }}\r\n animate={{ y: isClosing ? 100 : 0, opacity: isClosing ? 0 : 1 }}\r\n exit={{ y: 100, opacity: 0 }}\r\n transition={{ type: \"spring\", damping: 25, stiffness: 300 }}\r\n className={cn(\r\n \"fixed bottom-0 z-50 p-4 md:p-6 left-0 right-0\",\r\n position === \"left\" && \"md:right-auto\",\r\n position === \"right\" && \"md:left-auto\",\r\n className\r\n )}\r\n >\r\n <div className={cn(\r\n \"w-full mx-auto\",\r\n width === \"full\" && \"max-w-[var(--container-max-width)]\",\r\n width === \"compact\" && \"md:max-w-md\",\r\n width === \"auto\" && \"md:w-auto\"\r\n )}>\r\n <div className=\"bg-card border border-border rounded-2xl shadow-2xl p-5 relative\">\r\n {/* Close button */}\r\n <button\r\n onClick={handleClose}\r\n className=\"absolute top-3 right-3 p-1 text-muted-foreground hover:text-foreground transition-colors\"\r\n aria-label={t(\"close\", \"Close\")}\r\n >\r\n <X className=\"h-4 w-4\" />\r\n </button>\r\n\r\n {/* Title & Description */}\r\n <div className=\"mb-4 pr-6\">\r\n <h3 className=\"text-base font-semibold text-foreground mb-1\">\r\n {t(\"title\", \"Cookie Notice\")}\r\n </h3>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"description\", \"We use cookies to improve your experience.\")}{\" \"}\r\n <Link to={privacyPolicyUrl} className=\"text-primary hover:underline\">\r\n {t(\"privacyPolicy\", \"Privacy\")}\r\n </Link>\r\n {\" · \"}\r\n <Link to={cookiePolicyUrl} className=\"text-primary hover:underline\">\r\n {t(\"cookiePolicy\", \"Cookies\")}\r\n </Link>\r\n </p>\r\n </div>\r\n\r\n {/* Buttons */}\r\n <div className=\"flex flex-row gap-3\">\r\n <Button\r\n variant=\"outline\"\r\n size=\"sm\"\r\n onClick={handleDecline}\r\n className=\"flex-1\"\r\n >\r\n {t(\"decline\", \"Decline\")}\r\n </Button>\r\n <Button\r\n size=\"sm\"\r\n onClick={handleAccept}\r\n className=\"flex-1\"\r\n >\r\n {t(\"accept\", \"Accept\")}\r\n </Button>\r\n </div>\r\n </div>\r\n </div>\r\n </motion.div>\r\n )}\r\n </AnimatePresence>\r\n );\r\n}\r\n"
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
25
|
"path": "cookie-consent/lang/en.json",
|
|
26
26
|
"type": "registry:lang",
|
|
27
27
|
"target": "$modules$/cookie-consent/lang/en.json",
|
|
28
|
-
"content": "{\n \"title\": \"Cookie Notice\",\n \"description\": \"We use cookies to improve your experience.\",\n \"privacyPolicy\": \"Privacy\",\n \"cookiePolicy\": \"Cookies\",\n \"accept\": \"Accept\",\n \"decline\": \"Decline\",\n \"close\": \"Close\"\n}\n"
|
|
28
|
+
"content": "{\r\n \"title\": \"Cookie Notice\",\r\n \"description\": \"We use cookies to improve your experience.\",\r\n \"privacyPolicy\": \"Privacy\",\r\n \"cookiePolicy\": \"Cookies\",\r\n \"accept\": \"Accept\",\r\n \"decline\": \"Decline\",\r\n \"close\": \"Close\"\r\n}\r\n"
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"path": "cookie-consent/lang/tr.json",
|
|
32
32
|
"type": "registry:lang",
|
|
33
33
|
"target": "$modules$/cookie-consent/lang/tr.json",
|
|
34
|
-
"content": "{\n \"title\": \"Gizliliğinize değer veriyoruz\",\n \"description\": \"Göz atma deneyiminizi geliştirmek, kişiselleştirilmiş içerik sunmak ve trafiğimizi analiz etmek için çerezler kullanıyoruz. \\\"Tümünü Kabul Et\\\"e tıklayarak çerez kullanımımıza onay verirsiniz.\",\n \"privacyPolicy\": \"Gizlilik Politikası\",\n \"cookiePolicy\": \"Çerez Politikası\",\n \"accept\": \"Tümünü Kabul Et\",\n \"decline\": \"Reddet\",\n \"customize\": \"Özelleştir\",\n \"close\": \"Kapat\"\n}\n"
|
|
34
|
+
"content": "{\r\n \"title\": \"Gizliliğinize değer veriyoruz\",\r\n \"description\": \"Göz atma deneyiminizi geliştirmek, kişiselleştirilmiş içerik sunmak ve trafiğimizi analiz etmek için çerezler kullanıyoruz. \\\"Tümünü Kabul Et\\\"e tıklayarak çerez kullanımımıza onay verirsiniz.\",\r\n \"privacyPolicy\": \"Gizlilik Politikası\",\r\n \"cookiePolicy\": \"Çerez Politikası\",\r\n \"accept\": \"Tümünü Kabul Et\",\r\n \"decline\": \"Reddet\",\r\n \"customize\": \"Özelleştir\",\r\n \"close\": \"Kapat\"\r\n}\r\n"
|
|
35
35
|
}
|
|
36
36
|
],
|
|
37
37
|
"exports": {
|