From b02384f3cbccd186ae480812415944489b99df8b Mon Sep 17 00:00:00 2001 From: Adeeba273 Date: Wed, 10 Sep 2025 20:27:16 +0530 Subject: [PATCH] Added feedback popup with rating and comment form --- backend/models/feedback.js | 13 ++ backend/routes/feedback.route.js | 30 +++ backend/server.js | 98 ++++----- frontend/src/Components/Navbar/Navbar.jsx | 13 +- frontend/src/Components/ui/FeedbackModal.jsx | 204 +++++++++++++++++++ 5 files changed, 304 insertions(+), 54 deletions(-) create mode 100644 backend/models/feedback.js create mode 100644 backend/routes/feedback.route.js create mode 100644 frontend/src/Components/ui/FeedbackModal.jsx diff --git a/backend/models/feedback.js b/backend/models/feedback.js new file mode 100644 index 0000000..ae8b18b --- /dev/null +++ b/backend/models/feedback.js @@ -0,0 +1,13 @@ +import mongoose from "mongoose"; + +const feedbackSchema = new mongoose.Schema({ + userId: { type: String, default: null }, // null if anonymous + rating: { type: Number, required: true, min: 1, max: 5 }, + comment: { type: String, required: true }, + category: { type: String, default: "General" }, + date: { type: Date, default: Date.now }, +}); + +const Feedback = mongoose.model("Feedback", feedbackSchema); + +export default Feedback; diff --git a/backend/routes/feedback.route.js b/backend/routes/feedback.route.js new file mode 100644 index 0000000..4014d6d --- /dev/null +++ b/backend/routes/feedback.route.js @@ -0,0 +1,30 @@ +import express from "express"; +import Feedback from "../models/feedback.js"; + +const router = express.Router(); + +// POST /api/feedback +router.post("/", async (req, res) => { + try { + const { userId, rating, comment, category } = req.body; + + if (!rating || !comment) { + return res.status(400).json({ error: "Rating and comment required" }); + } + + const feedback = await Feedback.create({ + userId, + rating, + comment, + category, + date: new Date() + }); + + res.status(201).json({ message: "Feedback saved", feedback }); + } catch (err) { + console.error("Feedback error:", err); + res.status(500).json({ error: "Server error" }); + } +}); + +export default router; diff --git a/backend/server.js b/backend/server.js index d4938dc..aad270b 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,66 +1,70 @@ -// Entry point of the backend server -require("dotenv").config(); -const dbconnection = require("./db/connection"); -const express = require("express"); -const mongoose = require("mongoose"); -const cors = require("cors"); -const path = require("path"); -const contactRouter = require("./routes/contact.route"); -const passport = require("passport"); // import actual passport -require("./config/passport"); // just execute the strategy config -const session = require("express-session"); - - -// Importing Rate Limiter Middlewares - -const { generalMiddleware, authMiddleware } = require("./middleware/rateLimit/index") - - - -// Initialize express +// backend/server.js +import express from "express"; +import mongoose from "mongoose"; +import cors from "cors"; +import dotenv from "dotenv"; +import path from "path"; +import session from "express-session"; +import passport from "passport"; +import { fileURLToPath } from "url"; + +import feedbackRoutes from "./routes/feedback.route.js"; +import contactRouter from "./routes/contact.route.js"; +import profileRoutes from "./routes/profile.js"; +// import authRoutes from "./routes/auth.js"; // enable when ready + +import { generalMiddleware, authMiddleware } from "./middleware/rateLimit/index.js"; +import "./config/passport.js"; // configure passport strategies + +dotenv.config(); const app = express(); +// Middleware app.use(express.json()); -app.use(cors({ - origin: process.env.CLIENT_URL || "http://localhost:5173", // frontend URL for local dev - credentials: true -})); - - +app.use( + cors({ + origin: process.env.CLIENT_URL || "http://localhost:5173", // frontend URL + credentials: true, + }) +); app.use( - session({ - secret: process.env.SESSION_SECRET || "devsync_session_secret", - resave: false, - saveUninitialized: false, - cookie: { secure: false } // set true if using HTTPS - }) + session({ + secret: process.env.SESSION_SECRET || "devsync_session_secret", + resave: false, + saveUninitialized: false, + cookie: { secure: false }, // set true if using HTTPS + }) ); app.use(passport.initialize()); app.use(passport.session()); -// Serve uploaded files -app.use("/uploads", express.static(path.join(__dirname, "uploads"))); - -// Define routes - -// app.use("/api/auth", require("./routes/auth")); -app.use("/api/auth", authMiddleware, require("./routes/auth")); +// Fix __dirname in ES modules +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); -// app.use("/api/profile", require("./routes/profile")); -app.use("/api/profile", generalMiddleware, require("./routes/profile")); +// Serve static uploads +app.use("/uploads", express.static(path.join(__dirname, "uploads"))); -// app.use("/api/contact",contactRouter); +// Routes +app.use("/api/feedback", feedbackRoutes); +// app.use("/api/auth", authMiddleware, authRoutes); // enable when ready +app.use("/api/profile", generalMiddleware, profileRoutes); app.use("/api/contact", generalMiddleware, contactRouter); - -// Route to display the initial message on browser +// Root route app.get("/", (req, res) => { - res.send("DEVSYNC BACKEND API"); + res.send("DEVSYNC BACKEND API"); }); +// Connect DB and start server +mongoose + .connect(process.env.MONGO_URI) + .then(() => console.log("✅ MongoDB connected")) + .catch((err) => console.error("❌ DB error:", err)); + const PORT = process.env.PORT || 5000; app.listen(PORT, () => { - console.log(`Server is up and running at http://localhost:${PORT} 🚀`); -}); \ No newline at end of file + console.log(`🚀 Server running at http://localhost:${PORT}`); +}); diff --git a/frontend/src/Components/Navbar/Navbar.jsx b/frontend/src/Components/Navbar/Navbar.jsx index fb6221b..78c3f32 100644 --- a/frontend/src/Components/Navbar/Navbar.jsx +++ b/frontend/src/Components/Navbar/Navbar.jsx @@ -1,8 +1,8 @@ import React, { useEffect, useState } from "react"; -import { Github, Home, Info, Sparkle, LogIn, UserPlus, UserCircle } from "lucide-react"; +import { Github, Home, Info, Sparkle, LogIn, UserPlus, UserCircle, Phone } from "lucide-react"; import { FloatingNav } from "../ui/floating-navbar"; -import { Phone } from "lucide-react"; import { Link } from "react-router-dom"; +import FeedbackModal from "../ui/FeedbackModal"; const navItems = [ { @@ -44,21 +44,20 @@ const Navbar = () => { return () => window.removeEventListener("scroll", handleScroll); }, []); - const isAuthenticated = localStorage.getItem('token') !== null; + const isAuthenticated = localStorage.getItem("token") !== null; + const userId = localStorage.getItem("userId"); return (
{!showFloating && (
- {/* Logo */}

DevSync

- {/* Desktop Navigation */}
- {/* Mobile Menu Button */}
- {/* Mobile Navigation */} {menuOpen && (
{navItems.map((item) => ( @@ -158,6 +155,8 @@ const Navbar = () => { )} {showFloating && } + +
); }; diff --git a/frontend/src/Components/ui/FeedbackModal.jsx b/frontend/src/Components/ui/FeedbackModal.jsx new file mode 100644 index 0000000..6fa03e4 --- /dev/null +++ b/frontend/src/Components/ui/FeedbackModal.jsx @@ -0,0 +1,204 @@ +import React, { useEffect, useState } from "react"; +import { motion } from "framer-motion"; // npm i framer-motion + +const STORAGE_KEY = "devsync-feedback-lastshown"; + +export default function FeedbackModal({ userId = null, justLoggedIn = false, daysInterval = 5 }) { + const [open, setOpen] = useState(false); + const [rating, setRating] = useState(0); + const [comment, setComment] = useState(""); + const [category, setCategory] = useState(""); + const [allowAnonymous, setAllowAnonymous] = useState(false); + const [submitting, setSubmitting] = useState(false); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(null); + + useEffect(() => { + try { + const raw = localStorage.getItem(STORAGE_KEY); + const last = raw ? new Date(raw) : null; + const now = new Date(); + const msInterval = daysInterval * 24 * 60 * 60 * 1000; + + if (justLoggedIn) { + if (!last || now.getTime() - last.getTime() > 60 * 1000) { + setTimeout(() => setOpen(true), 800); + } + return; + } + + if (!last || now.getTime() - last.getTime() >= msInterval) { + const t = setTimeout(() => setOpen(true), 2500); + return () => clearTimeout(t); + } + } catch (e) { + console.warn("FeedbackModal: localStorage issue", e); + } + }, [justLoggedIn, daysInterval]); + + const handleClose = () => { + setOpen(false); + try { + localStorage.setItem(STORAGE_KEY, new Date().toISOString()); + } catch {} + }; + + const validate = () => { + if (rating < 1 || rating > 5) { + setError("Please give a rating between 1 and 5 stars."); + return false; + } + if (!comment || comment.trim().length < 5) { + setError("Comment should be at least 5 characters."); + return false; + } + setError(null); + return true; + }; + + const submit = async () => { + if (!validate()) return; + setSubmitting(true); + setError(null); + setSuccess(null); + + const payload = { + userId: allowAnonymous ? null : userId, + rating, + comment: comment.trim(), + category: category || null, + date: new Date().toISOString(), + }; + + try { + const res = await fetch("/api/feedback/submit", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload), + }); + + if (!res.ok) throw new Error("Failed to submit"); + + setSuccess("Thanks! Your feedback was recorded."); + setTimeout(() => handleClose(), 900); + } catch (err) { + setError(err.message || "Error submitting feedback"); + } finally { + setSubmitting(false); + } + }; + + const Star = ({ filled, onClick, index }) => ( + + ); + + if (!open) return null; + + return ( +
+ + +
+

Share your feedback

+ +
+ +
+
+ +
+ {[0, 1, 2, 3, 4].map((i) => ( + setRating(i + 1)} /> + ))} + {rating ? `${rating}/5` : "No rating"} +
+
+ +
+ +