From 9e0e514ff0df1ec1dbc5d6b62ff8b555191216f5 Mon Sep 17 00:00:00 2001 From: abhi75 Date: Sun, 27 Jul 2025 12:39:11 +0530 Subject: [PATCH 01/12] contact us form --- package-lock.json | 91 +++++++ package.json | 2 + src/components/ContactPage.jsx | 423 +++++++++++++++++++++++++-------- 3 files changed, 420 insertions(+), 96 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0a1e5dd..055a45e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,9 @@ "name": "expense-tracker", "version": "0.0.0", "dependencies": { + "@emailjs/browser": "^4.4.1", "@react-pdf/renderer": "^4.3.0", + "@splinetool/react-spline": "^4.1.0", "@tailwindcss/vite": "^4.1.11", "lucide-react": "^0.525.0", "react": "^18.2.0", @@ -335,6 +337,15 @@ "node": ">=6.9.0" } }, + "node_modules/@emailjs/browser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@emailjs/browser/-/browser-4.4.1.tgz", + "integrity": "sha512-DGSlP9sPvyFba3to2A50kDtZ+pXVp/0rhmqs2LmbMS3I5J8FSOgLwzY2Xb4qfKlOVHh29EAutLYwe5yuEZmEFg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.8", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", @@ -1453,6 +1464,38 @@ "win32" ] }, + "node_modules/@splinetool/react-spline": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@splinetool/react-spline/-/react-spline-4.1.0.tgz", + "integrity": "sha512-Y379gm17gw+1nxT/YXTCJnVIWuu7tsUH1tp/YxsYb0pZnc9Gljk7Om4Kpq7WPq0bZ4zidVCxf6xn6jgDcbHifQ==", + "dependencies": { + "blurhash": "2.0.5", + "lodash.debounce": "4.0.8", + "react-merge-refs": "2.1.1", + "thumbhash": "0.1.1" + }, + "peerDependencies": { + "@splinetool/runtime": "*", + "next": ">=14.2.0", + "react": "*", + "react-dom": "*" + }, + "peerDependenciesMeta": { + "next": { + "optional": true + } + } + }, + "node_modules/@splinetool/runtime": { + "version": "1.10.37", + "resolved": "https://registry.npmjs.org/@splinetool/runtime/-/runtime-1.10.37.tgz", + "integrity": "sha512-7KpSPuVYOQI4n1QGW3Z+hD4ADBiQsBfapJSnVAagaXhTSFpD/RuZezy18z8hQnEkHt8G+LoE5ZuhBTUkOYoojA==", + "peer": true, + "dependencies": { + "on-change": "^4.0.0", + "semver-compare": "^1.0.0" + } + }, "node_modules/@swc/helpers": { "version": "0.5.17", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", @@ -1928,6 +1971,12 @@ "require-from-string": "^2.0.2" } }, + "node_modules/blurhash": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/blurhash/-/blurhash-2.0.5.tgz", + "integrity": "sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -3112,6 +3161,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -3271,6 +3326,19 @@ "node": ">=0.10.0" } }, + "node_modules/on-change": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/on-change/-/on-change-4.0.2.tgz", + "integrity": "sha512-cMtCyuJmTx/bg2HCpHo3ZLeF7FZnBOapLqZHr2AlLeJ5Ul0Zu2mUJJz051Fdwu/Et2YW04ZD+TtU+gVy0ACNCA==", + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/on-change?sponsor=1" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3524,6 +3592,16 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/react-merge-refs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/react-merge-refs/-/react-merge-refs-2.1.1.tgz", + "integrity": "sha512-jLQXJ/URln51zskhgppGJ2ub7b2WFKGq3cl3NYKtlHoTG+dN2q7EzWrn3hN3EgPsTMvpR9tpq5ijdp7YwFZkag==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -3672,6 +3750,13 @@ "semver": "bin/semver.js" } }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "license": "MIT", + "peer": true + }, "node_modules/set-cookie-parser": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", @@ -3824,6 +3909,12 @@ "node": ">=18" } }, + "node_modules/thumbhash": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/thumbhash/-/thumbhash-0.1.1.tgz", + "integrity": "sha512-kH5pKeIIBPQXAOni2AiY/Cu/NKdkFREdpH+TLdM0g6WA7RriCv0kPLgP731ady67MhTAqrVG/4mnEeibVuCJcg==", + "license": "MIT" + }, "node_modules/tiny-inflate": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", diff --git a/package.json b/package.json index 38a78f6..95d929d 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,9 @@ "preview": "vite preview" }, "dependencies": { + "@emailjs/browser": "^4.4.1", "@react-pdf/renderer": "^4.3.0", + "@splinetool/react-spline": "^4.1.0", "@tailwindcss/vite": "^4.1.11", "lucide-react": "^0.525.0", "react": "^18.2.0", diff --git a/src/components/ContactPage.jsx b/src/components/ContactPage.jsx index 9a5a56f..368cf08 100644 --- a/src/components/ContactPage.jsx +++ b/src/components/ContactPage.jsx @@ -1,103 +1,334 @@ import React, { useState, useEffect } from 'react'; -import { Link } from 'react-router-dom'; // Placeholder, navigation is handled by props -import { Send, User, Mail } from 'lucide-react'; -import Footer from './Footer'; - -const ContactPage = ({ darkMode, navigateTo }) => { - // State to control the animations - const [isVisible, setIsVisible] = useState(false); - - // useEffect to trigger the animation on component mount - useEffect(() => { - const timer = setTimeout(() => setIsVisible(true), 100); - return () => clearTimeout(timer); - }, []); - - return ( - // Main container with dynamic background -
-
-
- - {/* Animated content card */} -
- - {/* Animated Title */} -

- Get In Touch -

+import emailjs from 'https://esm.sh/@emailjs/browser'; +import { Send, User, Mail, MessageSquare, BookOpen, CheckCircle, XCircle, MapPin, Phone, Linkedin, Twitter, Github } from 'lucide-react'; + +// Footer component +const Footer = ({ navigateTo }) => { + // A simple footer, can be expanded. + return ( +
+

© 2024 Your Company. All rights reserved.

+
+ ); +}; + + +// Main App component which includes the ContactPage logic +export default function App() { + // State for managing dark mode. Default is false (light mode). + const [darkMode, setDarkMode] = useState(false); + + // This is a placeholder for navigation logic. + const navigateTo = (path) => { + console.log(`Navigating to ${path}`); + // In a real multi-page app, you'd use a router here. + }; + + // The main component structure. + return ( + + ); +} + + +// The Contact Page component +const ContactPage = ({ darkMode, navigateTo, setDarkMode }) => { + // State for controlling the visibility and animation of the main card. + const [isVisible, setIsVisible] = useState(false); + // State for form data. + const [formData, setFormData] = useState({ + name: '', + email: '', + subject: '', + message: '', + }); + // State for form validation errors. + const [errors, setErrors] = useState({}); + // State to track form submission process. + const [isSubmitting, setIsSubmitting] = useState(false); + // State for the result of the submission ('success' or 'error'). + const [submissionStatus, setSubmissionStatus] = useState(null); + // State to control the visibility of the toast notification. + const [showToast, setShowToast] = useState(false); + + // Effect for triggering the main card animation on component mount. + useEffect(() => { + const timer = setTimeout(() => setIsVisible(true), 100); + return () => clearTimeout(timer); + }, []); + + // Effect for automatically hiding the toast notification after 5 seconds. + useEffect(() => { + if (showToast) { + const toastTimer = setTimeout(() => { + setShowToast(false); + setSubmissionStatus(null); + }, 5000); + return () => clearTimeout(toastTimer); + } + }, [showToast]); + + // Handles changes in form input fields. + const handleChange = (e) => { + const { name, value } = e.target; + setFormData((prevData) => ({ + ...prevData, + [name]: value, + })); + // Clear the error for the field being edited. + setErrors((prevErrors) => ({ + ...prevErrors, + [name]: null, + })); + }; + + // Validates the form fields before submission. + const validateForm = () => { + let newErrors = {}; + if (!formData.name.trim()) newErrors.name = 'Name is required'; + if (!formData.email.trim()) { + newErrors.email = 'Email is required'; + } else if (!/\S+@\S+\.\S+/.test(formData.email)) { + newErrors.email = 'Email address is invalid'; + } + if (!formData.message.trim()) newErrors.message = 'Message is required'; + + setErrors(newErrors); + // Returns true if there are no errors. + return Object.keys(newErrors).length === 0; + }; + + // Handles the form submission with your EmailJS keys. + const handleSubmit = (e) => { + e.preventDefault(); + setSubmissionStatus(null); + setShowToast(false); + + if (!validateForm()) { + return; + } + + setIsSubmitting(true); + + // Your specific EmailJS details are now included here. + const serviceID = 'service_sc9xokh'; + const templateID = 'template_22c3a2a'; + const publicKey = 'bcz_108RDETTP_XP4'; + + emailjs.send(serviceID, templateID, formData, publicKey) + .then((response) => { + console.log('SUCCESS!', response.status, response.text); + setSubmissionStatus('success'); + setFormData({ name: '', email: '', subject: '', message: '' }); // Clear form + }) + .catch((err) => { + console.error('FAILED...', err); + setSubmissionStatus('error'); + }) + .finally(() => { + setIsSubmitting(false); + setShowToast(true); + }); + }; + + return ( +
+ {/* Spline Background using a reliable iframe embed */} +
+ +
- {/* Animated Description */} -

