farheen_blog_app 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/info.md ADDED
@@ -0,0 +1,13 @@
1
+ # Project Information
2
+
3
+ ## Pages Created
4
+ - **Create User Page**: Added `CreateUser.jsx` inside the admin folder to allow admins to create new users. Added its route at `/admin/users/create`.
5
+ - **Single Post Page**: Added `SinglePost.jsx` page to view a specific post in detail along with its comments. Added its route at `/post/:id`.
6
+ - **Comment Table**: Created `CommentTable.jsx` component to cleanly display the list of comments on the `SinglePost` page.
7
+
8
+ ## Components Updated
9
+ - **User Table**: Modified `UserTable.jsx` to dynamically render user data passed via props.
10
+ - **Manage Users**: Updated `ManageUsers.jsx` to utilize the improved `UserTable` instead of simply rendering user data in `div` elements.
11
+
12
+ ## Clean Up
13
+ - Removed unused files from the admin section (`EditBlog.jsx` and `EditUser.jsx`) to clean up the codebase.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "farheen_blog_app",
3
3
  "private": false,
4
- "version": "1.0.4",
4
+ "version": "1.0.6",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
@@ -0,0 +1,28 @@
1
+ const CommentTable = ({ comments }) => {
2
+ if (!comments || comments.length === 0) {
3
+ return <p>No comments yet.</p>;
4
+ }
5
+
6
+ return (
7
+ <table className="w-full text-left border-collapse border border-gray-200">
8
+ <thead className="bg-gray-100">
9
+ <tr>
10
+ <th className="p-3 border-b">User</th>
11
+ <th className="p-3 border-b">Comment</th>
12
+ <th className="p-3 border-b">Date</th>
13
+ </tr>
14
+ </thead>
15
+ <tbody>
16
+ {comments.map((comment) => (
17
+ <tr key={comment._id} className="border-b hover:bg-gray-50">
18
+ <td className="p-3">{comment.user?.name || "Anonymous"}</td>
19
+ <td className="p-3">{comment.text}</td>
20
+ <td className="p-3">{new Date(comment.createdAt).toLocaleDateString()}</td>
21
+ </tr>
22
+ ))}
23
+ </tbody>
24
+ </table>
25
+ );
26
+ };
27
+
28
+ export default CommentTable;
@@ -1,29 +1,63 @@
1
1
  import { NavLink } from "react-router-dom";
2
+ import { hasAnyPermission } from "../../utils/checkPermission";
2
3
 
3
4
  const AdminSidebar = () => {
5
+
6
+ const menuItems = [
7
+ {
8
+ path: "/admin",
9
+ title: "Dashboard",
10
+ },
11
+ {
12
+ path: "/admin/users",
13
+ title: "Manage Users",
14
+ module: "USERS",
15
+ actions: ["READ", "UPDATE", "DELETE"]
16
+ },
17
+ {
18
+ path: "/admin/users/create",
19
+ title: "Create User",
20
+ module: "USERS",
21
+ actions: ["CREATE"]
22
+ },
23
+ {
24
+ path: "/admin/blogs",
25
+ title: "Manage Blogs",
26
+ module: "POSTS",
27
+ actions: ["READ", "UPDATE", "DELETE"]
28
+ },
29
+ {
30
+ path: "/admin/create",
31
+ title: "Create Post",
32
+ module: "POSTS",
33
+ actions: ["CREATE"]
34
+ },
35
+ {
36
+ path: "/admin/settings",
37
+ title: "Settings",
38
+ }
39
+ ];
40
+
4
41
  return (
5
42
  <aside className="sidebar">
6
43
  <h2 className="logo">Blogify</h2>
7
44
  <nav>
45
+ {menuItems.map((item, index) => {
46
+ let hasAccess = true;
8
47
 
9
- <NavLink to="/admin">
10
- Dashboard
11
- </NavLink>
12
-
13
- <NavLink to="/admin/users">
14
- Users
15
- </NavLink>
48
+ if (item.module && item.actions) {
49
+ hasAccess = hasAnyPermission(item.module, item.actions);
50
+ }
16
51
 
17
- <NavLink to="/admin/blogs">
18
- Blogs
19
- </NavLink>
20
-
21
- <NavLink to="/admin/settings">
22
- Settings
23
- </NavLink>
52
+ if (!hasAccess) return null;
24
53
 
54
+ return (
55
+ <NavLink key={index} to={item.path} end>
56
+ {item.title}
57
+ </NavLink>
58
+ );
59
+ })}
25
60
  </nav>
26
-
27
61
  </aside>
28
62
  );
29
63
  };
