farheen_blog_app 0.0.2 → 0.0.4
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
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
useContext,
|
|
4
|
+
useEffect,
|
|
5
|
+
useState
|
|
6
|
+
} from "react";
|
|
7
|
+
|
|
8
|
+
const UserContext = createContext();
|
|
9
|
+
|
|
10
|
+
export const UserProvider = ({ children }) => {
|
|
11
|
+
const [user, setUser] = useState(null);
|
|
12
|
+
const [token, setToken] = useState(null);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
|
|
16
|
+
const storedUser =
|
|
17
|
+
localStorage.getItem(
|
|
18
|
+
"user"
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const storedToken =
|
|
22
|
+
localStorage.getItem(
|
|
23
|
+
"token"
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
if (
|
|
27
|
+
storedUser &&
|
|
28
|
+
storedToken
|
|
29
|
+
) {
|
|
30
|
+
|
|
31
|
+
setUser(JSON.parse(storedUser));
|
|
32
|
+
setToken(storedToken);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
const login = (userData, jwtToken) => {
|
|
38
|
+
setUser(userData);
|
|
39
|
+
setToken(jwtToken);
|
|
40
|
+
|
|
41
|
+
localStorage.setItem(
|
|
42
|
+
"user",
|
|
43
|
+
JSON.stringify(
|
|
44
|
+
userData
|
|
45
|
+
)
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
localStorage.setItem(
|
|
49
|
+
"token",
|
|
50
|
+
jwtToken
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const logout = () => {
|
|
55
|
+
|
|
56
|
+
setUser(null);
|
|
57
|
+
|
|
58
|
+
setToken(null);
|
|
59
|
+
|
|
60
|
+
localStorage.removeItem(
|
|
61
|
+
"user"
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
localStorage.removeItem(
|
|
65
|
+
"token"
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<UserContext.Provider
|
|
71
|
+
value={{
|
|
72
|
+
user,
|
|
73
|
+
token,
|
|
74
|
+
login,
|
|
75
|
+
logout
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
{children}
|
|
79
|
+
</UserContext.Provider>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const useUser = () => {
|
|
84
|
+
return useContext(UserContext);
|
|
85
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useState,
|
|
3
|
+
useEffect
|
|
4
|
+
} from "react";
|
|
5
|
+
|
|
6
|
+
const useDebounce = (
|
|
7
|
+
value,
|
|
8
|
+
delay
|
|
9
|
+
) => {
|
|
10
|
+
|
|
11
|
+
const [
|
|
12
|
+
debouncedValue,
|
|
13
|
+
setDebouncedValue
|
|
14
|
+
] = useState(value);
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
|
|
18
|
+
const timer =
|
|
19
|
+
setTimeout(() => {
|
|
20
|
+
|
|
21
|
+
setDebouncedValue(
|
|
22
|
+
value
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
}, delay);
|
|
26
|
+
|
|
27
|
+
return () =>
|
|
28
|
+
clearTimeout(timer);
|
|
29
|
+
|
|
30
|
+
}, [value, delay]);
|
|
31
|
+
|
|
32
|
+
return debouncedValue;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default useDebounce;
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
Navigate,
|
|
3
|
+
Outlet
|
|
4
|
+
}
|
|
5
|
+
from "react-router-dom";
|
|
4
6
|
|
|
5
|
-
export
|
|
6
|
-
const token = localStorage.getItem("token");
|
|
7
|
+
export function ProtectedRoute(){
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const token =
|
|
10
|
+
localStorage.getItem(
|
|
11
|
+
"token"
|
|
12
|
+
);
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
return token
|
|
15
|
+
? <Outlet />
|
|
16
|
+
: <Navigate to="/login"/>
|
|
13
17
|
}
|
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;
|
|
@@ -1,33 +1,43 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
2
|
import api from "../../services/api";
|
|
3
|
+
import useDebounce from "../../hooks/useDebounce";
|
|
3
4
|
|
|
4
5
|
const ManageBlogs = () => {
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
// for pagination and debounce search
|
|
8
|
+
const [page, setPage] = useState(1);
|
|
9
|
+
const [totalPage, setTotalPage] = useState(1);
|
|
10
|
+
const [search, setSearch] = useState("");
|
|
11
|
+
|
|
12
|
+
const debouncedSearch = useDebounce(search, 500);
|
|
13
|
+
|
|
14
|
+
const [blogs, setBlogs] = useState([]);
|
|
7
15
|
|
|
8
16
|
const getBlogs = async () => {
|
|
9
|
-
try{
|
|
17
|
+
try {
|
|
10
18
|
|
|
11
|
-
const res = await api.get(
|
|
19
|
+
const res = await api.get(`/admin/blogs/posts?page=${page}&search=${debouncedSearch}`);
|
|
12
20
|
|
|
13
21
|
setBlogs(res.data);
|
|
22
|
+
setTotalPage(res.data.totalPges);
|
|
14
23
|
|
|
15
|
-
}catch(error){
|
|
24
|
+
} catch (error) {
|
|
16
25
|
console.log(error);
|
|
17
26
|
}
|
|
18
27
|
};
|
|
19
28
|
|
|
20
|
-
useEffect(()=>{
|
|
29
|
+
useEffect(() => {
|
|
21
30
|
getBlogs();
|
|
22
|
-
},[]);
|
|
31
|
+
}, [page, debouncedSearch]);
|
|
23
32
|
|
|
24
33
|
return (
|
|
25
34
|
<div>
|
|
35
|
+
<input type="text" value={search} placeholder="search" onChange={(e) => setSearch(e.target.value)} />
|
|
26
36
|
|
|
27
37
|
<h1>Manage Blogs</h1>
|
|
28
38
|
|
|
29
39
|
{
|
|
30
|
-
blogs.map((blog)=>(
|
|
40
|
+
blogs.map((blog) => (
|
|
31
41
|
<div key={blog._id}>
|
|
32
42
|
|
|
33
43
|
<h3>{blog.title}</h3>
|
|
@@ -40,6 +50,43 @@ const ManageBlogs = () => {
|
|
|
40
50
|
))
|
|
41
51
|
}
|
|
42
52
|
|
|
53
|
+
{/* pagination */}
|
|
54
|
+
<div>
|
|
55
|
+
<button
|
|
56
|
+
disabled={
|
|
57
|
+
page === 1
|
|
58
|
+
}
|
|
59
|
+
onClick={() =>
|
|
60
|
+
setPage(
|
|
61
|
+
page - 1
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
>
|
|
65
|
+
Prev
|
|
66
|
+
</button>
|
|
67
|
+
|
|
68
|
+
<span>
|
|
69
|
+
{" "}
|
|
70
|
+
{page} / {
|
|
71
|
+
totalPages
|
|
72
|
+
}{" "}
|
|
73
|
+
</span>
|
|
74
|
+
|
|
75
|
+
<button
|
|
76
|
+
disabled={
|
|
77
|
+
page ===
|
|
78
|
+
totalPages
|
|
79
|
+
}
|
|
80
|
+
onClick={() =>
|
|
81
|
+
setPage(
|
|
82
|
+
page + 1
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
>
|
|
86
|
+
Next
|
|
87
|
+
</button>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
43
90
|
</div>
|
|
44
91
|
);
|
|
45
92
|
};
|
package/src/routes/AppRoutes.jsx
CHANGED
|
@@ -13,8 +13,9 @@ import ManageBlogs from "../pages/admin/ManageBlogs";
|
|
|
13
13
|
import AdminDashboard from "../pages/admin/AdminDashboard";
|
|
14
14
|
import AdminLayout from "../layout/AdminLayout";
|
|
15
15
|
import Register from "../pages/Register";
|
|
16
|
+
import { ProtectedRoute } from "../middleware/ProtectedRoute";
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
function AppRoutes() {
|
|
18
19
|
return (
|
|
19
20
|
<BrowserRouter>
|
|
20
21
|
{/* <Navbar /> */}
|
|
@@ -25,7 +26,7 @@ const AppRoutes = () => {
|
|
|
25
26
|
element={<Home />}
|
|
26
27
|
/>
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
<Route
|
|
29
30
|
path="/register"
|
|
30
31
|
element={<Register />}
|
|
31
32
|
/>
|
|
@@ -40,22 +41,28 @@ const AppRoutes = () => {
|
|
|
40
41
|
element={<CreatePost />}
|
|
41
42
|
/>
|
|
42
43
|
|
|
43
|
-
<Route
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
44
|
+
<Route
|
|
45
|
+
element={<ProtectedRoute />}
|
|
46
|
+
>
|
|
47
|
+
|
|
48
|
+
<Route path="/admin" element={<AdminLayout />}>
|
|
49
|
+
<Route index element={<AdminDashboard />} />
|
|
50
|
+
<Route
|
|
51
|
+
path="users"
|
|
52
|
+
element={<ManageUsers />}
|
|
53
|
+
/>
|
|
54
|
+
<Route
|
|
55
|
+
path="blogs"
|
|
56
|
+
element={<ManageBlogs />}
|
|
57
|
+
/>
|
|
58
|
+
{/* <Route
|
|
54
59
|
path="settings"
|
|
55
60
|
element={<Settings />}
|
|
56
61
|
/> */}
|
|
62
|
+
</Route>
|
|
63
|
+
|
|
57
64
|
</Route>
|
|
58
|
-
|
|
65
|
+
|
|
59
66
|
</Routes>
|
|
60
67
|
</BrowserRouter>
|
|
61
68
|
);
|