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..0c44e3b 100644 --- a/backend/server.js +++ b/backend/server.js @@ -61,6 +61,13 @@ app.get("/", (req, res) => { }); 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 +dbconnection() + .then(() => { + app.listen(PORT, () => { + console.log(`Server is up and running at http://localhost:${PORT} 🚀`); + }); + }) + .catch((err) => { + console.error("❌ Failed to connect to MongoDB:", err.message); + process.exit(1); // stop server if DB fails + }); \ No newline at end of file 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"} +
+
+ +
+ +