farheen_blog_app 0.0.0 → 0.0.2
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 +1 -1
- package/src/App.css +260 -144
- package/src/components/Navbar.jsx +44 -19
- package/src/components/admin/AdminSidebar.jsx +31 -0
- package/src/components/admin/UserTable.jsx +29 -0
- package/src/layout/AdminLayout.jsx +24 -0
- package/src/middleware/ProtectedRoute.jsx +13 -0
- package/src/pages/Login.jsx +89 -21
- package/src/pages/Register.jsx +116 -0
- package/src/pages/admin/AdminDashboard.jsx +60 -0
- package/src/pages/admin/EditBlog.jsx +0 -0
- package/src/pages/admin/EditUser.jsx +0 -0
- package/src/pages/admin/ManageBlogs.jsx +47 -0
- package/src/pages/admin/ManageUsers.jsx +44 -0
- package/src/routes/AppRoutes.jsx +52 -28
- package/src/services/api.js +1 -1
- package/src/services/authService +15 -0
package/package.json
CHANGED
package/src/App.css
CHANGED
|
@@ -1,184 +1,300 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
border: 2px solid transparent;
|
|
8
|
-
transition: border-color 0.3s;
|
|
9
|
-
margin-bottom: 24px;
|
|
10
|
-
|
|
11
|
-
&:hover {
|
|
12
|
-
border-color: var(--accent-border);
|
|
13
|
-
}
|
|
14
|
-
&:focus-visible {
|
|
15
|
-
outline: 2px solid var(--accent);
|
|
16
|
-
outline-offset: 2px;
|
|
17
|
-
}
|
|
1
|
+
/* Reset */
|
|
2
|
+
|
|
3
|
+
* {
|
|
4
|
+
margin: 0;
|
|
5
|
+
padding: 0;
|
|
6
|
+
box-sizing: border-box;
|
|
18
7
|
}
|
|
19
8
|
|
|
20
|
-
|
|
21
|
-
|
|
9
|
+
body {
|
|
10
|
+
font-family: Inter, sans-serif;
|
|
11
|
+
background: #f5f7fb;
|
|
12
|
+
}
|
|
22
13
|
|
|
23
|
-
|
|
24
|
-
.framework,
|
|
25
|
-
.vite {
|
|
26
|
-
inset-inline: 0;
|
|
27
|
-
margin: 0 auto;
|
|
28
|
-
}
|
|
14
|
+
/* Layout */
|
|
29
15
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
16
|
+
.layout {
|
|
17
|
+
display: flex;
|
|
18
|
+
min-height: 100vh;
|
|
19
|
+
}
|
|
35
20
|
|
|
36
|
-
|
|
37
|
-
.vite {
|
|
38
|
-
position: absolute;
|
|
39
|
-
}
|
|
21
|
+
/* Sidebar */
|
|
40
22
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
23
|
+
.sidebar {
|
|
24
|
+
width: 260px;
|
|
25
|
+
background: #161c2a;
|
|
26
|
+
color: white;
|
|
27
|
+
padding: 20px;
|
|
28
|
+
flex-shrink: 0;
|
|
29
|
+
}
|
|
48
30
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
width: auto;
|
|
54
|
-
transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg)
|
|
55
|
-
scale(0.8);
|
|
56
|
-
}
|
|
31
|
+
.logo {
|
|
32
|
+
margin-bottom: 40px;
|
|
33
|
+
font-size: 24px;
|
|
34
|
+
font-weight: 600;
|
|
57
35
|
}
|
|
58
36
|
|
|
59
|
-
|
|
37
|
+
.sidebar nav {
|
|
60
38
|
display: flex;
|
|
61
39
|
flex-direction: column;
|
|
62
|
-
gap:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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;
|
|
71
54
|
}
|
|
72
55
|
|
|
73
|
-
|
|
56
|
+
/* Main Content */
|
|
57
|
+
|
|
58
|
+
.main-content {
|
|
59
|
+
flex: 1;
|
|
74
60
|
display: flex;
|
|
75
|
-
|
|
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;
|
|
76
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
|
+
}
|
|
77
186
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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;
|
|
84
201
|
}
|
|
85
202
|
|
|
86
|
-
.
|
|
87
|
-
|
|
88
|
-
width: 22px;
|
|
89
|
-
height: 22px;
|
|
203
|
+
.sidebar {
|
|
204
|
+
width: 100%;
|
|
90
205
|
}
|
|
91
206
|
|
|
92
|
-
|
|
207
|
+
.navbar {
|
|
93
208
|
flex-direction: column;
|
|
94
|
-
|
|
209
|
+
gap: 10px;
|
|
210
|
+
height: auto;
|
|
211
|
+
padding: 15px;
|
|
95
212
|
}
|
|
96
|
-
}
|
|
97
213
|
|
|
98
|
-
|
|
99
|
-
|
|
214
|
+
.navbar input {
|
|
215
|
+
width: 100%;
|
|
216
|
+
}
|
|
100
217
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
218
|
+
.cards {
|
|
219
|
+
grid-template-columns: 1fr;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.table-container {
|
|
223
|
+
overflow-x: auto;
|
|
104
224
|
}
|
|
105
225
|
}
|
|
106
226
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
227
|
+
/* auth css */
|
|
228
|
+
.auth-container {
|
|
229
|
+
min-height: 100vh;
|
|
230
|
+
background: #f5f7fb;
|
|
110
231
|
display: flex;
|
|
111
|
-
|
|
112
|
-
|
|
232
|
+
justify-content: center;
|
|
233
|
+
align-items: center;
|
|
234
|
+
padding: 20px;
|
|
235
|
+
}
|
|
113
236
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
+
}
|
|
117
245
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
display: flex;
|
|
124
|
-
padding: 6px 12px;
|
|
125
|
-
align-items: center;
|
|
126
|
-
gap: 8px;
|
|
127
|
-
text-decoration: none;
|
|
128
|
-
transition: box-shadow 0.3s;
|
|
129
|
-
|
|
130
|
-
&:hover {
|
|
131
|
-
box-shadow: var(--shadow);
|
|
132
|
-
}
|
|
133
|
-
.button-icon {
|
|
134
|
-
height: 18px;
|
|
135
|
-
width: 18px;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
246
|
+
.auth-card h1 {
|
|
247
|
+
text-align: center;
|
|
248
|
+
margin-bottom: 10px;
|
|
249
|
+
color: #111827;
|
|
250
|
+
}
|
|
138
251
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
252
|
+
.auth-card p {
|
|
253
|
+
text-align: center;
|
|
254
|
+
color: #6b7280;
|
|
255
|
+
margin-bottom: 30px;
|
|
256
|
+
}
|
|
143
257
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
258
|
+
.auth-form {
|
|
259
|
+
display: flex;
|
|
260
|
+
flex-direction: column;
|
|
261
|
+
gap: 15px;
|
|
262
|
+
}
|
|
147
263
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
264
|
+
.auth-form input {
|
|
265
|
+
padding: 14px;
|
|
266
|
+
border: 1px solid #d1d5db;
|
|
267
|
+
border-radius: 8px;
|
|
268
|
+
outline: none;
|
|
269
|
+
font-size: 15px;
|
|
154
270
|
}
|
|
155
271
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
border-top: 1px solid var(--border);
|
|
159
|
-
@media (max-width: 1024px) {
|
|
160
|
-
height: 48px;
|
|
161
|
-
}
|
|
272
|
+
.auth-form input:focus {
|
|
273
|
+
border-color: #2563eb;
|
|
162
274
|
}
|
|
163
275
|
|
|
164
|
-
.
|
|
165
|
-
|
|
166
|
-
|
|
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
|
+
}
|
|
167
286
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
position: absolute;
|
|
172
|
-
top: -4.5px;
|
|
173
|
-
border: 5px solid transparent;
|
|
174
|
-
}
|
|
287
|
+
.auth-form button:hover {
|
|
288
|
+
background: #1f2937;
|
|
289
|
+
}
|
|
175
290
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
&::after {
|
|
181
|
-
right: 0;
|
|
182
|
-
border-right-color: var(--border);
|
|
183
|
-
}
|
|
291
|
+
.auth-footer {
|
|
292
|
+
text-align: center;
|
|
293
|
+
margin-top: 20px;
|
|
184
294
|
}
|
|
295
|
+
|
|
296
|
+
.auth-footer a {
|
|
297
|
+
color: #2563eb;
|
|
298
|
+
text-decoration: none;
|
|
299
|
+
font-weight: 600;
|
|
300
|
+
}
|
|
@@ -1,25 +1,50 @@
|
|
|
1
1
|
import { Link } from "react-router-dom";
|
|
2
2
|
|
|
3
3
|
const Navbar = () => {
|
|
4
|
-
|
|
5
|
-
<div className="bg-black text-white px-6 py-4 flex justify-between">
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
</div>
|
|
22
|
-
|
|
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
|
+
return (
|
|
25
|
+
<header className="navbar">
|
|
26
|
+
<h1 className="text-2xl font-bold">
|
|
27
|
+
|
|
28
|
+
</h1>
|
|
29
|
+
|
|
30
|
+
<div className="navbar-right">
|
|
31
|
+
<input
|
|
32
|
+
type="text"
|
|
33
|
+
placeholder="Search..."
|
|
34
|
+
/>
|
|
35
|
+
<button>
|
|
36
|
+
🔔
|
|
37
|
+
</button>
|
|
38
|
+
|
|
39
|
+
<img
|
|
40
|
+
src="https://i.pravatar.cc/40"
|
|
41
|
+
alt="profile"
|
|
42
|
+
/>
|
|
43
|
+
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
</header>
|
|
47
|
+
);
|
|
23
48
|
};
|
|
24
49
|
|
|
25
50
|
export default Navbar;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { NavLink } from "react-router-dom";
|
|
2
|
+
|
|
3
|
+
const AdminSidebar = () => {
|
|
4
|
+
return (
|
|
5
|
+
<aside className="sidebar">
|
|
6
|
+
<h2 className="logo">Blogify</h2>
|
|
7
|
+
<nav>
|
|
8
|
+
|
|
9
|
+
<NavLink to="/admin">
|
|
10
|
+
Dashboard
|
|
11
|
+
</NavLink>
|
|
12
|
+
|
|
13
|
+
<NavLink to="/admin/users">
|
|
14
|
+
Users
|
|
15
|
+
</NavLink>
|
|
16
|
+
|
|
17
|
+
<NavLink to="/admin/blogs">
|
|
18
|
+
Blogs
|
|
19
|
+
</NavLink>
|
|
20
|
+
|
|
21
|
+
<NavLink to="/admin/settings">
|
|
22
|
+
Settings
|
|
23
|
+
</NavLink>
|
|
24
|
+
|
|
25
|
+
</nav>
|
|
26
|
+
|
|
27
|
+
</aside>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default AdminSidebar;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const UserTable = () => {
|
|
2
|
+
return (
|
|
3
|
+
<table className="user-table">
|
|
4
|
+
<thead>
|
|
5
|
+
<tr>
|
|
6
|
+
<th>Name</th>
|
|
7
|
+
<th>Email</th>
|
|
8
|
+
<th>Role</th>
|
|
9
|
+
<th>Action</th>
|
|
10
|
+
</tr>
|
|
11
|
+
</thead>
|
|
12
|
+
|
|
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>
|
|
24
|
+
</tbody>
|
|
25
|
+
</table>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default UserTable;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
|
|
2
|
+
import { Outlet } from "react-router-dom";
|
|
3
|
+
import AdminSidebar from "../components/admin/AdminSidebar";
|
|
4
|
+
import Navbar from "../components/Navbar";
|
|
5
|
+
|
|
6
|
+
const AdminLayout = () => {
|
|
7
|
+
return (
|
|
8
|
+
<div className="layout">
|
|
9
|
+
<AdminSidebar />
|
|
10
|
+
|
|
11
|
+
<div className="main-content">
|
|
12
|
+
<Navbar />
|
|
13
|
+
|
|
14
|
+
<div className="page-content">
|
|
15
|
+
<Outlet />
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default AdminLayout;
|
package/src/pages/Login.jsx
CHANGED
|
@@ -1,25 +1,93 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { Link, useNavigate } from "react-router-dom";
|
|
3
|
+
import { login } from "../services/authService";
|
|
4
|
+
|
|
1
5
|
const Login = () => {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
6
|
+
const navigate = useNavigate();
|
|
7
|
+
|
|
8
|
+
const [formData, setFormData] = useState({
|
|
9
|
+
email: "",
|
|
10
|
+
password: ""
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
const [loading, setloading] = useState(false);
|
|
14
|
+
|
|
15
|
+
const handleChange = (e) => {
|
|
16
|
+
setFormData({
|
|
17
|
+
...formData,
|
|
18
|
+
[e.target.name]: e.target.value
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const handleSubmit = async (e) => {
|
|
23
|
+
e.preventDefault()
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
setloading(true);
|
|
27
|
+
|
|
28
|
+
const payload = {
|
|
29
|
+
email: formData.email,
|
|
30
|
+
password: formData.password
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const response = await login(payload);
|
|
34
|
+
console.log(response)
|
|
35
|
+
const { token, user } = response;
|
|
36
|
+
|
|
37
|
+
localStorage.setItem("token", token);
|
|
38
|
+
localStorage.setItem("user", JSON.stringify(user));
|
|
39
|
+
|
|
40
|
+
navigate("/admin");
|
|
41
|
+
} catch (error) {
|
|
42
|
+
alert(
|
|
43
|
+
error.response?.data?.message ||
|
|
44
|
+
"Login failed"
|
|
45
|
+
);
|
|
46
|
+
} finally {
|
|
47
|
+
setloading(false);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div className="auth-container">
|
|
53
|
+
|
|
54
|
+
<div className="auth-card">
|
|
55
|
+
|
|
56
|
+
<h1>Welcome Back 👋</h1>
|
|
57
|
+
<p>Login to your account</p>
|
|
58
|
+
|
|
59
|
+
<form onSubmit={handleSubmit} className="auth-form">
|
|
60
|
+
|
|
61
|
+
<input
|
|
62
|
+
type="email"
|
|
63
|
+
name="email"
|
|
64
|
+
value={formData.email}
|
|
65
|
+
placeholder="Enter your email"
|
|
66
|
+
onChange={handleChange}
|
|
67
|
+
/>
|
|
68
|
+
|
|
69
|
+
<input
|
|
70
|
+
type="password"
|
|
71
|
+
name="password"
|
|
72
|
+
value={formData.password}
|
|
73
|
+
placeholder="Enter your password"
|
|
74
|
+
onChange={handleChange}
|
|
75
|
+
/>
|
|
76
|
+
|
|
77
|
+
<button disabled={loading} type="submit">
|
|
78
|
+
{loading ? "Logging in..." : "Login"}
|
|
79
|
+
</button>
|
|
80
|
+
</form>
|
|
81
|
+
|
|
82
|
+
<div className="auth-footer">
|
|
83
|
+
Don't have an account?
|
|
84
|
+
<Link to="/register"> Register</Link>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
23
91
|
};
|
|
24
92
|
|
|
25
93
|
export default Login;
|
package/src/pages/Register.jsx
CHANGED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Link, useNavigate } from "react-router-dom";
|
|
2
|
+
|
|
3
|
+
const Register = () => {
|
|
4
|
+
const navigate = useNavigate();
|
|
5
|
+
|
|
6
|
+
const [formData, setFormData] = useState({
|
|
7
|
+
name: "",
|
|
8
|
+
email: "",
|
|
9
|
+
password: "",
|
|
10
|
+
confirmPassword: ""
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const [loading, setLoading] = useState(false);
|
|
14
|
+
|
|
15
|
+
const handleChange = (e) => {
|
|
16
|
+
setFormData({
|
|
17
|
+
...formData,
|
|
18
|
+
[e.target.name]: e.target.value
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const handleSubmit = async (e) => {
|
|
23
|
+
e.preventDefault();
|
|
24
|
+
|
|
25
|
+
if (formData.password !== formData.confirmPassword) {
|
|
26
|
+
return alert("Passwords do not match");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
setLoading(true);
|
|
31
|
+
|
|
32
|
+
const payload = {
|
|
33
|
+
name: formData.name,
|
|
34
|
+
email: formData.email,
|
|
35
|
+
password: formData.password
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const response = await registerUser(payload);
|
|
39
|
+
|
|
40
|
+
alert(response.data.message);
|
|
41
|
+
navigate("/login");
|
|
42
|
+
|
|
43
|
+
} catch (error) {
|
|
44
|
+
|
|
45
|
+
alert(
|
|
46
|
+
error.response?.data?.message ||
|
|
47
|
+
"Registration failed"
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
} finally {
|
|
51
|
+
setLoading(false);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className="auth-container">
|
|
57
|
+
|
|
58
|
+
<div className="auth-card">
|
|
59
|
+
|
|
60
|
+
<h1>Create Account 🚀</h1>
|
|
61
|
+
<p>Join Blogify today</p>
|
|
62
|
+
|
|
63
|
+
<form onSubmit={handleSubmit} className="auth-form">
|
|
64
|
+
|
|
65
|
+
<input
|
|
66
|
+
type="text"
|
|
67
|
+
name="name"
|
|
68
|
+
placeholder="Full Name"
|
|
69
|
+
onChange={handleChange}
|
|
70
|
+
/>
|
|
71
|
+
|
|
72
|
+
<input
|
|
73
|
+
type="email"
|
|
74
|
+
name="email"
|
|
75
|
+
placeholder="Email Address"
|
|
76
|
+
onChange={handleChange}
|
|
77
|
+
/>
|
|
78
|
+
|
|
79
|
+
<input
|
|
80
|
+
type="text"
|
|
81
|
+
name="companyName"
|
|
82
|
+
placeholder="Company Name"
|
|
83
|
+
onChange={handleChange}
|
|
84
|
+
/>
|
|
85
|
+
|
|
86
|
+
<input
|
|
87
|
+
type="text"
|
|
88
|
+
name="companyEmail"
|
|
89
|
+
placeholder="Company Email"
|
|
90
|
+
onChange={handleChange}
|
|
91
|
+
/>
|
|
92
|
+
|
|
93
|
+
<input
|
|
94
|
+
type="password"
|
|
95
|
+
name="password"
|
|
96
|
+
placeholder="Password"
|
|
97
|
+
onChange={handleChange}
|
|
98
|
+
/>
|
|
99
|
+
|
|
100
|
+
<button disabled={loading}>
|
|
101
|
+
{loading ? "Registering..." : "Register"}
|
|
102
|
+
</button>
|
|
103
|
+
</form>
|
|
104
|
+
|
|
105
|
+
<div className="auth-footer">
|
|
106
|
+
Already have an account?
|
|
107
|
+
<Link to="/login"> Login</Link>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export default Register;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import api from "../../services/api";
|
|
3
|
+
import AdminSidebar from "../../components/admin/AdminSidebar";
|
|
4
|
+
|
|
5
|
+
const AdminDashboard = () => {
|
|
6
|
+
|
|
7
|
+
const [data, setData] = useState({});
|
|
8
|
+
|
|
9
|
+
const getDashboard = async () => {
|
|
10
|
+
try {
|
|
11
|
+
|
|
12
|
+
const res = await api.get("/admin/dashboard");
|
|
13
|
+
|
|
14
|
+
setData(res.data);
|
|
15
|
+
|
|
16
|
+
} catch (error) {
|
|
17
|
+
console.log(error);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
getDashboard();
|
|
23
|
+
}, []);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className="dashboard-container">
|
|
27
|
+
<main className="dashboard-content">
|
|
28
|
+
<h1>Dashboard</h1>
|
|
29
|
+
|
|
30
|
+
<div className="cards">
|
|
31
|
+
|
|
32
|
+
<div className="card">
|
|
33
|
+
<h3>Total Users</h3>
|
|
34
|
+
<p>150</p>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<div className="card">
|
|
38
|
+
<h3>Total Blogs</h3>
|
|
39
|
+
<p>450</p>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div className="card">
|
|
43
|
+
<h3>Published Blogs</h3>
|
|
44
|
+
<p>400</p>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div className="card">
|
|
48
|
+
<h3>Draft Blogs</h3>
|
|
49
|
+
<p>50</p>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
</main>
|
|
55
|
+
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export default AdminDashboard;
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import api from "../../services/api";
|
|
3
|
+
|
|
4
|
+
const ManageBlogs = () => {
|
|
5
|
+
|
|
6
|
+
const [blogs,setBlogs] = useState([]);
|
|
7
|
+
|
|
8
|
+
const getBlogs = async () => {
|
|
9
|
+
try{
|
|
10
|
+
|
|
11
|
+
const res = await api.get("/admin/blogs");
|
|
12
|
+
|
|
13
|
+
setBlogs(res.data);
|
|
14
|
+
|
|
15
|
+
}catch(error){
|
|
16
|
+
console.log(error);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
useEffect(()=>{
|
|
21
|
+
getBlogs();
|
|
22
|
+
},[]);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div>
|
|
26
|
+
|
|
27
|
+
<h1>Manage Blogs</h1>
|
|
28
|
+
|
|
29
|
+
{
|
|
30
|
+
blogs.map((blog)=>(
|
|
31
|
+
<div key={blog._id}>
|
|
32
|
+
|
|
33
|
+
<h3>{blog.title}</h3>
|
|
34
|
+
|
|
35
|
+
<p>
|
|
36
|
+
{blog.createdBy?.name}
|
|
37
|
+
</p>
|
|
38
|
+
|
|
39
|
+
</div>
|
|
40
|
+
))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export default ManageBlogs;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import api from "../../services/api";
|
|
3
|
+
|
|
4
|
+
const ManageUsers = () => {
|
|
5
|
+
|
|
6
|
+
const [users,setUsers] = useState([]);
|
|
7
|
+
|
|
8
|
+
const getUsers = async () => {
|
|
9
|
+
try{
|
|
10
|
+
|
|
11
|
+
const res = await api.get("/admin/users");
|
|
12
|
+
|
|
13
|
+
setUsers(res.data);
|
|
14
|
+
|
|
15
|
+
}catch(error){
|
|
16
|
+
console.log(error);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
useEffect(()=>{
|
|
21
|
+
getUsers();
|
|
22
|
+
},[]);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div>
|
|
26
|
+
|
|
27
|
+
<h1>Manage Users</h1>
|
|
28
|
+
|
|
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
|
+
}
|
|
39
|
+
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export default ManageUsers;
|
package/src/routes/AppRoutes.jsx
CHANGED
|
@@ -1,40 +1,64 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
BrowserRouter,
|
|
3
|
+
Routes,
|
|
4
|
+
Route,
|
|
5
5
|
} from "react-router-dom";
|
|
6
6
|
|
|
7
7
|
import Navbar from "../components/Navbar";
|
|
8
|
-
|
|
9
8
|
import Home from "../pages/Home";
|
|
10
|
-
|
|
11
9
|
import Login from "../pages/Login";
|
|
12
|
-
|
|
13
10
|
import CreatePost from "../pages/CreatePost";
|
|
11
|
+
import ManageUsers from "../pages/admin/ManageUsers";
|
|
12
|
+
import ManageBlogs from "../pages/admin/ManageBlogs";
|
|
13
|
+
import AdminDashboard from "../pages/admin/AdminDashboard";
|
|
14
|
+
import AdminLayout from "../layout/AdminLayout";
|
|
15
|
+
import Register from "../pages/Register";
|
|
14
16
|
|
|
15
17
|
const AppRoutes = () => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
18
|
+
return (
|
|
19
|
+
<BrowserRouter>
|
|
20
|
+
{/* <Navbar /> */}
|
|
21
|
+
|
|
22
|
+
<Routes>
|
|
23
|
+
<Route
|
|
24
|
+
path="/"
|
|
25
|
+
element={<Home />}
|
|
26
|
+
/>
|
|
27
|
+
|
|
28
|
+
<Route
|
|
29
|
+
path="/register"
|
|
30
|
+
element={<Register />}
|
|
31
|
+
/>
|
|
32
|
+
|
|
33
|
+
<Route
|
|
34
|
+
path="/login"
|
|
35
|
+
element={<Login />}
|
|
36
|
+
/>
|
|
37
|
+
|
|
38
|
+
<Route
|
|
39
|
+
path="/create"
|
|
40
|
+
element={<CreatePost />}
|
|
41
|
+
/>
|
|
42
|
+
|
|
43
|
+
<Route path="/admin" element={<AdminLayout />}>
|
|
44
|
+
<Route index element={<AdminDashboard />} />
|
|
45
|
+
<Route
|
|
46
|
+
path="users"
|
|
47
|
+
element={<ManageUsers />}
|
|
48
|
+
/>
|
|
49
|
+
<Route
|
|
50
|
+
path="blogs"
|
|
51
|
+
element={<ManageBlogs />}
|
|
52
|
+
/>
|
|
53
|
+
{/* <Route
|
|
54
|
+
path="settings"
|
|
55
|
+
element={<Settings />}
|
|
56
|
+
/> */}
|
|
57
|
+
</Route>
|
|
58
|
+
|
|
59
|
+
</Routes>
|
|
60
|
+
</BrowserRouter>
|
|
61
|
+
);
|
|
38
62
|
};
|
|
39
63
|
|
|
40
64
|
export default AppRoutes;
|
package/src/services/api.js
CHANGED
|
@@ -0,0 +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
|
+
};
|