farheen_blog_app 1.0.7 → 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": "1.0.7",
4
+ "version": "2.0.1",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
@@ -12,6 +12,7 @@
12
12
  "dependencies": {
13
13
  "@tanstack/react-query": "^5.100.14",
14
14
  "axios": "^1.16.1",
15
+ "lucide-react": "^1.18.0",
15
16
  "react": "^19.2.6",
16
17
  "react-dom": "^19.2.6",
17
18
  "react-hook-form": "^7.76.1",
package/src/App.css CHANGED
@@ -1,300 +1,12 @@
1
- /* Reset */
1
+ /* App.css - Cleaned up to use Tailwind CSS exclusively */
2
2
 
3
- * {
4
- margin: 0;
5
- padding: 0;
6
- box-sizing: border-box;
3
+ /* Animations for new components */
4
+ @keyframes fadeInUp {
5
+ from { opacity: 0; transform: translateY(15px); }
6
+ to { opacity: 1; transform: translateY(0); }
7
7
  }
8
8
 
9
- body {
10
- font-family: Inter, sans-serif;
11
- background: #f5f7fb;
12
- }
13
-
14
- /* Layout */
15
-
16
- .layout {
17
- display: flex;
18
- min-height: 100vh;
19
- }
20
-
21
- /* Sidebar */
22
-
23
- .sidebar {
24
- width: 260px;
25
- background: #161c2a;
26
- color: white;
27
- padding: 20px;
28
- flex-shrink: 0;
29
- }
30
-
31
- .logo {
32
- margin-bottom: 40px;
33
- font-size: 24px;
34
- font-weight: 600;
35
- }
36
-
37
- .sidebar nav {
38
- display: flex;
39
- flex-direction: column;
40
- gap: 10px;
41
- }
42
-
43
- .sidebar a {
44
- text-decoration: none;
45
- color: white;
46
- padding: 12px 16px;
47
- border-radius: 8px;
48
- transition: 0.3s;
49
- }
50
-
51
- .sidebar a:hover,
52
- .sidebar a.active {
53
- background: #1f2937;
54
- }
55
-
56
- /* Main Content */
57
-
58
- .main-content {
59
- flex: 1;
60
- display: flex;
61
- flex-direction: column;
62
- }
63
-
64
- /* Navbar */
65
-
66
- .navbar {
67
- height: 70px;
68
- background: #161c2a;
69
- display: flex;
70
- justify-content: space-between;
71
- align-items: center;
72
- padding: 0 30px;
73
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
74
- }
75
-
76
- .navbar input {
77
- width: 350px;
78
- padding: 10px 15px;
79
- border: 1px solid #d1d5db;
80
- border-radius: 8px;
81
- outline: none;
82
- }
83
-
84
- .navbar-right {
85
- display: flex;
86
- align-items: center;
87
- gap: 15px;
88
- color: white;
89
- }
90
-
91
- .navbar-right button {
92
- border: none;
93
- background: transparent;
94
- cursor: pointer;
95
- font-size: 18px;
96
- }
97
-
98
- .navbar-right img {
99
- width: 40px;
100
- height: 40px;
101
- border-radius: 50%;
102
- }
103
-
104
- /* Page Content */
105
-
106
- .page-content {
107
- flex: 1;
108
- padding: 30px;
109
- }
110
-
111
- /* Dashboard */
112
-
113
- .dashboard h1 {
114
- margin-bottom: 25px;
115
- color: #111827;
116
- }
117
-
118
- /* Cards */
119
-
120
- .cards {
121
- display: grid;
122
- grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
123
- gap: 20px;
124
- }
125
-
126
- .card {
127
- background: white;
128
- padding: 25px;
129
- border-radius: 12px;
130
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
131
- }
132
-
133
- .card h4 {
134
- color: #6b7280;
135
- margin-bottom: 10px;
136
- font-weight: 500;
137
- }
138
-
139
- .card h2 {
140
- color: #111827;
141
- font-size: 32px;
142
- }
143
-
144
- /* Tables */
145
-
146
- .table-container {
147
- margin-top: 30px;
148
- background: white;
149
- border-radius: 12px;
150
- overflow: hidden;
151
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
152
- }
153
-
154
- .user-table {
155
- width: 100%;
156
- border-collapse: collapse;
157
- }
158
-
159
- .user-table th {
160
- background: #111827;
161
- color: white;
162
- padding: 15px;
163
- text-align: left;
164
- }
165
-
166
- .user-table td {
167
- padding: 15px;
168
- border-bottom: 1px solid #e5e7eb;
169
- }
170
-
171
- .user-table tr:hover {
172
- background: #f9fafb;
173
- }
174
-
175
- /* Buttons */
176
-
177
- .edit-btn {
178
- background: #2563eb;
179
- color: white;
180
- border: none;
181
- padding: 8px 14px;
182
- border-radius: 6px;
183
- cursor: pointer;
184
- margin-right: 10px;
185
- }
186
-
187
- .delete-btn {
188
- background: #dc2626;
189
- color: white;
190
- border: none;
191
- padding: 8px 14px;
192
- border-radius: 6px;
193
- cursor: pointer;
194
- }
195
-
196
- /* Mobile */
197
-
198
- @media (max-width: 768px) {
199
- .layout {
200
- flex-direction: column;
201
- }
202
-
203
- .sidebar {
204
- width: 100%;
205
- }
206
-
207
- .navbar {
208
- flex-direction: column;
209
- gap: 10px;
210
- height: auto;
211
- padding: 15px;
212
- }
213
-
214
- .navbar input {
215
- width: 100%;
216
- }
217
-
218
- .cards {
219
- grid-template-columns: 1fr;
220
- }
221
-
222
- .table-container {
223
- overflow-x: auto;
224
- }
225
- }
226
-
227
- /* auth css */
228
- .auth-container {
229
- min-height: 100vh;
230
- background: #f5f7fb;
231
- display: flex;
232
- justify-content: center;
233
- align-items: center;
234
- padding: 20px;
235
- }
236
-
237
- .auth-card {
238
- width: 100%;
239
- max-width: 450px;
240
- background: white;
241
- padding: 40px;
242
- border-radius: 16px;
243
- box-shadow: 0 8px 30px rgba(0,0,0,0.08);
244
- }
245
-
246
- .auth-card h1 {
247
- text-align: center;
248
- margin-bottom: 10px;
249
- color: #111827;
250
- }
251
-
252
- .auth-card p {
253
- text-align: center;
254
- color: #6b7280;
255
- margin-bottom: 30px;
256
- }
257
-
258
- .auth-form {
259
- display: flex;
260
- flex-direction: column;
261
- gap: 15px;
262
- }
263
-
264
- .auth-form input {
265
- padding: 14px;
266
- border: 1px solid #d1d5db;
267
- border-radius: 8px;
268
- outline: none;
269
- font-size: 15px;
270
- }
271
-
272
- .auth-form input:focus {
273
- border-color: #2563eb;
274
- }
275
-
276
- .auth-form button {
277
- background: #111827;
278
- color: white;
279
- border: none;
280
- padding: 14px;
281
- border-radius: 8px;
282
- cursor: pointer;
283
- font-size: 16px;
284
- transition: .3s;
285
- }
286
-
287
- .auth-form button:hover {
288
- background: #1f2937;
289
- }
290
-
291
- .auth-footer {
292
- text-align: center;
293
- margin-top: 20px;
294
- }
295
-
296
- .auth-footer a {
297
- color: #2563eb;
298
- text-decoration: none;
299
- font-weight: 600;
9
+ .animate-fade-in-up {
10
+ opacity: 0;
11
+ animation: fadeInUp 0.5s ease forwards;
300
12
  }
package/src/App.jsx CHANGED
@@ -1,12 +1,13 @@
1
- import { useState } from 'react'
2
- import reactLogo from './assets/react.svg'
3
- import viteLogo from './assets/vite.svg'
4
- import heroImg from './assets/hero.png'
5
1
  import './App.css'
6
2
  import AppRoutes from './routes/AppRoutes'
3
+ import { UserProvider } from './context/UserContext'
7
4
 
8
5
  function App() {
9
- return <AppRoutes />
6
+ return (
7
+ <UserProvider>
8
+ <AppRoutes />
9
+ </UserProvider>
10
+ )
10
11
  }
11
12
 
12
13
  export default App
@@ -1,27 +1,37 @@
1
1
  const CommentTable = ({ comments }) => {
2
2
  if (!comments || comments.length === 0) {
3
- return <p>No comments yet.</p>;
3
+ return (
4
+ <div className="text-center py-12 px-4 rounded-2xl border border-dashed border-gray-300 bg-gray-50">
5
+ <div className="text-4xl mb-3 opacity-30">💬</div>
6
+ <h3 className="text-lg font-medium text-gray-900">No comments yet</h3>
7
+ <p className="text-gray-500 mt-1">Be the first to share your thoughts!</p>
8
+ </div>
9
+ );
4
10
  }
5
11
 
6
12
  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>
13
+ <div className="space-y-6">
14
+ {comments.map((comment, index) => (
15
+ <div key={comment._id || index} className="flex gap-4 animate-fade-in-up" style={{ animationDelay: `${index * 0.1}s` }}>
16
+ <div className="flex-shrink-0">
17
+ <div className="w-12 h-12 rounded-full bg-gradient-to-br from-blue-100 to-indigo-100 border border-indigo-200 flex items-center justify-center text-indigo-700 font-bold text-lg shadow-sm">
18
+ {(comment.user?.name || "A").charAt(0).toUpperCase()}
19
+ </div>
20
+ </div>
21
+ <div className="flex-1 bg-gray-50 rounded-2xl rounded-tl-none p-5 shadow-sm border border-gray-100">
22
+ <div className="flex items-center justify-between mb-2">
23
+ <h4 className="font-semibold text-gray-900">{comment.user?.name || "Anonymous"}</h4>
24
+ <span className="text-xs font-medium text-gray-500 bg-white px-2 py-1 rounded-full shadow-sm border border-gray-100">
25
+ {comment.createdAt ? new Date(comment.createdAt).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) : 'Just now'}
26
+ </span>
27
+ </div>
28
+ <p className="text-gray-700 leading-relaxed text-sm">
29
+ {comment.text}
30
+ </p>
31
+ </div>
32
+ </div>
33
+ ))}
34
+ </div>
25
35
  );
26
36
  };
27
37
 
@@ -1,48 +1,35 @@
1
1
  import { Link } from "react-router-dom";
2
+ import { Search, Bell } from "lucide-react";
2
3
 
3
4
  const Navbar = () => {
4
- // return (
5
- // <div className="bg-black text-white px-6 py-4 flex justify-between">
6
- // <h1 className="text-2xl font-bold">
7
- // Blog App
8
- // </h1>
9
-
10
- // <div className="flex gap-4">
11
- // <Link to="/">Home</Link>
12
-
13
- // <Link to="/create">
14
- // Create Post
15
- // </Link>
16
-
17
- // <Link to="/login">
18
- // Login
19
- // </Link>
20
- // </div>
21
- // </div>
22
- // );
23
-
24
5
  return (
25
- <header className="navbar">
26
- <h1 className="text-2xl font-bold">
27
-
6
+ <header className="bg-white h-20 border-b border-slate-200 flex justify-between items-center px-8 shadow-[0_2px_10px_rgba(0,0,0,0.02)] z-10 sticky top-0 flex-shrink-0">
7
+ <h1 className="text-xl font-semibold text-slate-800">
8
+ {/* Optional page title could go here */}
28
9
  </h1>
29
10
 
30
- <div className="navbar-right">
31
- <input
32
- type="text"
33
- placeholder="Search..."
34
- />
35
- <button>
36
- 🔔
11
+ <div className="flex items-center gap-6">
12
+ <div className="relative hidden md:block">
13
+ <input
14
+ type="text"
15
+ placeholder="Search anything..."
16
+ className="bg-slate-50 border border-slate-200 text-sm rounded-full pl-11 pr-4 py-2.5 w-64 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:bg-white transition-all"
17
+ />
18
+ <Search className="absolute left-4 top-2.5 text-slate-400" size={18} />
19
+ </div>
20
+ <button className="relative text-slate-500 hover:text-indigo-600 transition-colors p-2 rounded-full hover:bg-indigo-50">
21
+ <Bell size={20} />
22
+ <span className="absolute top-1.5 right-1.5 w-2 h-2 bg-red-500 rounded-full border border-white"></span>
37
23
  </button>
38
24
 
39
- <img
40
- src="https://i.pravatar.cc/40"
41
- alt="profile"
42
- />
43
-
25
+ <div className="flex items-center gap-3 pl-4 border-l border-slate-200 cursor-pointer group">
26
+ <img
27
+ src="https://ui-avatars.com/api/?name=Admin+User&background=6366f1&color=fff"
28
+ alt="profile"
29
+ className="w-10 h-10 rounded-full border-2 border-indigo-100 group-hover:border-indigo-300 transition-all shadow-sm"
30
+ />
31
+ </div>
44
32
  </div>
45
-
46
33
  </header>
47
34
  );
48
35
  };
@@ -1,22 +1,48 @@
1
+ import { Link } from "react-router-dom";
2
+
1
3
  const PostCard = ({ post }) => {
2
4
  return (
3
- <div className="border rounded-xl p-4 shadow">
4
- {post.image && (
5
- <img
6
- src={post.image}
7
- alt={post.title}
8
- className="w-full h-52 object-cover rounded"
9
- />
10
- )}
5
+ <Link to={`/post/${post._id}`} className="group block h-full">
6
+ <div className="flex flex-col h-full bg-white rounded-2xl overflow-hidden shadow-sm border border-gray-100 hover:shadow-xl hover:-translate-y-1 transition-all duration-300">
7
+ <div className="relative overflow-hidden h-56 bg-gray-100">
8
+ {post.image_url || post.image ? (
9
+ <img
10
+ src={`http://localhost:3000${post.image_url || post.image}`}
11
+ alt={post.title || "Blog post"}
12
+ className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
13
+ onError={(e) => { e.target.onerror = null; e.target.src = "https://images.unsplash.com/photo-1499750310107-5fef28a66643?w=800&q=80"; }}
14
+ />
15
+ ) : (
16
+ <div className="w-full h-full flex items-center justify-center text-4xl opacity-20">
17
+ 📝
18
+ </div>
19
+ )}
20
+ <div className="absolute top-4 left-4 bg-white/90 backdrop-blur-sm px-3 py-1 rounded-full text-xs font-semibold text-indigo-700 shadow-sm">
21
+ Blog
22
+ </div>
23
+ </div>
24
+
25
+ <div className="p-6 flex flex-col flex-grow">
26
+ <h2 className="text-xl font-bold text-gray-900 mb-3 group-hover:text-indigo-600 transition-colors line-clamp-2">
27
+ {post.title || "Untitled Post"}
28
+ </h2>
11
29
 
12
- <h2 className="text-2xl font-bold mt-3">
13
- {post.title}
14
- </h2>
30
+ <p className="text-gray-600 mb-6 line-clamp-3 text-sm flex-grow">
31
+ {post.content ? post.content.substring(0, 120) + "..." : "No preview available for this post."}
32
+ </p>
15
33
 
16
- <p className="text-gray-600 mt-2">
17
- {post.content.slice(0, 100)}...
18
- </p>
19
- </div>
34
+ <div className="flex items-center justify-between pt-4 border-t border-gray-100 mt-auto">
35
+ <div className="flex items-center gap-2">
36
+ <div className="w-8 h-8 rounded-full bg-gradient-to-br from-blue-500 to-indigo-600 flex items-center justify-center text-white text-xs font-bold">
37
+ {(post.User?.name || "A").charAt(0).toUpperCase()}
38
+ </div>
39
+ <span className="text-sm font-medium text-gray-700">{post.User?.name || "Author"}</span>
40
+ </div>
41
+ <span className="text-xs text-gray-500 font-medium">Read more →</span>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ </Link>
20
46
  );
21
47
  };
22
48
 
@@ -1,63 +1,81 @@
1
- import { NavLink } from "react-router-dom";
1
+ import { NavLink, useNavigate } from "react-router-dom";
2
2
  import { hasAnyPermission } from "../../utils/checkPermission";
3
+ import { LayoutDashboard, Users, FileText, PenTool, Settings, User, LogOut } from "lucide-react";
4
+ import { useUser } from "../../context/UserContext";
3
5
 
4
6
  const AdminSidebar = () => {
7
+ const { logout, user } = useUser();
8
+ const navigate = useNavigate();
9
+
10
+ const handleLogout = (e) => {
11
+ e.preventDefault();
12
+ logout();
13
+ navigate("/");
14
+ };
5
15
 
6
16
  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
- }
17
+ { path: "/admin", title: "Dashboard", icon: <LayoutDashboard size={20} /> },
18
+ { path: "/admin/users", title: "Manage Users", module: "USERS", actions: ["READ", "UPDATE", "DELETE"], icon: <Users size={20} /> },
19
+ { path: "/admin/blogs", title: "Manage Blogs", module: "POSTS", actions: ["READ", "UPDATE", "DELETE"], icon: <FileText size={20} /> },
20
+ { path: "/admin/create", title: "Create Post", module: "POSTS", actions: ["CREATE"], icon: <PenTool size={20} /> },
21
+ { path: "/admin/settings", title: "Settings", icon: <Settings size={20} /> }
39
22
  ];