- Have a question or feedback? We'd love to hear from you. -

- - {/* Clickable Mailto Link, styled as a button */} -
- - - Send us an Email - + {/* Dark Mode Toggle */} +
+
-
- - {/* Developer Details */} -
-

- This project is maintained by: -

-
- - Tanmay Kalra -
-
+ {/* Main Content */} +
+
+
+ {/* Left Section: Contact Information */} +
+
+

Contact Us

+

Feel free to reach out to us through any of these channels.

+ +
+
+ +

123 Main Street, Anytown, USA

+
+
+ +

info@smartlog.com

+
+
+ +

+1 (555) 123-4567

+
+
+
+ + {/* Social Media Icons */} +
+ + + +
+
+ + {/* Right Section: Get In Touch Form */} +
+

+ Get In Touch +

+ +

+ Have a question or feedback? We'd love to hear from you. +

+ +
+ {/* Form fields with animations */} +
+
+ +
+ + {errors.name &&

{errors.name}

} +
-
+
+
+ +
+ + {errors.email &&

{errors.email}

} +
+ +
+
+ +
+ +
+ +
+
+ +
+ + {errors.message &&

{errors.message}

} +
+ +
+ +
+ + +
+

+ This project is maintained by: +

+
+ + Tanmay Kalra +
+
+
+
+
+
+ + {/* Confirmation Toast Notification */} + {showToast && ( +
+ {submissionStatus === 'success' ? : } + {submissionStatus === 'success' ? 'Message sent successfully!' : 'Failed to send message.'} +
+ )} + +
- - -