@@ -1,26 +1,28 @@
1
- const UserTable = () => {
1
+ const UserTable = ({ users }) => {
2
2
  return (
3
- <table className="user-table">
3
+ <table className="user-table w-full text-left border-collapse">
4
4
  <thead>
5
- <tr>
6
- <th>Name</th>
7
- <th>Email</th>
8
- <th>Role</th>
9
- <th>Action</th>
5
+ <tr className="border-b">
6
+ <th className="p-3">Name</th>
7
+ <th className="p-3">Email</th>
8
+ <th className="p-3">Role</th>
9
+ <th className="p-3">Action</th>
10
10
  </tr>
11
11
  </thead>
12
12
 
13
13
  <tbody>
14
- <tr>
15
- <td>Farheen</td>
16
- <td>farheen@gmail.com</td>
17
- <td>User</td>
18
- <td>
19
- <button className="delete-btn">
20
- Delete
21
- </button>
22
- </td>
23
- </tr>
14
+ {users?.map(user => (
15
+ <tr key={user._id} className="border-b">
16
+ <td className="p-3">{user.name}</td>
17
+ <td className="p-3">{user.email}</td>
18
+ <td className="p-3">{user.role}</td>
19
+ <td className="p-3">
20
+ <button className="delete-btn bg-red-500 text-white px-3 py-1 rounded">
21
+ Delete
22
+ </button>
23
+ </td>
24
+ </tr>
25
+ ))}
24
26
  </tbody>
25
27
  </table>
26
28
  )
@@ -13,5 +13,5 @@ export function ProtectedRoute(){
13
13
 
14
14
  return token
15
15
  ? <Outlet />
16
- : <Navigate to="/login"/>
16
+ : <Navigate to="/"/>
17
17
  }
@@ -38,7 +38,7 @@ const Register = () => {
38
38
  const response = await registerUser(payload);
39
39
 
40
40
  alert(response.data.message);
41
- navigate("/login");
41
+ navigate("/");
42
42
 
43
43
  } catch (error) {
44
44
 
@@ -0,0 +1,40 @@
1
+ import { useEffect, useState } from "react";
2
+ import { useParams } from "react-router-dom";
3
+ import api from "../services/api";
4
+ import CommentTable from "../components/CommentTable";
5
+
6
+ const SinglePost = () => {
7
+ const { id } = useParams();
8
+ const [post, setPost] = useState(null);
9
+ const [comments, setComments] = useState([]);
10
+
11
+ const fetchPostAndComments = async () => {
12
+ try {
13
+ const postRes = await api.get(`/posts/${id}`);
14
+ setPost(postRes.data);
15
+ const commentsRes = await api.get(`/posts/${id}/comments`);
16
+ setComments(commentsRes.data);
17
+ } catch (error) {
18
+ console.log("Error fetching post or comments", error);
19
+ }
20
+ };
21
+
22
+ useEffect(() => {
23
+ fetchPostAndComments();
24
+ }, [id]);
25
+
26
+ if (!post) return <div className="text-center p-10">Loading...</div>;
27
+
28
+ return (
29
+ <div className="max-w-4xl mx-auto p-4 mt-10">
30
+ <h1 className="text-4xl font-bold mb-4">{post.title}</h1>
31
+ {post.image && <img src={post.image} alt={post.title} className="w-full h-96 object-cover mb-8 rounded shadow" />}
32
+ <div className="text-gray-800 text-lg mb-12 whitespace-pre-wrap">{post.content}</div>
33
+
34
+ <h2 className="text-2xl font-bold mb-4">Comments</h2>
35
+ <CommentTable comments={comments} />
36
+ </div>
37
+ );
38
+ };
39
+
40
+ export default SinglePost;
@@ -0,0 +1,51 @@
1
+ import { useState } from "react";
2
+ import api from "../../services/api";
3
+
4
+ const CreateUser = () => {
5
+ const [formData, setFormData] = useState({
6
+ name: "",
7
+ email: "",
8
+ password: "",
9
+ role: "user"
10
+ });
11
+ const [loading, setLoading] = useState(false);
12
+
13
+ const handleChange = (e) => {
14
+ setFormData({ ...formData, [e.target.name]: e.target.value });
15
+ };
16
+
17
+ const handleSubmit = async (e) => {
18
+ e.preventDefault();
19
+ try {
20
+ setLoading(true);
21
+ await api.post("/admin/users", formData);
22
+ alert("User created successfully");
23
+ setFormData({ name: "", email: "", password: "", role: "user" });
24
+ } catch (error) {
25
+ console.log(error);
26
+ alert("Failed to create user");
27
+ } finally {
28
+ setLoading(false);
29
+ }
30
+ };
31
+
32
+ return (
33
+ <div className="max-w-xl mx-auto mt-10">
34
+ <h1 className="text-2xl font-bold mb-4">Create User</h1>
35
+ <form onSubmit={handleSubmit} className="flex flex-col gap-4">
36
+ <input type="text" name="name" placeholder="Name" value={formData.name} onChange={handleChange} required className="border p-3 rounded" />
37
+ <input type="email" name="email" placeholder="Email" value={formData.email} onChange={handleChange} required className="border p-3 rounded" />
38
+ <input type="password" name="password" placeholder="Password" value={formData.password} onChange={handleChange} required className="border p-3 rounded" />
39
+ <select name="role" value={formData.role} onChange={handleChange} className="border p-3 rounded">
40
+ <option value="user">User</option>
41
+ <option value="admin">Admin</option>
42
+ </select>
43
+ <button type="submit" disabled={loading} className="bg-black text-white p-3 rounded">
44
+ {loading ? "Creating..." : "Create User"}
45
+ </button>
46
+ </form>
47
+ </div>
48
+ );
49
+ };
50
+
51
+ export default CreateUser;
@@ -1,5 +1,6 @@
1
1
  import { useEffect, useState } from "react";
2
2
  import api from "../../services/api";
3
+ import UserTable from "../../components/admin/UserTable";
3
4
 
4
5
  const ManageUsers = () => {
5
6
 
@@ -24,18 +25,9 @@ const ManageUsers = () => {
24
25
  return (
25
26
  <div>
26
27
 
27
- <h1>Manage Users</h1>
28
+ <h1 className="text-2xl font-bold mb-4">Manage Users</h1>
28
29
 
29
- {
30
- users.map((user)=>(
31
- <div key={user._id}>
32
-
33
- <h3>{user.name}</h3>
34
- <p>{user.email}</p>
35
-
36
- </div>
37
- ))
38
- }
30
+ <UserTable users={users} />
39
31
 
40
32
  </div>
41
33
  );
@@ -14,6 +14,8 @@ import AdminDashboard from "../pages/admin/AdminDashboard";
14
14
  import AdminLayout from "../layout/AdminLayout";
15
15
  import Register from "../pages/Register";
16
16
  import { ProtectedRoute } from "../middleware/ProtectedRoute";
17
+ import SinglePost from "../pages/SinglePost";
18
+ import CreateUser from "../pages/admin/CreateUser";
17
19
 
18
20
  function AppRoutes() {
19
21
  return (
@@ -23,22 +25,22 @@ function AppRoutes() {
23
25
  <Routes>
24
26
  <Route
25
27
  path="/"
26
- element={<Home />}
28
+ element={<Login />}
27
29
  />
28
30
 
29
31
  <Route
30
- path="/register"
31
- element={<Register />}
32
+ path="/home"
33
+ element={<Home />}
32
34
  />
33
35
 
34
36
  <Route
35
- path="/login"
36
- element={<Login />}
37
+ path="/register"
38
+ element={<Register />}
37
39
  />
38
40
 
39
41
  <Route
40
- path="/create"
41
- element={<CreatePost />}
42
+ path="/post/:id"
43
+ element={<SinglePost />}
42
44
  />
43
45
 
44
46
  <Route
@@ -51,10 +53,18 @@ function AppRoutes() {
51
53
  path="users"
52
54
  element={<ManageUsers />}
53
55
  />
56
+ <Route
57
+ path="users/create"
58
+ element={<CreateUser />}
59
+ />
54
60
  <Route
55
61
  path="blogs"
56
62
  element={<ManageBlogs />}
57
63
  />
64
+ <Route
65
+ path="create"
66
+ element={<CreatePost />}
67
+ />
58
68
  {/* <Route
59
69
  path="settings"
60
70
  element={<Settings />}
@@ -1,15 +1,15 @@
1
- import api from "./api";
2
-
3
- export const register = async (formData) => {
4
- const response = await api.post("/platformUser/register");
5
- return response.data;
6
- };
7
-
8
- export const login = async (data) => {
9
- const response = await api.post(
10
- "/platformUser/login",
11
- data
12
- );
13
-
14
- return response.data;
15
- };
1
+ import api from "./api";
2
+
3
+ export const register = async (formData) => {
4
+ const response = await api.post("/auth/register", formData);
5
+ return response.data;
6
+ };
7
+
8
+ export const login = async (data) => {
9
+ const response = await api.post(
10
+ "/auth/login",
11
+ data
12
+ );
13
+
14
+ return response.data;
15
+ };
@@ -0,0 +1,28 @@
1
+ export const checkPermission = (module, action) => {
2
+ const token = localStorage.getItem("token");
3
+ if (!token) return false;
4
+
5
+ try {
6
+ const payloadBase64 = token.split('.')[1];
7
+ // handle base64 strings not padded properly
8
+ const base64 = payloadBase64.replace(/-/g, '+').replace(/_/g, '/');
9
+ const jsonPayload = decodeURIComponent(atob(base64).split('').map(function (c) {
10
+ return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
11
+ }).join(''));
12
+
13
+ const decodedPayload = JSON.parse(jsonPayload);
14
+ const permissions = decodedPayload.permissions || [];
15
+
16
+ // Example: "USERS:READ"
17
+ const requiredPermission = `${module}:${action}`;
18
+
19
+ return permissions.includes(requiredPermission);
20
+ } catch (e) {
21
+ console.error("Failed to parse token for permissions", e);
22
+ return false;
23
+ }
24
+ };
25
+
26
+ export const hasAnyPermission = (module, actions) => {
27
+ return actions.some(action => checkPermission(module, action));
28
+ };
File without changes
File without changes