farheen_blog_app 0.0.1 → 0.0.3
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 +217 -151
- package/src/components/Navbar.jsx +44 -43
- package/src/components/admin/AdminSidebar.jsx +11 -17
- package/src/layout/AdminLayout.jsx +6 -3
- package/src/pages/CreatePost.jsx +118 -72
- package/src/pages/Login.jsx +89 -21
- package/src/pages/Register.jsx +116 -0
- package/src/pages/admin/AdminDashboard.jsx +1 -13
- package/src/routes/AppRoutes.jsx +24 -25
- package/src/services/api.js +1 -1
- package/src/services/authService +15 -0
package/package.json
CHANGED
package/src/App.css
CHANGED
|
@@ -1,234 +1,300 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/* Reset */
|
|
2
|
+
|
|
3
|
+
* {
|
|
4
|
+
margin: 0;
|
|
5
|
+
padding: 0;
|
|
6
|
+
box-sizing: border-box;
|
|
5
7
|
}
|
|
6
8
|
|
|
7
|
-
body{
|
|
8
|
-
font-family:Inter,sans-serif;
|
|
9
|
-
background
|
|
9
|
+
body {
|
|
10
|
+
font-family: Inter, sans-serif;
|
|
11
|
+
background: #f5f7fb;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
/* Layout */
|
|
15
|
+
|
|
16
|
+
.layout {
|
|
17
|
+
display: flex;
|
|
18
|
+
min-height: 100vh;
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
/* Sidebar */
|
|
18
22
|
|
|
19
|
-
.sidebar{
|
|
20
|
-
width:260px;
|
|
21
|
-
background
|
|
22
|
-
color:white;
|
|
23
|
-
padding:20px;
|
|
23
|
+
.sidebar {
|
|
24
|
+
width: 260px;
|
|
25
|
+
background: #161c2a;
|
|
26
|
+
color: white;
|
|
27
|
+
padding: 20px;
|
|
28
|
+
flex-shrink: 0;
|
|
24
29
|
}
|
|
25
30
|
|
|
26
|
-
.logo{
|
|
27
|
-
margin-bottom:40px;
|
|
31
|
+
.logo {
|
|
32
|
+
margin-bottom: 40px;
|
|
33
|
+
font-size: 24px;
|
|
34
|
+
font-weight: 600;
|
|
28
35
|
}
|
|
29
36
|
|
|
30
|
-
.sidebar nav{
|
|
31
|
-
display:flex;
|
|
32
|
-
flex-direction:column;
|
|
33
|
-
gap:10px;
|
|
37
|
+
.sidebar nav {
|
|
38
|
+
display: flex;
|
|
39
|
+
flex-direction: column;
|
|
40
|
+
gap: 10px;
|
|
34
41
|
}
|
|
35
42
|
|
|
36
|
-
.sidebar a{
|
|
37
|
-
text-decoration:none;
|
|
38
|
-
color:white;
|
|
39
|
-
padding:12px;
|
|
40
|
-
border-radius:8px;
|
|
43
|
+
.sidebar a {
|
|
44
|
+
text-decoration: none;
|
|
45
|
+
color: white;
|
|
46
|
+
padding: 12px 16px;
|
|
47
|
+
border-radius: 8px;
|
|
48
|
+
transition: 0.3s;
|
|
41
49
|
}
|
|
42
50
|
|
|
43
|
-
.sidebar a:hover
|
|
44
|
-
|
|
51
|
+
.sidebar a:hover,
|
|
52
|
+
.sidebar a.active {
|
|
53
|
+
background: #1f2937;
|
|
45
54
|
}
|
|
46
55
|
|
|
47
56
|
/* Main Content */
|
|
48
57
|
|
|
49
|
-
.main-content{
|
|
50
|
-
flex:1;
|
|
58
|
+
.main-content {
|
|
59
|
+
flex: 1;
|
|
60
|
+
display: flex;
|
|
61
|
+
flex-direction: column;
|
|
51
62
|
}
|
|
52
63
|
|
|
53
64
|
/* Navbar */
|
|
54
65
|
|
|
55
|
-
.navbar{
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
display:flex;
|
|
59
|
-
justify-content:space-between;
|
|
60
|
-
align-items:center;
|
|
61
|
-
|
|
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);
|
|
62
74
|
}
|
|
63
75
|
|
|
64
|
-
.navbar input{
|
|
65
|
-
width:350px;
|
|
66
|
-
padding:10px;
|
|
67
|
-
border:1px solid #
|
|
68
|
-
border-radius:8px;
|
|
76
|
+
.navbar input {
|
|
77
|
+
width: 350px;
|
|
78
|
+
padding: 10px 15px;
|
|
79
|
+
border: 1px solid #d1d5db;
|
|
80
|
+
border-radius: 8px;
|
|
81
|
+
outline: none;
|
|
69
82
|
}
|
|
70
83
|
|
|
71
|
-
.navbar-right{
|
|
72
|
-
display:flex;
|
|
73
|
-
align-items:center;
|
|
74
|
-
gap:15px;
|
|
84
|
+
.navbar-right {
|
|
85
|
+
display: flex;
|
|
86
|
+
align-items: center;
|
|
87
|
+
gap: 15px;
|
|
88
|
+
color: white;
|
|
75
89
|
}
|
|
76
90
|
|
|
77
|
-
.navbar-right
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
91
|
+
.navbar-right button {
|
|
92
|
+
border: none;
|
|
93
|
+
background: transparent;
|
|
94
|
+
cursor: pointer;
|
|
95
|
+
font-size: 18px;
|
|
81
96
|
}
|
|
82
97
|
|
|
83
|
-
|
|
98
|
+
.navbar-right img {
|
|
99
|
+
width: 40px;
|
|
100
|
+
height: 40px;
|
|
101
|
+
border-radius: 50%;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* Page Content */
|
|
84
105
|
|
|
85
|
-
.
|
|
86
|
-
|
|
106
|
+
.page-content {
|
|
107
|
+
flex: 1;
|
|
108
|
+
padding: 30px;
|
|
87
109
|
}
|
|
88
110
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
111
|
+
/* Dashboard */
|
|
112
|
+
|
|
113
|
+
.dashboard h1 {
|
|
114
|
+
margin-bottom: 25px;
|
|
115
|
+
color: #111827;
|
|
93
116
|
}
|
|
94
117
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
118
|
+
/* Cards */
|
|
119
|
+
|
|
120
|
+
.cards {
|
|
121
|
+
display: grid;
|
|
122
|
+
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
123
|
+
gap: 20px;
|
|
100
124
|
}
|
|
101
125
|
|
|
102
|
-
.card
|
|
103
|
-
|
|
104
|
-
|
|
126
|
+
.card {
|
|
127
|
+
background: white;
|
|
128
|
+
padding: 25px;
|
|
129
|
+
border-radius: 12px;
|
|
130
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
|
|
105
131
|
}
|
|
106
132
|
|
|
107
|
-
.card
|
|
108
|
-
color
|
|
133
|
+
.card h4 {
|
|
134
|
+
color: #6b7280;
|
|
135
|
+
margin-bottom: 10px;
|
|
136
|
+
font-weight: 500;
|
|
109
137
|
}
|
|
110
138
|
|
|
111
|
-
.
|
|
112
|
-
|
|
113
|
-
|
|
139
|
+
.card h2 {
|
|
140
|
+
color: #111827;
|
|
141
|
+
font-size: 32px;
|
|
114
142
|
}
|
|
115
143
|
|
|
116
|
-
/*
|
|
144
|
+
/* Tables */
|
|
117
145
|
|
|
118
|
-
.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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);
|
|
123
152
|
}
|
|
124
153
|
|
|
125
|
-
.
|
|
126
|
-
|
|
154
|
+
.user-table {
|
|
155
|
+
width: 100%;
|
|
156
|
+
border-collapse: collapse;
|
|
127
157
|
}
|
|
128
158
|
|
|
129
|
-
.
|
|
130
|
-
|
|
159
|
+
.user-table th {
|
|
160
|
+
background: #111827;
|
|
161
|
+
color: white;
|
|
162
|
+
padding: 15px;
|
|
163
|
+
text-align: left;
|
|
131
164
|
}
|
|
132
165
|
|
|
133
|
-
.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
border-radius:6px;
|
|
137
|
-
margin-bottom:10px;
|
|
138
|
-
transition:0.3s;
|
|
166
|
+
.user-table td {
|
|
167
|
+
padding: 15px;
|
|
168
|
+
border-bottom: 1px solid #e5e7eb;
|
|
139
169
|
}
|
|
140
170
|
|
|
141
|
-
.
|
|
142
|
-
|
|
171
|
+
.user-table tr:hover {
|
|
172
|
+
background: #f9fafb;
|
|
143
173
|
}
|
|
144
174
|
|
|
145
|
-
/*
|
|
175
|
+
/* Buttons */
|
|
146
176
|
|
|
147
|
-
.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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;
|
|
151
185
|
}
|
|
152
186
|
|
|
153
|
-
.
|
|
154
|
-
|
|
187
|
+
.delete-btn {
|
|
188
|
+
background: #dc2626;
|
|
189
|
+
color: white;
|
|
190
|
+
border: none;
|
|
191
|
+
padding: 8px 14px;
|
|
192
|
+
border-radius: 6px;
|
|
193
|
+
cursor: pointer;
|
|
155
194
|
}
|
|
156
195
|
|
|
157
|
-
/*
|
|
196
|
+
/* Mobile */
|
|
158
197
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
198
|
+
@media (max-width: 768px) {
|
|
199
|
+
.layout {
|
|
200
|
+
flex-direction: column;
|
|
201
|
+
}
|
|
164
202
|
|
|
165
|
-
.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
+
}
|
|
171
217
|
|
|
172
|
-
.
|
|
173
|
-
|
|
174
|
-
|
|
218
|
+
.cards {
|
|
219
|
+
grid-template-columns: 1fr;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.table-container {
|
|
223
|
+
overflow-x: auto;
|
|
224
|
+
}
|
|
175
225
|
}
|
|
176
226
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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;
|
|
180
235
|
}
|
|
181
236
|
|
|
182
|
-
.
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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);
|
|
187
244
|
}
|
|
188
245
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
padding:12px;
|
|
194
|
-
text-align:left;
|
|
246
|
+
.auth-card h1 {
|
|
247
|
+
text-align: center;
|
|
248
|
+
margin-bottom: 10px;
|
|
249
|
+
color: #111827;
|
|
195
250
|
}
|
|
196
251
|
|
|
197
|
-
.
|
|
198
|
-
|
|
199
|
-
|
|
252
|
+
.auth-card p {
|
|
253
|
+
text-align: center;
|
|
254
|
+
color: #6b7280;
|
|
255
|
+
margin-bottom: 30px;
|
|
200
256
|
}
|
|
201
257
|
|
|
202
|
-
.
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
padding:8px 15px;
|
|
207
|
-
border-radius:5px;
|
|
208
|
-
cursor:pointer;
|
|
258
|
+
.auth-form {
|
|
259
|
+
display: flex;
|
|
260
|
+
flex-direction: column;
|
|
261
|
+
gap: 15px;
|
|
209
262
|
}
|
|
210
263
|
|
|
211
|
-
.
|
|
212
|
-
|
|
264
|
+
.auth-form input {
|
|
265
|
+
padding: 14px;
|
|
266
|
+
border: 1px solid #d1d5db;
|
|
267
|
+
border-radius: 8px;
|
|
268
|
+
outline: none;
|
|
269
|
+
font-size: 15px;
|
|
213
270
|
}
|
|
214
271
|
|
|
215
|
-
|
|
216
|
-
|
|
272
|
+
.auth-form input:focus {
|
|
273
|
+
border-color: #2563eb;
|
|
274
|
+
}
|
|
217
275
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
+
}
|
|
221
286
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
287
|
+
.auth-form button:hover {
|
|
288
|
+
background: #1f2937;
|
|
289
|
+
}
|
|
225
290
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
291
|
+
.auth-footer {
|
|
292
|
+
text-align: center;
|
|
293
|
+
margin-top: 20px;
|
|
294
|
+
}
|
|
229
295
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
296
|
+
.auth-footer a {
|
|
297
|
+
color: #2563eb;
|
|
298
|
+
text-decoration: none;
|
|
299
|
+
font-weight: 600;
|
|
234
300
|
}
|
|
@@ -1,49 +1,50 @@
|
|
|
1
1
|
import { Link } from "react-router-dom";
|
|
2
2
|
|
|
3
3
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
+
);
|
|
47
48
|
};
|
|
48
49
|
|
|
49
50
|
export default Navbar;
|
|
@@ -1,32 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NavLink } from "react-router-dom";
|
|
2
2
|
|
|
3
3
|
const AdminSidebar = () => {
|
|
4
4
|
return (
|
|
5
5
|
<aside className="sidebar">
|
|
6
|
-
|
|
7
6
|
<h2 className="logo">Blogify</h2>
|
|
8
|
-
|
|
9
7
|
<nav>
|
|
10
8
|
|
|
11
|
-
<
|
|
9
|
+
<NavLink to="/admin">
|
|
12
10
|
Dashboard
|
|
13
|
-
</
|
|
11
|
+
</NavLink>
|
|
14
12
|
|
|
15
|
-
<
|
|
16
|
-
Blogs
|
|
17
|
-
</Link>
|
|
18
|
-
|
|
19
|
-
<Link to="/admin/users">
|
|
13
|
+
<NavLink to="/admin/users">
|
|
20
14
|
Users
|
|
21
|
-
</
|
|
15
|
+
</NavLink>
|
|
22
16
|
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
</
|
|
17
|
+
<NavLink to="/admin/blogs">
|
|
18
|
+
Blogs
|
|
19
|
+
</NavLink>
|
|
26
20
|
|
|
27
|
-
<
|
|
21
|
+
<NavLink to="/admin/settings">
|
|
28
22
|
Settings
|
|
29
|
-
</
|
|
23
|
+
</NavLink>
|
|
30
24
|
|
|
31
25
|
</nav>
|
|
32
26
|
|
|
@@ -34,4 +28,4 @@ const AdminSidebar = () => {
|
|
|
34
28
|
);
|
|
35
29
|
};
|
|
36
30
|
|
|
37
|
-
export default
|
|
31
|
+
export default AdminSidebar;
|
|
@@ -4,14 +4,17 @@ import AdminSidebar from "../components/admin/AdminSidebar";
|
|
|
4
4
|
import Navbar from "../components/Navbar";
|
|
5
5
|
|
|
6
6
|
const AdminLayout = () => {
|
|
7
|
-
|
|
7
|
+
return (
|
|
8
8
|
<div className="layout">
|
|
9
|
-
|
|
10
9
|
<AdminSidebar />
|
|
11
10
|
|
|
12
11
|
<div className="main-content">
|
|
13
12
|
<Navbar />
|
|
14
|
-
|
|
13
|
+
|
|
14
|
+
<div className="page-content">
|
|
15
|
+
<Outlet />
|
|
16
|
+
</div>
|
|
17
|
+
|
|
15
18
|
</div>
|
|
16
19
|
|
|
17
20
|
</div>
|
package/src/pages/CreatePost.jsx
CHANGED
|
@@ -1,79 +1,125 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
|
-
|
|
3
2
|
import { createPost } from "../services/postService";
|
|
4
3
|
|
|
5
4
|
const CreatePost = () => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
e
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
5
|
+
const [formData, setFormData] = useState({
|
|
6
|
+
title: "",
|
|
7
|
+
content: "",
|
|
8
|
+
image: null
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const [loading, setLoading] =
|
|
12
|
+
useState(false);
|
|
13
|
+
|
|
14
|
+
const handleChange = (e) => {
|
|
15
|
+
const { name, value, files } =
|
|
16
|
+
e.target;
|
|
17
|
+
|
|
18
|
+
setFormData({
|
|
19
|
+
...formData,
|
|
20
|
+
[name]: files ? files[0] : value
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const handleSubmit = async (e) => {
|
|
25
|
+
e.preventDefault();
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
setLoading(true);
|
|
29
|
+
|
|
30
|
+
const data = new FormData();
|
|
31
|
+
|
|
32
|
+
data.append(
|
|
33
|
+
"title",
|
|
34
|
+
formData.title
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
data.append(
|
|
38
|
+
"content",
|
|
39
|
+
formData.content
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
if (formData.image) {
|
|
43
|
+
data.append(
|
|
44
|
+
"image",
|
|
45
|
+
formData.image
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
await createPost(data);
|
|
50
|
+
|
|
51
|
+
alert("Post Created");
|
|
52
|
+
|
|
53
|
+
setFormData({
|
|
54
|
+
title: "",
|
|
55
|
+
content: "",
|
|
56
|
+
image: null
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
} catch (error) {
|
|
60
|
+
|
|
61
|
+
console.log(error);
|
|
62
|
+
|
|
63
|
+
alert(
|
|
64
|
+
error.response?.data?.message ||
|
|
65
|
+
"Something went wrong"
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
} finally {
|
|
69
|
+
setLoading(false);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<div className="max-w-xl mx-auto mt-10">
|
|
75
|
+
|
|
76
|
+
<form
|
|
77
|
+
onSubmit={handleSubmit}
|
|
78
|
+
className="flex flex-col gap-4"
|
|
79
|
+
>
|
|
80
|
+
|
|
81
|
+
<input
|
|
82
|
+
type="text"
|
|
83
|
+
name="title"
|
|
84
|
+
placeholder="Enter Title"
|
|
85
|
+
className="border p-3 rounded"
|
|
86
|
+
value={formData.title}
|
|
87
|
+
onChange={handleChange}
|
|
88
|
+
required
|
|
89
|
+
/>
|
|
90
|
+
|
|
91
|
+
<textarea
|
|
92
|
+
name="content"
|
|
93
|
+
placeholder="Enter Content"
|
|
94
|
+
className="border p-3 rounded h-40"
|
|
95
|
+
value={formData.content}
|
|
96
|
+
onChange={handleChange}
|
|
97
|
+
required
|
|
98
|
+
/>
|
|
99
|
+
|
|
100
|
+
<input
|
|
101
|
+
type="file"
|
|
102
|
+
name="image"
|
|
103
|
+
accept="image/*"
|
|
104
|
+
onChange={handleChange}
|
|
105
|
+
/>
|
|
106
|
+
|
|
107
|
+
<button
|
|
108
|
+
type="submit"
|
|
109
|
+
disabled={loading}
|
|
110
|
+
className="bg-black text-white p-3 rounded"
|
|
111
|
+
>
|
|
112
|
+
{
|
|
113
|
+
loading
|
|
114
|
+
? "Creating..."
|
|
115
|
+
: "Create Post"
|
|
116
|
+
}
|
|
117
|
+
</button>
|
|
118
|
+
|
|
119
|
+
</form>
|
|
120
|
+
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
77
123
|
};
|
|
78
124
|
|
|
79
125
|
export default CreatePost;
|
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;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
2
|
import api from "../../services/api";
|
|
3
|
+
import AdminSidebar from "../../components/admin/AdminSidebar";
|
|
3
4
|
|
|
4
5
|
const AdminDashboard = () => {
|
|
5
6
|
|
|
@@ -23,20 +24,7 @@ const AdminDashboard = () => {
|
|
|
23
24
|
|
|
24
25
|
return (
|
|
25
26
|
<div className="dashboard-container">
|
|
26
|
-
|
|
27
|
-
<aside className="sidebar">
|
|
28
|
-
<h2>Admin Panel</h2>
|
|
29
|
-
|
|
30
|
-
<ul>
|
|
31
|
-
<li>Dashboard</li>
|
|
32
|
-
<li>Users</li>
|
|
33
|
-
<li>Blogs</li>
|
|
34
|
-
<li>Settings</li>
|
|
35
|
-
</ul>
|
|
36
|
-
</aside>
|
|
37
|
-
|
|
38
27
|
<main className="dashboard-content">
|
|
39
|
-
|
|
40
28
|
<h1>Dashboard</h1>
|
|
41
29
|
|
|
42
30
|
<div className="cards">
|
package/src/routes/AppRoutes.jsx
CHANGED
|
@@ -11,11 +11,13 @@ import CreatePost from "../pages/CreatePost";
|
|
|
11
11
|
import ManageUsers from "../pages/admin/ManageUsers";
|
|
12
12
|
import ManageBlogs from "../pages/admin/ManageBlogs";
|
|
13
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
18
|
return (
|
|
17
19
|
<BrowserRouter>
|
|
18
|
-
<Navbar />
|
|
20
|
+
{/* <Navbar /> */}
|
|
19
21
|
|
|
20
22
|
<Routes>
|
|
21
23
|
<Route
|
|
@@ -23,6 +25,11 @@ const AppRoutes = () => {
|
|
|
23
25
|
element={<Home />}
|
|
24
26
|
/>
|
|
25
27
|
|
|
28
|
+
<Route
|
|
29
|
+
path="/register"
|
|
30
|
+
element={<Register />}
|
|
31
|
+
/>
|
|
32
|
+
|
|
26
33
|
<Route
|
|
27
34
|
path="/login"
|
|
28
35
|
element={<Login />}
|
|
@@ -33,31 +40,23 @@ const AppRoutes = () => {
|
|
|
33
40
|
element={<CreatePost />}
|
|
34
41
|
/>
|
|
35
42
|
|
|
36
|
-
<Route
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
<
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
<Route
|
|
53
|
-
path="/admin/blogs"
|
|
54
|
-
element={
|
|
55
|
-
<ManageBlogs />
|
|
56
|
-
}
|
|
57
|
-
/>
|
|
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
|
+
|
|
58
59
|
</Routes>
|
|
59
|
-
|
|
60
|
-
|
|
61
60
|
</BrowserRouter>
|
|
62
61
|
);
|
|
63
62
|
};
|
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
|
+
};
|