farheen_blog_app 2.0.0 → 2.0.1

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "farheen_blog_app",
3
3
  "private": false,
4
- "version": "2.0.0",
4
+ "version": "2.0.1",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
@@ -12,10 +12,10 @@ const UserTable = ({ users }) => {
12
12
 
13
13
  <tbody className="divide-y divide-gray-100">
14
14
  {users?.map((user, index) => (
15
- <tr
16
- key={user._id}
17
- className="hover:bg-blue-50/50 transition-colors duration-200 animate-fade-in-up"
18
- style={{ animationDelay: `${index * 0.08}s` }}
15
+ <tr
16
+ key={user._id}
17
+ className="hover:bg-blue-50/50 transition-colors duration-200 animate-fade-in-up"
18
+ style={{ animationDelay: `${index * 0.08}s` }}
19
19
  >
20
20
  <td className="p-4">
21
21
  <div className="flex items-center gap-4">
@@ -29,8 +29,8 @@ const UserTable = ({ users }) => {
29
29
  </div>
30
30
  </td>
31
31
  <td className="p-4">
32
- <span className={`px-3 py-1.5 text-xs font-semibold rounded-full border ${user.role === 'admin' ? 'bg-indigo-50 text-indigo-700 border-indigo-200' : 'bg-gray-50 text-gray-700 border-gray-200'}`}>
33
- {(user.role || 'user').toUpperCase()}
32
+ <span className={`px-3 py-1.5 text-xs font-semibold rounded-full border ${user.role?.name === 'admin' ? 'bg-indigo-50 text-indigo-700 border-indigo-200' : 'bg-gray-50 text-gray-700 border-gray-200'}`}>
33
+ {(user.role?.name || 'user').toUpperCase()}
34
34
  </span>
35
35
  </td>
36
36
  <td className="p-4 text-right">
@@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
2
2
  import { Link } from "react-router-dom";
3
3
  import api from "../../services/api";
4
4
  import useDebounce from "../../hooks/useDebounce";
5
- import { getPosts } from "../../services/postService";
5
+ import { deletePost, getPosts } from "../../services/postService";
6
6
  import { Search, FileX } from "lucide-react";
7
7
 
8
8
  const ManageBlogs = () => {
@@ -20,12 +20,13 @@ const ManageBlogs = () => {
20
20
  const loadBlogs = async () => {
21
21
  try {
22
22
  setLoading(true);
23
- const res = await api.get(`/admin/blogs/posts?page=${page}&search=${debouncedSearch}`).catch(async () => {
23
+ const res = await api.get(`/posts?page=${page}&search=${debouncedSearch}`).catch(async () => {
24
24
  // Fallback if admin route doesn't exist
25
25
  return { data: { posts: await getPosts(), totalPages: 1 } };
26
26
  });
27
-
28
- const fetchedBlogs = res.data?.posts || res.data || [];
27
+
28
+ const fetchedBlogs = res.data || [];
29
+ console.log(res);
29
30
  setBlogs(Array.isArray(fetchedBlogs) ? fetchedBlogs : []);
30
31
  setTotalPages(res.data?.totalPages || 1);
31
32
  } catch (error) {
@@ -38,7 +39,7 @@ const ManageBlogs = () => {
38
39
  const handleDelete = async (id) => {
39
40
  if (window.confirm("Are you sure you want to delete this blog?")) {
40
41
  try {
41
- await api.delete(`/posts/${id}`);
42
+ await deletePost(id);
42
43
  loadBlogs();
43
44
  } catch (error) {
44
45
  console.log(error);
@@ -52,95 +53,95 @@ const ManageBlogs = () => {
52
53
  }, [page, debouncedSearch]);
53
54
 
54
55
  return (
55
- <div className="p-8 max-w-[1400px] mx-auto animate-fade-in-up">
56
- <div className="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 bg-white p-6 rounded-2xl shadow-sm border border-slate-200">
57
- <div className="mb-4 md:mb-0">
58
- <h1 className="text-3xl font-bold text-slate-800 tracking-tight">Manage Blogs</h1>
59
- <p className="text-slate-500 mt-1 text-sm">View, edit, and moderate all blog posts</p>
60
- </div>
56
+ <div className="p-8 max-w-[1400px] mx-auto animate-fade-in-up">
57
+ <div className="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 bg-white p-6 rounded-2xl shadow-sm border border-slate-200">
58
+ <div className="mb-4 md:mb-0">
59
+ <h1 className="text-3xl font-bold text-slate-800 tracking-tight">Manage Blogs</h1>
60
+ <p className="text-slate-500 mt-1 text-sm">View, edit, and moderate all blog posts</p>
61
+ </div>
61
62
 
62
- <div className="relative w-full md:w-auto">
63
- <input
64
- type="text"
65
- value={search}
66
- placeholder="Search blogs..."
67
- onChange={(e) => setSearch(e.target.value)}
68
- className="border border-slate-200 bg-slate-50 rounded-lg pl-11 pr-4 py-2.5 w-full md:w-80 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 focus:bg-white shadow-sm transition-all"
69
- />
70
- <Search className="absolute left-4 top-3 text-slate-400" size={18} />
71
- </div>
63
+ <div className="relative w-full md:w-auto">
64
+ <input
65
+ type="text"
66
+ value={search}
67
+ placeholder="Search blogs..."
68
+ onChange={(e) => setSearch(e.target.value)}
69
+ className="border border-slate-200 bg-slate-50 rounded-lg pl-11 pr-4 py-2.5 w-full md:w-80 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 focus:bg-white shadow-sm transition-all"
70
+ />
71
+ <Search className="absolute left-4 top-3 text-slate-400" size={18} />
72
72
  </div>
73
+ </div>
73
74
 
74
- <div className="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden">
75
- {loading ? (
76
- <div className="flex justify-center py-32">
77
- <div className="w-10 h-10 border-4 border-indigo-200 border-t-indigo-600 rounded-full animate-spin"></div>
78
- </div>
79
- ) : (
80
- <div className="overflow-x-auto">
81
- <table className="w-full text-left border-collapse min-w-[800px]">
82
- <thead className="bg-slate-50/80 border-b border-slate-200">
75
+ <div className="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden">
76
+ {loading ? (
77
+ <div className="flex justify-center py-32">
78
+ <div className="w-10 h-10 border-4 border-indigo-200 border-t-indigo-600 rounded-full animate-spin"></div>
79
+ </div>
80
+ ) : (
81
+ <div className="overflow-x-auto">
82
+ <table className="w-full text-left border-collapse min-w-[800px]">
83
+ <thead className="bg-slate-50/80 border-b border-slate-200">
84
+ <tr>
85
+ <th className="px-6 py-4 font-semibold text-slate-600 text-xs tracking-wider uppercase">Image</th>
86
+ <th className="px-6 py-4 font-semibold text-slate-600 text-xs tracking-wider uppercase">Content</th>
87
+ <th className="px-6 py-4 font-semibold text-slate-600 text-xs tracking-wider uppercase">Author</th>
88
+ <th className="px-6 py-4 font-semibold text-slate-600 text-xs tracking-wider uppercase">Date</th>
89
+ <th className="px-6 py-4 font-semibold text-slate-600 text-xs tracking-wider uppercase text-center">Status</th>
90
+ <th className="px-6 py-4 font-semibold text-slate-600 text-xs tracking-wider uppercase text-right">Actions</th>
91
+ </tr>
92
+ </thead>
93
+ <tbody className="divide-y divide-slate-100">
94
+ {blogs.length === 0 ? (
83
95
  <tr>
84
- <th className="px-6 py-4 font-semibold text-slate-600 text-xs tracking-wider uppercase">Image</th>
85
- <th className="px-6 py-4 font-semibold text-slate-600 text-xs tracking-wider uppercase">Content</th>
86
- <th className="px-6 py-4 font-semibold text-slate-600 text-xs tracking-wider uppercase">Author</th>
87
- <th className="px-6 py-4 font-semibold text-slate-600 text-xs tracking-wider uppercase">Date</th>
88
- <th className="px-6 py-4 font-semibold text-slate-600 text-xs tracking-wider uppercase text-center">Status</th>
89
- <th className="px-6 py-4 font-semibold text-slate-600 text-xs tracking-wider uppercase text-right">Actions</th>
96
+ <td colSpan="6" className="p-16 text-center text-slate-400 bg-slate-50/50">
97
+ <div className="flex flex-col items-center justify-center">
98
+ <FileX size={48} strokeWidth={1} className="text-slate-300 mb-4" />
99
+ <p className="font-medium text-lg text-slate-500">No blogs found.</p>
100
+ <p className="text-sm mt-1">Try adjusting your search criteria</p>
101
+ </div>
102
+ </td>
90
103
  </tr>
91
- </thead>
92
- <tbody className="divide-y divide-slate-100">
93
- {blogs.length === 0 ? (
94
- <tr>
95
- <td colSpan="6" className="p-16 text-center text-slate-400 bg-slate-50/50">
96
- <div className="flex flex-col items-center justify-center">
97
- <FileX size={48} strokeWidth={1} className="text-slate-300 mb-4" />
98
- <p className="font-medium text-lg text-slate-500">No blogs found.</p>
99
- <p className="text-sm mt-1">Try adjusting your search criteria</p>
104
+ ) : (
105
+ blogs.map((blog, index) => (
106
+ <tr key={blog.id} className="hover:bg-slate-50/80 transition-colors duration-200 animate-fade-in-up group" style={{ animationDelay: `${index * 0.05}s` }}>
107
+ <td className="px-6 py-4">
108
+ {blog.image_url ? (
109
+ <img src={`http://localhost:3000${blog.image_url}`} alt="Blog post" className="h-14 w-14 object-cover rounded-xl shadow-sm border border-slate-200" />
110
+ ) : (
111
+ <div className="h-14 w-14 bg-gradient-to-br from-slate-100 to-slate-200 rounded-xl flex items-center justify-center text-xs text-slate-400 border border-slate-200 shadow-sm">N/A</div>
112
+ )}
113
+ </td>
114
+ <td className="px-6 py-4 w-2/5">
115
+ <p className="font-medium text-slate-800 line-clamp-2 leading-relaxed">{blog.content || 'No content'}</p>
116
+ </td>
117
+ <td className="px-6 py-4">
118
+ <div className="flex items-center gap-3">
119
+ <div className="w-8 h-8 rounded-full bg-indigo-100 text-indigo-700 flex items-center justify-center font-bold text-xs">
120
+ {(blog.User?.name || 'U').charAt(0).toUpperCase()}
121
+ </div>
122
+ <span className="font-medium text-slate-700">{blog.User?.name || 'Unknown'}</span>
100
123
  </div>
101
124
  </td>
125
+ <td className="px-6 py-4 text-slate-500 text-sm font-medium">
126
+ {blog.created_at ? new Date(blog.created_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) : 'N/A'}
127
+ </td>
128
+ <td className="px-6 py-4 text-center">
129
+ <span className="inline-flex px-3 py-1 rounded-full text-xs font-semibold bg-emerald-50 text-emerald-700 border border-emerald-200">
130
+ PUBLISHED
131
+ </span>
132
+ </td>
133
+ <td className="px-6 py-4 text-right whitespace-nowrap">
134
+ <button onClick={() => handleDelete(blog.id)} className="bg-white hover:bg-red-50 hover:text-red-600 text-slate-600 px-4 py-2 rounded-lg font-medium transition-all duration-200 shadow-sm border border-slate-200 hover:border-red-200 cursor-pointer text-sm">
135
+ Delete
136
+ </button>
137
+ </td>
102
138
  </tr>
103
- ) : (
104
- blogs.map((blog, index) => (
105
- <tr key={blog.id} className="hover:bg-slate-50/80 transition-colors duration-200 animate-fade-in-up group" style={{ animationDelay: `${index * 0.05}s` }}>
106
- <td className="px-6 py-4">
107
- {blog.image_url ? (
108
- <img src={`http://localhost:3000${blog.image_url}`} alt="Blog post" className="h-14 w-14 object-cover rounded-xl shadow-sm border border-slate-200" />
109
- ) : (
110
- <div className="h-14 w-14 bg-gradient-to-br from-slate-100 to-slate-200 rounded-xl flex items-center justify-center text-xs text-slate-400 border border-slate-200 shadow-sm">N/A</div>
111
- )}
112
- </td>
113
- <td className="px-6 py-4 w-2/5">
114
- <p className="font-medium text-slate-800 line-clamp-2 leading-relaxed">{blog.content || 'No content'}</p>
115
- </td>
116
- <td className="px-6 py-4">
117
- <div className="flex items-center gap-3">
118
- <div className="w-8 h-8 rounded-full bg-indigo-100 text-indigo-700 flex items-center justify-center font-bold text-xs">
119
- {(blog.User?.name || 'U').charAt(0).toUpperCase()}
120
- </div>
121
- <span className="font-medium text-slate-700">{blog.User?.name || 'Unknown'}</span>
122
- </div>
123
- </td>
124
- <td className="px-6 py-4 text-slate-500 text-sm font-medium">
125
- {blog.created_at ? new Date(blog.created_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) : 'N/A'}
126
- </td>
127
- <td className="px-6 py-4 text-center">
128
- <span className="inline-flex px-3 py-1 rounded-full text-xs font-semibold bg-emerald-50 text-emerald-700 border border-emerald-200">
129
- PUBLISHED
130
- </span>
131
- </td>
132
- <td className="px-6 py-4 text-right whitespace-nowrap">
133
- <button onClick={() => handleDelete(blog.id)} className="bg-white hover:bg-red-50 hover:text-red-600 text-slate-600 px-4 py-2 rounded-lg font-medium transition-all duration-200 shadow-sm border border-slate-200 hover:border-red-200 cursor-pointer text-sm">
134
- Delete
135
- </button>
136
- </td>
137
- </tr>
138
- )))}
139
- </tbody>
140
- </table>
141
- </div>
142
- )}
143
- </div>
139
+ )))}
140
+ </tbody>
141
+ </table>
142
+ </div>
143
+ )}
144
+ </div>
144
145
 
145
146
  {/* Pagination */}
146
147
  <div className="flex items-center justify-between mt-8">
@@ -12,4 +12,16 @@ export const createPost = async (formData) => {
12
12
  );
13
13
 
14
14
  return response.data;
15
+ };
16
+
17
+ export const updatePost = async (id, formData) => {
18
+ const response = await api.put(
19
+ `/posts/${id}`,
20
+ formData
21
+ );
22
+ return response.data;
23
+ };
24
+
25
+ export const deletePost = async (id) => {
26
+ return await api.delete(`/posts/${id}`);
15
27
  };