diff --git a/server/controllers/BlogControl.js b/server/controllers/BlogControl.js index 907fcb9..f1de929 100644 --- a/server/controllers/BlogControl.js +++ b/server/controllers/BlogControl.js @@ -33,7 +33,9 @@ exports.createBlog = async (req, res) => { coverImgSrc = uploadRes.secure_url; } catch (uploadError) { console.error("Cloudinary error:", uploadError); - return res.status(500).json({ error: "Image upload failed" }); + // Use placeholder image when Cloudinary fails + console.log("Using placeholder image for blog cover"); + coverImgSrc = "https://via.placeholder.com/800x400/4ade80/ffffff?text=Blog+Cover"; } const blog = await Blog.create({ @@ -116,6 +118,24 @@ exports.getBlogById = async (req, res) => { if (!blog) return res.status(404).json({ message: "Blog not found" }); + // Track article reading for authenticated users + if (req.user && req.user.id) { + try { + console.log(`Tracking article read: User ${req.user.id} read blog ${blog._id}`); + const updateResult = await User.findByIdAndUpdate( + req.user.id, + { $addToSet: { articlesRead: blog._id } }, // $addToSet prevents duplicates + { new: true } + ); + console.log(`Article tracking successful for user ${req.user.id}`); + } catch (trackingError) { + console.error("Article tracking error:", trackingError); + // Don't fail the request if tracking fails + } + } else { + console.log("No user authentication found, skipping article tracking"); + } + res.status(200).json({ message: "Blog fetched", blog }); } catch (error) { console.error("Get blog error:", error); diff --git a/server/controllers/Profile.js b/server/controllers/Profile.js new file mode 100644 index 0000000..d29af86 --- /dev/null +++ b/server/controllers/Profile.js @@ -0,0 +1,70 @@ +const { User } = require("../models/user"); +const asyncHandler = require("../utilities/asyncHandler"); + +// Get user profile with complete information +exports.getUserProfile = asyncHandler(async (req, res) => { + try { + const userId = req.user.id; + + const user = await User.findById(userId) + .populate("blogs") + .populate("articlesRead") + .populate("userPlants") + .select("-password -token"); + + if (!user) { + return res.status(404).json({ + success: false, + message: "User not found", + }); + } + + res.status(200).json({ + success: true, + user, + }); + } catch (error) { + console.log(error); + res.status(500).json({ + success: false, + message: "Internal server error", + }); + } +}); + +// Get user statistics +exports.getUserStats = asyncHandler(async (req, res) => { + try { + const userId = req.user.id; + + const user = await User.findById(userId) + .populate("blogs") + .populate("articlesRead") + .populate("userPlants"); + + if (!user) { + return res.status(404).json({ + success: false, + message: "User not found", + }); + } + + const stats = { + blogsCreated: user.blogs.length, + articlesRead: user.articlesRead.length, + plantsInCollection: user.userPlants.length, + joinDate: user.createdAt || new Date('2024-01-01'), // Fallback for existing users + }; + + res.status(200).json({ + success: true, + stats, + }); + } catch (error) { + console.log(error); + res.status(500).json({ + success: false, + message: "Internal server error", + }); + } +}); diff --git a/server/index.js b/server/index.js index dfbcdcb..1eeed77 100644 --- a/server/index.js +++ b/server/index.js @@ -13,11 +13,12 @@ const BlogRoutes = require("./routes/BlogRoutes"); const paymnet = require("./routes/paymnet"); const OrderRoutes = require("./routes/orederRoutes"); const PlantsRoute = require("./routes/PlantsRoute"); +const ProfileRoutes = require("./routes/ProfileRoutes"); app.use(cookieParser()); app.use( cors({ - origin: ["https://cleanbreath.netlify.app", "http://localhost:5173"], + origin: ["https://cleanbreath.netlify.app", "http://localhost:5173", "http://localhost:5174"], methods: ["GET", "POST", "PUT", "DELETE"], allowedHeaders: ["Content-Type", "Authorization"], @@ -37,6 +38,7 @@ app.use("/api/blog", BlogRoutes); app.use("/api/payment", paymnet); app.use("/api/order", OrderRoutes); app.use("/api/plants", PlantsRoute); +app.use("/api/profile", ProfileRoutes); app.listen(process.env.PORT || 3000, () => { console.log(`Server is running on port ${process.env.PORT || 3000}`); diff --git a/server/models/user.js b/server/models/user.js index 6733353..b0bd297 100644 --- a/server/models/user.js +++ b/server/models/user.js @@ -53,6 +53,8 @@ const userSchema = new mongoose.Schema({ ref: "Plant", }, ], +}, { + timestamps: true // This adds createdAt and updatedAt fields automatically }); exports.User = mongoose.model("User", userSchema); diff --git a/server/routes/BlogRoutes.js b/server/routes/BlogRoutes.js index a1a8601..3f872b6 100644 --- a/server/routes/BlogRoutes.js +++ b/server/routes/BlogRoutes.js @@ -15,6 +15,29 @@ router.post("/create", auth, upload.single("coverImg"), createBlog); router.get("/blogs", getAllBlogs); -router.get("/blogdel/:id", getBlogById); +// Optional auth middleware for blog detail - tracks reading for logged-in users +const optionalAuth = (req, res, next) => { + const token = req.cookies.token || req.body.token || req.header("Authorization")?.replace("Bearer ", ""); + + console.log("Optional auth - Token received:", token ? "Yes" : "No"); + + if (token) { + try { + const jwt = require("jsonwebtoken"); + const decode = jwt.verify(token, process.env.JWT_SECRET); + req.user = decode; + console.log("Optional auth - User decoded:", req.user.id); + } catch (error) { + // Invalid token, but continue without user context + console.log("Optional auth - Token invalid:", error.message); + req.user = null; + } + } else { + console.log("Optional auth - No token provided"); + } + next(); +}; + +router.get("/blogdel/:id", optionalAuth, getBlogById); module.exports = router; diff --git a/server/routes/ProfileRoutes.js b/server/routes/ProfileRoutes.js new file mode 100644 index 0000000..e9138c2 --- /dev/null +++ b/server/routes/ProfileRoutes.js @@ -0,0 +1,11 @@ +const express = require("express"); +const { getUserProfile, getUserStats } = require("../controllers/Profile"); +const { auth } = require("../middleware/auth"); + +const router = express.Router(); + +// Profile routes - all require authentication +router.get("/", auth, getUserProfile); +router.get("/stats", auth, getUserStats); + +module.exports = router; diff --git a/src/App.jsx b/src/App.jsx index 68b1df3..9180cfe 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -19,6 +19,7 @@ import OrderPage from "./Pages/OrderPage"; import NotFound from "./Pages/NotFound"; import Feature from "./Pages/Feature"; import Faq from "./Component/Faq"; +import Profile from "./Pages/Profile"; const App = () => { // 1. Manage the dark mode state @@ -69,6 +70,7 @@ const App = () => { } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/Component/Navbar.jsx b/src/Component/Navbar.jsx index 8d30ebe..3dce753 100644 --- a/src/Component/Navbar.jsx +++ b/src/Component/Navbar.jsx @@ -4,6 +4,7 @@ import { useSelector, useDispatch } from "react-redux"; import { logout } from "../service/oprations/authApi"; import logo from "../assets/Untitled_design__1_-removebg-preview.png"; import { GiHamburgerMenu } from "react-icons/gi"; +import { FaUser } from "react-icons/fa"; import { motion } from "framer-motion"; import { BsSun, BsMoon } from "react-icons/bs"; // Import sun and moon icons @@ -27,8 +28,9 @@ const itemVariants = { // Accept darkMode and toggleDarkMode as props const Navbar = ({ darkMode, toggleDarkMode }) => { - const login = useSelector((state) => state.auth.islogin); + const islogin = useSelector((state) => state.auth.islogin); const token = useSelector((state) => state.auth.token); + const user = useSelector((state) => state.auth.user); const dispatch = useDispatch(); const [isMenuOpen, setIsMenuOpen] = useState(false); @@ -117,7 +119,18 @@ const Navbar = ({ darkMode, toggleDarkMode }) => { {/* Auth Buttons */}
{token !== null ? ( - + + {/* Profile Icon */} + +
+ +
+
+ + {/* Logout Button */}