From a6fd07c7abc8e34961f01ba6824daa773abfe242 Mon Sep 17 00:00:00 2001 From: Shivangi1515 Date: Fri, 12 Jun 2026 14:16:46 +0530 Subject: [PATCH 1/5] fix: redesign contact page as multi-section premium component layout --- .../contact/ContactChannelCards.jsx | 64 +++ .../src/components/contact/ContactFAQ.jsx | 101 +++++ .../src/components/contact/ContactForm.jsx | 391 ++++++++++++++++++ .../src/components/contact/ContactHero.jsx | 27 ++ .../src/components/contact/ContactSidebar.jsx | 153 +++++++ .../contact/ContactSuccessState.jsx | 111 +++++ frontend/src/pages/Contact.jsx | 144 ++----- 7 files changed, 881 insertions(+), 110 deletions(-) create mode 100644 frontend/src/components/contact/ContactChannelCards.jsx create mode 100644 frontend/src/components/contact/ContactFAQ.jsx create mode 100644 frontend/src/components/contact/ContactForm.jsx create mode 100644 frontend/src/components/contact/ContactHero.jsx create mode 100644 frontend/src/components/contact/ContactSidebar.jsx create mode 100644 frontend/src/components/contact/ContactSuccessState.jsx diff --git a/frontend/src/components/contact/ContactChannelCards.jsx b/frontend/src/components/contact/ContactChannelCards.jsx new file mode 100644 index 0000000..abc4826 --- /dev/null +++ b/frontend/src/components/contact/ContactChannelCards.jsx @@ -0,0 +1,64 @@ +import { Mail, Bug, Lightbulb, MessageCircle, ArrowUpRight } from "lucide-react"; + +const channels = [ + { + icon: Mail, + title: "Email", + description: "Send us an email directly for inquiries and support.", + href: "mailto:hello@codelens.dev", + }, + { + icon: Bug, + title: "GitHub Issues", + description: "Report bugs and track progress on existing issues.", + href: "https://github.com/kunalverma2512/CodeLens/issues", + }, + { + icon: Lightbulb, + title: "Feature Request", + description: "Suggest new features and help shape the roadmap.", + href: "https://github.com/kunalverma2512/CodeLens/discussions", + }, + { + icon: MessageCircle, + title: "Discord Community", + description: "Join our community for real-time discussions and help.", + href: "#", + }, +]; + +export default function ContactChannelCards() { + return ( +
+
+
+ {channels.map((channel) => { + const Icon = channel.icon; + return ( + +
+
+ +
+ +
+

+ {channel.title} +

+

+ {channel.description} +

+
+ ); + })} +
+
+
+ ); +} diff --git a/frontend/src/components/contact/ContactFAQ.jsx b/frontend/src/components/contact/ContactFAQ.jsx new file mode 100644 index 0000000..0f119a4 --- /dev/null +++ b/frontend/src/components/contact/ContactFAQ.jsx @@ -0,0 +1,101 @@ +import { useState, useCallback } from "react"; +import { ChevronDown } from "lucide-react"; + +const FAQS = [ + { + q: "Is CodeLens free to use?", + a: "Yes, CodeLens is completely free and open source. You can use it without any restrictions. We believe in making developer tools accessible to everyone.", + }, + { + q: "How do I report a security vulnerability?", + a: "Please report security vulnerabilities privately by emailing security@codelens.dev or opening a GitHub Security Advisory. Do not post security vulnerabilities publicly.", + }, + { + q: "Can I contribute?", + a: "Absolutely! CodeLens is open source and we welcome contributions. Check out our GitHub repository for contributing guidelines, open issues, and the project roadmap.", + }, + { + q: "How do I request new integrations?", + a: "You can request new integrations by opening a feature request on GitHub or reaching out via our Discord community. We prioritize integrations based on community demand.", + }, + { + q: "Where is my data stored?", + a: "Your data is stored securely on our servers. We follow industry best practices for data encryption and privacy. For detailed information, please refer to our privacy policy.", + }, +]; + +export default function ContactFAQ() { + const [openIndex, setOpenIndex] = useState(null); + + const toggle = useCallback( + (index) => { + setOpenIndex((prev) => (prev === index ? null : index)); + }, + [], + ); + + return ( +
+
+
+

+ FAQ +

+

+ Frequently asked questions +

+
+ +
+ {FAQS.map((faq, index) => { + const isOpen = openIndex === index; + const panelId = `faq-panel-${index}`; + const buttonId = `faq-button-${index}`; + + return ( +
+ + +
+
+

+ {faq.a} +

+
+
+
+ ); + })} +
+
+
+ ); +} diff --git a/frontend/src/components/contact/ContactForm.jsx b/frontend/src/components/contact/ContactForm.jsx new file mode 100644 index 0000000..1a48711 --- /dev/null +++ b/frontend/src/components/contact/ContactForm.jsx @@ -0,0 +1,391 @@ +import { useState, useRef, useCallback, useMemo } from "react"; +// eslint-disable-next-line no-unused-vars +import { AnimatePresence, motion } from "framer-motion"; +import { Check, Loader2 } from "lucide-react"; +import ContactSuccessState from "./ContactSuccessState"; + +const CATEGORIES = ["Bug Report", "Feature Request", "Partnership", "General"]; + +const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + +const FIELD_DEFAULTS = { + General: { name: "", email: "", message: "" }, + "Bug Report": { + email: "", + platform: "", + stepsToReproduce: "", + expectedBehavior: "", + actualBehavior: "", + }, + "Feature Request": { + email: "", + platform: "", + description: "", + }, + Partnership: { company: "", email: "", proposal: "" }, +}; + +const FIELD_LABELS = { + General: { + name: "Name", + email: "Email", + message: "Message", + }, + "Bug Report": { + email: "Email", + platform: "Platform", + stepsToReproduce: "Steps to Reproduce", + expectedBehavior: "Expected Behavior", + actualBehavior: "Actual Behavior", + }, + "Feature Request": { + email: "Email", + platform: "Platform", + description: "Description", + }, + Partnership: { + company: "Company", + email: "Email", + proposal: "Proposal", + }, +}; + +const FIELD_TYPES = { + company: "text", + name: "text", + email: "email", + platform: "text", + message: "textarea", + stepsToReproduce: "textarea", + expectedBehavior: "textarea", + actualBehavior: "textarea", + description: "textarea", + proposal: "textarea", +}; + +const FIELD_MAX_LENGTH = { + name: 100, + company: 100, + platform: 100, + email: 254, + message: 2000, + stepsToReproduce: 2000, + expectedBehavior: 1000, + actualBehavior: 1000, + description: 2000, + proposal: 2000, +}; + +function validateEmail(email) { + return EMAIL_RE.test(email); +} + +function buildValidator() { + return function validateField(name, value) { + if (name === "email") { + if (!value) return "Email is required"; + if (!validateEmail(value)) return "Invalid email address"; + } + if (name === "name" && !value) return "Name is required"; + if (name === "company" && !value) return "Company is required"; + if (name === "platform" && !value) return "Platform is required"; + if (name === "message" && !value) return "Message is required"; + if (name === "stepsToReproduce" && !value) + return "Steps to reproduce is required"; + if (name === "expectedBehavior" && !value) + return "Expected behavior is required"; + if (name === "actualBehavior" && !value) + return "Actual behavior is required"; + if (name === "description" && !value) return "Description is required"; + + if (name === "proposal" && !value) return "Proposal is required"; + return null; + }; +} + +function isFormValid(category, values) { + const fields = Object.keys(FIELD_LABELS[category]); + for (const field of fields) { + const trimmed = (values[field] || "").trim(); + if (!trimmed) return false; + if (field === "email" && !validateEmail(trimmed)) return false; + } + return true; +} + +function AutoTextarea({ value, onChange, onBlur, maxLength, id, name, required, placeholder, hasError, hasSuccess }) { + const ref = useRef(null); + const handleInput = useCallback((e) => { + const el = e.target; + el.style.height = "auto"; + el.style.height = el.scrollHeight + "px"; + onChange(e); + }, [onChange]); + + return ( +