Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion server/controllers/BlogControl.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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);
Expand Down
70 changes: 70 additions & 0 deletions server/controllers/Profile.js
Original file line number Diff line number Diff line change
@@ -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",
});
}
});
4 changes: 3 additions & 1 deletion server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand All @@ -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}`);
Expand Down
2 changes: 2 additions & 0 deletions server/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
25 changes: 24 additions & 1 deletion server/routes/BlogRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
11 changes: 11 additions & 0 deletions server/routes/ProfileRoutes.js
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 2 additions & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -69,6 +70,7 @@ const App = () => {
<Route path="/plant/:plantName/Order" element={<OrderPage />} />
<Route path="/signup" element={<SignUp />} />
<Route path="/signin" element={<Login />} />
<Route path="/profile" element={<Profile />} />
<Route path="/contact" element={<ContactUs />} />
<Route path="/about" element={<AboutUs />} />
<Route path="*" element={<NotFound />} />
Expand Down
48 changes: 38 additions & 10 deletions src/Component/Navbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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);

Expand Down Expand Up @@ -117,7 +119,18 @@ const Navbar = ({ darkMode, toggleDarkMode }) => {
{/* Auth Buttons */}
<div className="hidden lg:flex gap-3 items-center">
{token !== null ? (
<motion.div>
<motion.div className="flex items-center gap-3">
{/* Profile Icon */}
<NavLink
to="/profile"
className="hover:scale-110 transition-transform duration-200"
>
<div className="w-8 h-8 bg-green-600 dark:bg-green-500 rounded-full flex items-center justify-center hover:bg-green-700 dark:hover:bg-green-400 transition-colors duration-200">
<FaUser className="text-white text-sm" />
</div>
</NavLink>

{/* Logout Button */}
<button
onClick={logouthandel}
className="hover:text-green-900 transition-colors duration-200 dark:text-gray-200 dark:hover:text-green-400"
Expand Down Expand Up @@ -213,14 +226,29 @@ const Navbar = ({ darkMode, toggleDarkMode }) => {
</NavLink>
</motion.div>
{token !== null ? (
<motion.div variants={itemVariants}>
<div
className="py-2 w-full text-center dark:text-gray-200"
onClick={logouthandel}
>
Log Out
</div>
</motion.div>
<>
<motion.div variants={itemVariants}>
<NavLink
to="/profile"
className="py-2 w-full text-center border-b dark:border-gray-700 dark:text-gray-200 flex items-center justify-center gap-2"
onClick={toggleMenu}
>
<FaUser className="text-green-600 dark:text-green-400" />
Profile
</NavLink>
</motion.div>
<motion.div variants={itemVariants}>
<div
className="py-2 w-full text-center dark:text-gray-200 cursor-pointer"
onClick={() => {
logouthandel();
toggleMenu();
}}
>
Log Out
</div>
</motion.div>
</>
) : (
<>
<motion.div variants={itemVariants}>
Expand Down
Loading