40
23
 
41
24
  return (
42
- <aside className="sidebar">
43
- <h2 className="logo">Blogify</h2>
44
- <nav>
25
+ <aside className="w-64 bg-[#0f172a] text-slate-300 flex-shrink-0 flex flex-col min-h-screen border-r border-slate-800 z-20">
26
+ <div className="h-20 flex items-center gap-3 px-6 border-b border-slate-800/50">
27
+ <div className="w-8 h-8 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-lg flex items-center justify-center font-bold text-white shadow-lg">B</div>
28
+ <h2 className="text-xl font-bold tracking-tight text-white">Blogify</h2>
29
+ </div>
30
+
31
+ <nav className="flex-1 py-6 px-4 flex flex-col gap-1.5 overflow-y-auto">
32
+ <div className="text-xs font-semibold text-slate-500 uppercase tracking-wider mb-2 px-3">Menu</div>
45
33
  {menuItems.map((item, index) => {
46
34
  let hasAccess = true;
47
-
48
35
  if (item.module && item.actions) {
49
36
  hasAccess = hasAnyPermission(item.module, item.actions);
50
37
  }
51
-
52
38
  if (!hasAccess) return null;
53
39
 
54
40
  return (
55
- <NavLink key={index} to={item.path} end>
41
+ <NavLink
42
+ key={index}
43
+ to={item.path}
44
+ end
45
+ className={({ isActive }) => `
46
+ flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all duration-200 font-medium text-sm
47
+ ${isActive
48
+ ? 'bg-indigo-600/10 text-indigo-400'
49
+ : 'hover:bg-slate-800/50 hover:text-slate-100'
50
+ }
51
+ `}
52
+ >
53
+ {item.icon}
56
54
  {item.title}
57
55
  </NavLink>
58
56
  );
59
57
  })}
60
58
  </nav>
59
+
60
+ <div className="p-4 border-t border-slate-800/50 flex flex-col gap-3">
61
+ <div className="bg-slate-800/50 p-3 rounded-xl flex items-center gap-3 border border-slate-700/50">
62
+ <div className="w-9 h-9 rounded-full bg-indigo-500/20 flex items-center justify-center text-indigo-400">
63
+ <User size={18} />
64
+ </div>
65
+ <div className="flex-1 overflow-hidden">
66
+ <p className="text-sm font-semibold text-white truncate">{user?.name || 'Admin User'}</p>
67
+ <p className="text-xs text-slate-400 truncate">{user?.role || 'Administrator'}</p>
68
+ </div>
69
+ </div>
70
+
71
+ <button
72
+ onClick={handleLogout}
73
+ className="flex items-center justify-center gap-2 w-full px-4 py-2.5 bg-red-500/10 hover:bg-red-500/20 text-red-400 hover:text-red-300 rounded-lg text-sm font-medium transition-colors border border-red-500/20"
74
+ >
75
+ <LogOut size={16} />
76
+ Logout
77
+ </button>
78
+ </div>
61
79
  </aside>
62
80
  );
63
81
  };