diff --git a/Backend/config/db.js b/Backend/config/db.js index b01dc4a..842c7a0 100644 --- a/Backend/config/db.js +++ b/Backend/config/db.js @@ -1,18 +1,25 @@ const mongoose = require("mongoose"); -require("dotenv").config(); const connectDB = async () => { try { - await mongoose.connect(process.env.MONGO_URI, { - useNewUrlParser: true, - useUnifiedTopology: true, + // We are adding a 'tls' option here. + // This is a common workaround for the 'tlsv1 alert internal error' + // that can happen on some networks or computers. + const conn = await mongoose.connect(process.env.MONGO_URI, { + tls: true, + tlsAllowInvalidCertificates: true, // Use this for development only }); - console.log("MongoDB Connected"); + + console.log(`MongoDB Connected: ${conn.connection.host}`); } catch (error) { - console.error(error); - process.exit(1); + console.error(`Error connecting to MongoDB: ${error.message}`); + process.exit(1); // Exit process with failure } }; +// The original file likely calls the function to connect immediately. +// We will keep that structure. connectDB(); -module.exports = mongoose; \ No newline at end of file + +// We don't strictly need to export, but it's good practice. +module.exports = connectDB; diff --git a/Backend/controllers/visitorController.js b/Backend/controllers/visitorController.js new file mode 100644 index 0000000..a46760e --- /dev/null +++ b/Backend/controllers/visitorController.js @@ -0,0 +1,16 @@ +const Visitor = require('../models/Visitor'); + +// Increments visitor count and returns it +exports.getAndIncrementVisitorCount = async (req, res) => { + try { + const stats = await Visitor.findOneAndUpdate( + { name: 'site_stats' }, // Filter to find the document + { $inc: { count: 1 } }, // Increment the 'count' field by 1 + { new: true, upsert: true } // Options: return the updated doc, and create it if it doesn't exist + ); + res.status(200).json({ count: stats.count }); + } catch (error) { + console.error('Error updating visitor count:', error); + res.status(500).json({ message: 'Server error' }); + } +}; \ No newline at end of file diff --git a/Backend/index.js b/Backend/index.js index a1f84e6..1fd7008 100644 --- a/Backend/index.js +++ b/Backend/index.js @@ -5,9 +5,12 @@ require("dotenv").config(); const bodyParser = require("body-parser"); require("./config/db"); +// 1. Import the new visitor routes +const visitorRoutes = require('./routes/visitorRoutes'); + const app = express(); -app.use(bodyParser.json({ limit: "50mb" })); // Increase JSON request size limit -app.use(bodyParser.urlencoded({ limit: "50mb", extended: true })); +app.use(bodyParser.json({ limit: "50mb" })); // Increase JSON request size limit +app.use(bodyParser.urlencoded({ limit: "50mb", extended: true })); app.use(cors()); app.use(express.json()); @@ -15,10 +18,15 @@ app.use(express.json()); app.use("/api", require("./routes/certificateRoutes")); app.use("/api/images", require("./routes/imageRoutes")); app.use("/auth", require("./routes/authRoutes")); + +// 2. Add the new visitor route to the application +app.use("/api/visitors", visitorRoutes); + //404 handler app.use((req, res, next) => { res.status(404).json({ message: "Not Found" }); }); + //Global error handler app.use((err,req,res,next)=>{ console.error(err.stack); @@ -26,4 +34,4 @@ app.use((err,req,res,next)=>{ }); const PORT = process.env.PORT || 5000; -app.listen(PORT, () => console.log(`Server running on port ${PORT}`)); \ No newline at end of file +app.listen(PORT, () => console.log(`Server running on port ${PORT}`)); diff --git a/Backend/models/Visitor.js b/Backend/models/Visitor.js new file mode 100644 index 0000000..0d35dc2 --- /dev/null +++ b/Backend/models/Visitor.js @@ -0,0 +1,15 @@ +const mongoose = require('mongoose'); + +const VisitorSchema = new mongoose.Schema({ + name: { + type: String, + required: true, + default: 'site_stats' + }, + count: { + type: Number, + default: 0 + } +}); + +module.exports = mongoose.model('Visitor', VisitorSchema); \ No newline at end of file diff --git a/Backend/package-lock.json b/Backend/package-lock.json index 599194c..42a4546 100644 --- a/Backend/package-lock.json +++ b/Backend/package-lock.json @@ -40,18 +40,18 @@ } }, "node_modules/@mongodb-js/saslprep": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.2.0.tgz", - "integrity": "sha512-+ywrb0AqkfaYuhHs6LxKWgqbh3I72EpEgESCw37o+9qPx9WTCkgDm2B+eMrwehGtHBWHFU4GXvnSCNiFhhausg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz", + "integrity": "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==", "license": "MIT", "dependencies": { "sparse-bitfield": "^3.0.3" } }, "node_modules/@react-oauth/google": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/@react-oauth/google/-/google-0.12.1.tgz", - "integrity": "sha512-qagsy22t+7UdkYAiT5ZhfM4StXi9PPNvw0zuwNmabrWyMKddczMtBIOARflbaIj+wHiQjnMAsZmzsUYuXeyoSg==", + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@react-oauth/google/-/google-0.12.2.tgz", + "integrity": "sha512-d1GVm2uD4E44EJft2RbKtp8Z1fp/gK8Lb6KHgs3pHlM0PxCXGLaq8LLYQYENnN4xPWO1gkL4apBtlPKzpLvZwg==", "license": "MIT", "peerDependencies": { "react": ">=16.8.0", @@ -208,9 +208,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -230,9 +230,9 @@ } }, "node_modules/bson": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.3.tgz", - "integrity": "sha512-MTxGsqgYTwfshYWTRdmZRC+M7FnG1b4y7RO7p2k3X24Wq0yv1m77Wsj0BzlPzd/IowgESfsruQCUToa7vbOpPQ==", + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", "license": "Apache-2.0", "engines": { "node": ">=16.20.1" @@ -481,9 +481,9 @@ "license": "MIT" }, "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -1154,13 +1154,13 @@ } }, "node_modules/mongodb": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.14.2.tgz", - "integrity": "sha512-kMEHNo0F3P6QKDq17zcDuPeaywK/YaJVCEQRzPF3TOM/Bl9MFg64YE5Tu7ifj37qZJMhwU1tl2Ioivws5gRG5Q==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.18.0.tgz", + "integrity": "sha512-fO5ttN9VC8P0F5fqtQmclAkgXZxbIkYRTUi1j8JO6IYwvamkhtYDilJr35jOPELR49zqCJgXZWwCtW7B+TM8vQ==", "license": "Apache-2.0", "dependencies": { "@mongodb-js/saslprep": "^1.1.9", - "bson": "^6.10.3", + "bson": "^6.10.4", "mongodb-connection-string-url": "^3.0.0" }, "engines": { @@ -1210,14 +1210,14 @@ } }, "node_modules/mongoose": { - "version": "8.12.2", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.12.2.tgz", - "integrity": "sha512-tfCyekj0CK4Hw41u78dHCN7fimvvky0oH4QOiHxzcHtjNLosljZVjwlOqFAolIn/sROFLYGheGafqd3h/BzaFA==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.17.0.tgz", + "integrity": "sha512-mxW6TBPHViORfNYOFXCVOnT4d5aRr+CgDxTs1ViYXfuHzNpkelgJQrQa+Lz6hofoEQISnKlXv1L3ZnHyJRkhfA==", "license": "MIT", "dependencies": { - "bson": "^6.10.3", + "bson": "^6.10.4", "kareem": "2.6.3", - "mongodb": "~6.14.0", + "mongodb": "~6.18.0", "mpath": "0.9.0", "mquery": "5.0.0", "ms": "2.1.3", @@ -1259,9 +1259,9 @@ } }, "node_modules/mquery/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1291,6 +1291,7 @@ "version": "1.4.5-lts.2", "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", + "deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.", "license": "MIT", "dependencies": { "append-field": "^1.0.0", @@ -1315,18 +1316,18 @@ } }, "node_modules/nodemailer": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.0.tgz", - "integrity": "sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA==", + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.1.tgz", + "integrity": "sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==", "license": "MIT-0", "engines": { "node": ">=6.0.0" } }, "node_modules/nodemon": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", - "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", "license": "MIT", "dependencies": { "chokidar": "^3.5.2", @@ -1352,9 +1353,9 @@ } }, "node_modules/nodemon/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1587,6 +1588,29 @@ "node": ">= 0.8" } }, + "node_modules/react": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", + "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", + "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.1" + } + }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -1661,10 +1685,17 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT", + "peer": true + }, "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -1939,9 +1970,9 @@ } }, "node_modules/tr46": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.0.tgz", - "integrity": "sha512-IUWnUK7ADYR5Sl1fZlO1INDUhVhatWl7BtJWsIhwJ0UAK7ilzzIa8uIqOO/aYVWHZPJkKbEL+362wrzoeRF7bw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", "license": "MIT", "dependencies": { "punycode": "^2.3.1" diff --git a/Backend/routes/visitorRoutes.js b/Backend/routes/visitorRoutes.js new file mode 100644 index 0000000..c85fdad --- /dev/null +++ b/Backend/routes/visitorRoutes.js @@ -0,0 +1,8 @@ +const express = require('express'); +const router = express.Router(); +const { getAndIncrementVisitorCount } = require('../controllers/visitorController'); + +// GET /api/visitors/count +router.get('/count', getAndIncrementVisitorCount); + +module.exports = router; \ No newline at end of file diff --git a/Frontend/src/components/Footer.jsx b/Frontend/src/components/Footer.jsx index 824a859..ab690b5 100644 --- a/Frontend/src/components/Footer.jsx +++ b/Frontend/src/components/Footer.jsx @@ -1,14 +1,39 @@ import { motion, useInView } from "framer-motion"; import { FaGithub, FaCertificate } from "react-icons/fa"; -import { useRef, useState } from "react"; +// 1. Import useEffect to fetch data +import { useRef, useState, useEffect } from "react"; const Footer = () => { const ref = useRef(null); const isInView = useInView(ref, { once: true, amount: 0.3 }); const [hoveredLink, setHoveredLink] = useState(null); + // 2. Add state to store the visitor count + const [visitorCount, setVisitorCount] = useState(null); + + // 3. Use useEffect to fetch the visitor count when the component mounts + useEffect(() => { + const fetchVisitorCount = async () => { + try { + // This endpoint proxies to your backend via the vite.config.js setup + const response = await fetch('/api/visitors/count'); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + setVisitorCount(data.count); + } catch (error) { + console.error("Failed to fetch visitor count:", error); + // Set a default or error state if needed + setVisitorCount('N/A'); + } + }; + + fetchVisitorCount(); + }, []); // The empty dependency array [] ensures this effect runs only once + return ( - { { ))} - { initial={{ scale: 0.95, opacity: 0 }} animate={isInView ? { scale: 1, opacity: 1 } : {}} transition={{ duration: 0.6, delay: 0.2 }} - whileHover={{ + whileHover={{ boxShadow: '0 25px 50px -12px rgba(139, 92, 246, 0.25)', borderColor: 'rgba(180,120,255,0.3)' }} @@ -79,15 +104,15 @@ const Footer = () => { {/* Compact Grid Layout */}
{/* Left Section - Brand */} - - { > HonorBox - - Open Source + Open Source { {/* Center Section - Navigation Links */} - { { > About - { { > Terms - { target="_blank" rel="noopener noreferrer" className="footer-link flex items-center gap-1 text-white/80 hover:text-pink-400 transition text-sm font-medium" - whileHover={{ + whileHover={{ scale: 1.1, color: '#f472b6', textShadow: '0 0 15px rgba(244, 114, 182, 0.5)' @@ -186,45 +211,56 @@ const Footer = () => { - {/* Right Section - Copyright */} - - © {new Date().getFullYear()} HonorBox - - Made with + Made with - ❤️ + {' '}❤️ {' '}by Ram + + {/* 4. Display the visitor count here */} + + Visitors: {visitorCount !== null ? visitorCount : '...'} + +
@@ -259,4 +295,4 @@ const Footer = () => { ); }; -export default Footer; \ No newline at end of file +export default Footer; diff --git a/Frontend/vite.config.js b/Frontend/vite.config.js index c2dea19..51e53f2 100644 --- a/Frontend/vite.config.js +++ b/Frontend/vite.config.js @@ -1,6 +1,18 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' -// https://vite.dev/config/ + +// https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], -}) + // Add the server configuration here + server: { + proxy: { + // This will proxy any request starting with /api to your backend server + '/api': { + target: 'http://localhost:5000', // Your backend server address + changeOrigin: true, // Recommended for virtual hosted sites + secure: false, // Can be set to false if your backend is not using HTTPS + }, + }, + }, +}) \ No newline at end of file