diff --git a/frontend/src/components/contact/ContactChannelCards.jsx b/frontend/src/components/contact/ContactChannelCards.jsx
new file mode 100644
index 0000000..328c457
--- /dev/null
+++ b/frontend/src/components/contact/ContactChannelCards.jsx
@@ -0,0 +1,58 @@
+import { Mail, Bug, Lightbulb, ArrowUpRight } from "lucide-react";
+
+const channels = [
+ {
+ icon: Mail,
+ title: "Email",
+ description: "Fire off an email and we'll get back to you — fast.",
+ href: "mailto:hello@codelens.dev",
+ },
+ {
+ icon: Bug,
+ title: "GitHub Issues",
+ description: "Found a bug? Report it and we'll squash it.",
+ href: "https://github.com/kunalverma2512/CodeLens/issues",
+ },
+ {
+ icon: Lightbulb,
+ title: "Feature Request",
+ description: "Got an idea? Drop it and help us shape the future.",
+ href: "https://github.com/kunalverma2512/CodeLens/discussions",
+ },
+];
+
+export default function ContactChannelCards() {
+ return (
+
+ );
+}
diff --git a/frontend/src/components/contact/ContactFAQ.jsx b/frontend/src/components/contact/ContactFAQ.jsx
new file mode 100644
index 0000000..7bf26d1
--- /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 — 100% free and open source. No hidden fees, no restrictions. Developer tools should be accessible to everyone, period.",
+ },
+ {
+ q: "How do I report a security vulnerability?",
+ a: "Found a flaw? Email security@codelens.dev or open a GitHub Security Advisory. Keep vulnerabilities private until we fix them.",
+ },
+ {
+ q: "Can I contribute?",
+ a: "Absolutely. CodeLens is open source and we welcome contributors with open arms. Head to our GitHub repo for guidelines, open issues, and the roadmap.",
+ },
+ {
+ q: "How do I request new integrations?",
+ a: "Open a feature request on GitHub. We listen to the community and prioritize what matters most to you.",
+ },
+ {
+ q: "Where is my data stored?",
+ a: "On secure servers with enterprise-grade encryption. Your data stays yours. Check our privacy policy for the full breakdown.",
+ },
+];
+
+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 (
+
+
toggle(index)}
+ className="flex w-full items-center justify-between gap-4 px-5 py-4 text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-900 focus-visible:ring-inset"
+ aria-expanded={isOpen}
+ aria-controls={panelId}
+ >
+
+ {faq.q}
+
+
+
+
+
+
+ );
+ })}
+
+
+
+ );
+}
diff --git a/frontend/src/components/contact/ContactForm.jsx b/frontend/src/components/contact/ContactForm.jsx
new file mode 100644
index 0000000..2ac9f38
--- /dev/null
+++ b/frontend/src/components/contact/ContactForm.jsx
@@ -0,0 +1,378 @@
+import { useState, useRef, useCallback, useMemo } from "react";
+import { AnimatePresence, motion } from "framer-motion";
+import { Check } 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 (
+
+ );
+}
+
+function TextInput({ value, onChange, onBlur, type, id, name, required, placeholder, hasError, hasSuccess }) {
+ return (
+
+
+ {name === "email" && value && validateEmail(value) && (
+
+ )}
+
+ );
+}
+
+export default function ContactForm() {
+ const [category, setCategory] = useState("General");
+ const [values, setValues] = useState(FIELD_DEFAULTS["General"]);
+ const [errors, setErrors] = useState({});
+ const [touched, setTouched] = useState({});
+ const [submitted, setSubmitted] = useState(false);
+
+ const validate = useMemo(() => buildValidator(), []);
+
+ const switchCategory = useCallback((cat) => {
+ setCategory(cat);
+ setValues(FIELD_DEFAULTS[cat]);
+ setErrors({});
+ setTouched({});
+ }, []);
+
+ const handleChange = useCallback(
+ (e) => {
+ const { name, value } = e.target;
+ const newValues = { ...values, [name]: value };
+ setValues(newValues);
+ if (touched[name]) {
+ const err = validate(name, value);
+ setErrors((prev) => ({ ...prev, [name]: err }));
+ }
+ },
+ [values, touched, validate],
+ );
+
+ const handleBlur = useCallback(
+ (e) => {
+ const { name, value } = e.target;
+ setTouched((prev) => ({ ...prev, [name]: true }));
+ const err = validate(name, value);
+ setErrors((prev) => ({ ...prev, [name]: err }));
+ },
+ [validate],
+ );
+
+ const valid = useMemo(() => isFormValid(category, values), [category, values]);
+
+ const handleSubmit = useCallback(
+ (e) => {
+ e.preventDefault();
+ if (!valid) return;
+ setSubmitted(true);
+ },
+ [valid],
+ );
+
+ const handleReset = useCallback(() => {
+ setSubmitted(false);
+ setValues(FIELD_DEFAULTS["General"]);
+ setErrors({});
+ setTouched({});
+ setCategory("General");
+ }, []);
+
+ const fields = Object.entries(FIELD_LABELS[category]);
+
+ const currentMaxLength = useCallback(
+ (fieldName) => FIELD_MAX_LENGTH[fieldName] || 2000,
+ [],
+ );
+
+ return (
+
+ {submitted ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+ );
+}
diff --git a/frontend/src/components/contact/ContactHero.jsx b/frontend/src/components/contact/ContactHero.jsx
new file mode 100644
index 0000000..5be20e4
--- /dev/null
+++ b/frontend/src/components/contact/ContactHero.jsx
@@ -0,0 +1,26 @@
+export default function ContactHero() {
+ return (
+
+
+
+
+ Contact
+
+
+ Reach out — we don't keep you waiting
+
+
+ Questions, bugs, big ideas — don't sit on them. Drop us a line and we'll take it from there.
+
+
+
+
+
+
+ We reply within 24 hours
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/contact/ContactSidebar.jsx b/frontend/src/components/contact/ContactSidebar.jsx
new file mode 100644
index 0000000..d6de579
--- /dev/null
+++ b/frontend/src/components/contact/ContactSidebar.jsx
@@ -0,0 +1,183 @@
+import { useState, useEffect } from "react";
+import { Star, GitFork, Bug } from "lucide-react";
+
+function AvailabilityCard() {
+ return (
+
+
+ Availability
+
+
+
+
+
+
+
+ Open for feedback and project discussions.
+
+
+
+ );
+}
+
+function TimezoneCard() {
+ return (
+
+
+ Timezone
+
+
IST
+
UTC +5:30
+
+ );
+}
+
+const STATS_CACHE = new Map();
+const CACHE_TTL = 300_000;
+
+function GitHubStats() {
+ const [stats, setStats] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState("");
+
+ useEffect(() => {
+ let cancelled = false;
+
+ async function fetchStats() {
+ const repo = "kunalverma2512/CodeLens";
+
+ const cached = STATS_CACHE.get(repo);
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
+ if (!cancelled) {
+ setStats(cached.stats);
+ setLoading(false);
+ }
+ return;
+ }
+
+ try {
+ const res = await fetch(
+ `https://api.github.com/repos/${repo}`,
+ );
+
+ const rateLimitRemaining = res.headers.get("X-RateLimit-Remaining");
+
+ if (!res.ok) {
+ if (rateLimitRemaining === "0") {
+ throw new Error("Rate limit exceeded. Please try again later.");
+ }
+ throw new Error(`GitHub API responded with ${res.status}`);
+ }
+
+ const data = await res.json();
+
+ if (!cancelled) {
+ const statsData = {
+ stars: data.stargazers_count,
+ forks: data.forks_count,
+ openIssues: data.open_issues_count,
+ };
+ STATS_CACHE.set(repo, { stats: statsData, timestamp: Date.now() });
+ setStats(statsData);
+ setError("");
+ }
+ } catch (err) {
+ console.error("[GitHubStats]", err.message);
+ if (!cancelled) {
+ setError(err.message || "Unable to load stats");
+ }
+ } finally {
+ if (!cancelled) setLoading(false);
+ }
+ }
+
+ fetchStats();
+ return () => {
+ cancelled = true;
+ };
+ }, []);
+
+ if (error) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+ GitHub
+
+ {loading ? (
+
+ ) : (
+
+
+
+
+ {stats.stars.toLocaleString()}
+
+ stars
+
+
+
+
+ {stats.forks.toLocaleString()}
+
+ forks
+
+
+
+
+ {stats.openIssues.toLocaleString()}
+
+ open issues
+
+
+ )}
+
+ );
+}
+
+function BuilderCard() {
+ return (
+
+
+ Built by
+
+
+
+ KV
+
+
+
Kunal Verma
+
+ Built by developers, for developers.
+
+
+
+
+ );
+}
+
+export default function ContactSidebar() {
+ return (
+
+ );
+}
diff --git a/frontend/src/components/contact/ContactSuccessState.jsx b/frontend/src/components/contact/ContactSuccessState.jsx
new file mode 100644
index 0000000..14dfdfc
--- /dev/null
+++ b/frontend/src/components/contact/ContactSuccessState.jsx
@@ -0,0 +1,109 @@
+import { motion } from "framer-motion";
+import { useNavigate } from "react-router-dom";
+import { ArrowRight } from "lucide-react";
+
+const RESPONSE_TIME = {
+ General: "Within 24 hours",
+ "Bug Report": "Within 48 hours",
+ "Feature Request": "Within 1 week",
+ Partnership: "Within 72 hours",
+};
+
+export default function ContactSuccessState({ category, onReset }) {
+ const navigate = useNavigate();
+
+ return (
+
+
+
+
+
+
+
+
+ Message sent!
+
+
+ We've got your message and we're on it. You'll hear from us sooner than you think.
+
+
+
+
+
+ Category
+
+
{category}
+
+
+
+ Estimated response
+
+
+ {RESPONSE_TIME[category]}
+
+
+
+
+
+
navigate("/dashboard")}
+ className="inline-flex items-center gap-2 bg-neutral-900 px-5 py-2.5 text-sm font-medium text-white shadow-sm transition-all duration-200 hover:bg-neutral-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-900 focus-visible:ring-offset-2"
+ >
+ Explore Dashboard
+
+
+
+
+ Check GitHub Issues
+
+
+
+
+
+
+ Send Another Message
+
+
+
+ );
+}
diff --git a/frontend/src/pages/Contact.jsx b/frontend/src/pages/Contact.jsx
index b117f53..2f15ae0 100644
--- a/frontend/src/pages/Contact.jsx
+++ b/frontend/src/pages/Contact.jsx
@@ -1,114 +1,35 @@
-const Contact = () => {
- const handleSubmit = (e) => {
- e.preventDefault();
- alert("Message sent!");
- e.target.reset();
- };
+import ContactHero from "../components/contact/ContactHero";
+import ContactForm from "../components/contact/ContactForm";
+import ContactChannelCards from "../components/contact/ContactChannelCards";
+import ContactSidebar from "../components/contact/ContactSidebar";
+import ContactFAQ from "../components/contact/ContactFAQ";
- return (
-
- Contact- CodeLens
-
-
-
-
-
- Get in touch
-
-
- Let's build the future together
-
-
Have questions, partnership inquiries, or feedback? We'd love to hear from you.
-
+export default function Contact() {
+ return (
+
+ Contact — CodeLens
+
- {[
- {
- name: "GitHub",
- desc: "Star us & contribute",
- href: "https://github.com/",
- icon: (
-
-
-
- ),
- },
- {
- name: "X / Twitter",
- desc: "Follow for updates",
- href: "https://twitter.com/",
- icon: (
-
-
-
- ),
- },
- {
- name: "LinkedIn",
- desc: "Connect with the team",
- href: "https://linkedin.com/",
- icon: (
-
-
-
- ),
- },
- {
- name: "Discord",
- desc: "Join the community",
- href: "#",
- icon: (
-
-
-
- ),
- },
- ].map((s) => (
-
-
- {s.icon}
-
-
-
- {s.name}
-
-
- {s.desc}
-
-
-
- ))}
-
-
+
-
-
Send us a message
-
- Fill out the form below and we will get back to you soon
-
-
-
-
-
-
- )
-}
+
+
+
-export default Contact
+
+
+
+
+ );
+}